什么是跨域及来源
跨域问题来源于浏览器的同源策略,JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。
什么是同源策略
同源策略是浏览器为安全性考虑实施的非常重要的安全策略。只有协议、端口、和域名相同,则允许相互访问。
-
同源策略举例分析
对http://www.baidu.com/src/home/index.html来说,
http是协议,www.baidu.com是域名。端口号如下表第四行的:8080所示。
-
Ajax跨域代码示例
在这个代码中,我们创建了一个oBtn
按钮对象,当点击它的时候会创建名为xhr
的XMLHttpRequest对象,想让它获取api.binstd.com
上的天气数据。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ajax</title>
</head>
<body>
<div id="mydiv">
<button id="btn">点击</button>
</div>
</body>
<script type="text/javascript">
window.onload = function() {
var oBtn = document.getElementById('btn');
oBtn.onclick = function() {
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
alert( xhr.responseText );
}
};
xhr.open('get', 'https://api.binstd.com/weather2/query?appkey=0baae6a71fc7209b&city=安顺&date=2018-01-01', true);
xhr.send();
};
点击按钮发现浏览器提示如下,也就是说,我们的访问被拒绝了。这个就是实际的跨域问题。如何解决跨域问题
1.通过JSONP解决
JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写。
本质上是利用HTML元素的src属性都可以跨域的思路来解决的。
img
,script
,iframe
等标记的src属性的值都可以赋成其它域名的合法地址。我们回想一下,在添加img
标签的时候,里面有src
属性,属性的链接可以是任何图片的有效链接,这时候是没有跨域的问题的。
下图描述了用JSONP解决跨域问题的基本原理。
该过程涉及到客户端和服务器端两部分:
- 在客户端,我们在
src
里面写上请求地址以及回调函数func()
,并把回调函数放在全局,然后向服务器发送请求 - 服务器在收到我们的请求以后,先准备我们请求的数据,然后返回函数执行字符串
'func('+JSON.stringify(data)+')'
。 - 客户端执行这条语句(回调函数)。
现在我们用JSONP方案来改进Ajax跨域代码示例,代码如下。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP实现跨域2</title>
</head>
<body>
<div id="mydiv">
<button id="btn">点击</button>
</div>
</body>
<script type="text/javascript">
function handleResponse(response){
console.log(response);
}
</script>
<script type="text/javascript">
window.onload = function() {
var oBtn = document.getElementById('btn');
oBtn.onclick = function() {
var script = document.createElement("script");
script.src = "https://api.binstd.com/weather2/query?appkey=0baae6a71fc7209b&city=安顺&date=2018-01-01&callback=handleResponse";
//在body的第一个孩子之前,插入script节点
document.body.insertBefore(script, document.body.firstChild);
};
};
</script>
</html>
代码是在oBtn
点击事件里面添加了一个script
标签,它的src
属性里面包含了请求地址和回调函数。
点击按钮,请求数据成功,控制台打印如下。
JSONP之需要注意的几点:
1、jsonp没有使用XMLHttpRequest对象。
2、jsonp只支持Get方式
2.通过CORS标准解决
细心的你可能会发现,示例代码的错误里面有这句话... origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header ...
,意思是原要求被CORS政策限制了。
什么是CORS
CORS:全称"跨域资源共享"(Cross-origin resource sharing)。
CORS需要浏览器和服务器同时支持,才可以实现跨域请求,目前几乎所有浏览器都支持CORS,IE则不能低于IE10。CORS的整个过程都由浏览器自动完成,前端无需做任何设置,跟平时发送http请求并无差异。
所以,实现CORS的关键在于服务器,只要服务器实现CORS接口,就可以实现跨域通信。
CORS请求类型
CORS请求类型分为简单请求和非简单请求,后者需要预检请求。
-
简单请求
符合以下条件的,为简单请求
使用下列方法之一:
* GET
* HEAD
* POST
Content-Type的值仅限于下列三者之一:
* text/plain
* multipart/form-data
* application/x-www-form-urlencoded
简单请求的流程如下:
客户端:在客户端的
header
里面添加Origin
信息,将域名写在里面,然后正常的发请求到服务器。服务端:服务端根据
Access-Control-Allow-Origin
属性来判别这个请求源是否可以请求成功。Access-Control-Allow-Origin
:* 标识任何外域Access-Control-Allow-Origin
: 允许访问源(多个源用,分隔)
如果客户端的origin在上述允许源的范围内,则可以正确返回数据。这里其实相当于服务器给特定的源开一个“会员”通道,只有满足条件的才能进。
-
非简单请求
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
举例来讲,现在发一个put请求,并添加header信息:
var url = 'https://api.binstd.com/weather2';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
浏览器发现这是一个非简单请求以后,就会自动发出“预检请求”,请求的一部分如下:
OPTIONS /cors HTTP/1.1
Origin: 原域名
Access-Control-Request-Method: PUT //请求方式
Access-Control-Request-Headers: X-Custom-Header //请求头
服务器在收到上述的预检请求以后,检查上述字段是否在“会员通道”中,如果在的话,会确认该跨源请求,并作出回应。
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: 允许的域名 //可以为特定的域名,也可以用*代表允许任何跨源请求
Access-Control-Allow-Methods: GET, POST, PUT //允许的方式
Access-Control-Allow-Headers: X-Custom-Header //允许的头信息
Content-Type: text/html; charset=utf-8
...
一旦通过了预检请求以后,后面的就和简单请求一样了。
3.使用代理服务器
有时候处于安全考虑或者因为在实际开发中多个环境,在部分环境下,我们需要前端自己解决跨域问题。这个时候我们会采用一种名为代理服务器方法。
下图为该方法的原理:
该方法涉及到客户端,代理服务器和服务器三个部分:
- 客户端:发送请求到代理服务器上面
- 代理服务器:代理服务器设置CORS(代理服务器可以自己设置),并将浏览器的请求转发到服务器
- 服务器:服务器和代理服务器之间是没有同源的策略的,所以服务器可以处理代理服务器的转发请求
代理服务器不是请求的生产者,只是请求的搬运工。
现在我们用代理服务器方案来进行代码示例:
需求:用代理服务器解决ajax请求天气数据的跨域问题。
TIPS:数据接口API可以上聚合数据或者是进制数据,里面有免费数据API。
这个方法中需要改变的是客户端和代理服务器的设置:
- 客户端:正常发送ajax请求,将请求url写成代理服务器的地址
ajax({
type: 'get',
url: 'http://localhost:7777', //代理服务器的域名
data: {
city: city,
key: ************
},
success: resultSuccess,
error: function (error) {
console.log('error', error);
}
});
function ajax (options) {
options = options || {};
options.data = options.data || {};
var json = json(options);
// ajax请求
function json(options) {
// 请求方式,默认是GET
options.type = (options.type || 'GET').toUpperCase();
var xhr = null;
// 实例化XMLHttpRequest对象
if(window.XMLHttpRequest) {
xhr = new XMLHttpRequest();
} else {
// IE6及其以下版本
xhr = new ActiveXObjcet('Microsoft.XMLHTTP');
};
// 监听事件
xhr.onreadystatechange = function() {
if(xhr.readyState == 4) {
var status = xhr.status;
if(status >= 200 && status < 300) {
options.success(JSON.parse(xhr.responseText));
} else {
//options.error && options.error(status);
options.error(status);
}
}
};
// 连接和传输数据
if(options.type == 'GET') {
xhr.open(options.type, options.url + '/?city='+ options.data.city + "&key=" + options.data.key, true);
xhr.send(null);
} else {
xhr.open(options.type, options.url, true);
//设置提交时的内容类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
xhr.send(options.data);
}
}
}
- 代理服务器:设置CORS标准,允许客户端访问,并将请求转发到真正的请求地址上。
//设置CORS标准的两种方式
response.writeHead(200,{"Access-Control-Allow-Origin":"http://127.0.0.1:5500/weather/index.html",
"Content-Type":"text/plain;charset=utf-8"});
response.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5500");
//转发请求到聚合数据API上
http.get('http://apis.juhe.cn/simpleWeather/query?' + parsedUrl.query, res => {
var body = '';
res.on('data', data => {
body += data;
});
请求成功啦,header信息如下:
代理服务器之需要注意的几点:
1、这里的CORS标准就是“会员通道”的通行证,我们还可以设置其他条件(详情见第二小节)。
2、当使用 response.setHeader()
设置响应头时,它们将与传给 response.writeHead()
的任何响应头合并,其中 response.writeHead()
的响应头优先。详情请移步链接[9][10]。
参考链接:
[1]轻松搞定JSONP跨域请求
[2]珠峰培训官方
[3]原生JS的面试题:jsonp的实现原理
[4]HTTP访问控制(CORS)
[5]cors实现请求跨域
[6]跨域资源共享 CORS 详解
[7]跨域 CORS
[8]前端项目中nginx 本地反向代理配置
[9]response.setHeader(name, value)
[10]response.writeHead(statusCode[, statusMessage][, headers])