1、跨域的原因
跨域是是因为浏览器的同源策略限制,是浏览器的一种安全机制,服务端之间是不存在跨域的。
所谓同源指的是两个页面具有相同的协议、主机和端口,三者有任一不相同即会产生跨域。
2、跨域举例
3、跨域的解决办法
3.1 浏览器限制
既然跨域是浏览器的一种安全限制,那是否可以让浏览器不做跨域限制呢?答案是肯定的,我们设置浏览器禁止检查--disabled-web-security --user-data-dir,重新打开浏览器,会发现在浏览器顶部提示:
设置成功,跨域请求可被正常发送出去。
3.2、jsonp的实现方式
jsonp的实现原理为:虽然浏览器限制了页面直接向非同源的服务器发送ajax请求,但是并没有限制
页面向非同源的服务器请求JS脚本,就像我们可以使用标签请求任意域上图片一样。
当使用jsonp方式来实现跨域请求时,需要服务端进行配合。因为一般使用ajax请求从服务端返回的数据都是json对象,但是我们需要的是JS脚本,这就需要服务端将返回的数据形式变成JS脚本形式,所以jsonp是一种约定(实现方式将在下面进行讲解),是前后端为了实现跨域达成的一种协议。
当我们在本地请求慕课网上的地址时,控制台报错,提示跨域。(之所以请求慕课网的url,是因为慕课网支持jsonp,我们暂且理解为慕课网后台已经配合进行了改动)。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="jquery-3.5.1.min.js"></script>
</head>
<body>
<a href="#" onclick="get1()">发生跨域请求</a>
<textarea id="text" style="width: 400px; height: 100px;"></textarea>
<script>
function get1() {
$.ajax({
url:"https://www.imooc.com/index/getstarlist",
type: "GET"
})
}
</script>
</body>
</html>
修改请求的方式,使用jsonp的方式发送跨域请求。在ajax请求中设置dataType="jsonp",
$.ajax({
url:"https://www.imooc.com/index/getstarlist",
type: "GET",
dataType:"jsonp"
})
这时候看到请求成功了。
服务端返回结果为:
细心观察发现,getstarlist请求后面自动跟上了callback参数,参数的值与服务端返回的函数名称相同
由此可以得出jsonp的实现原理为:当前端请求设置dataType="jsonp"时,浏览器会自动在url中添加callback="jqueryxxx"请求参数。对应服务器在接收到callback参数时,知道了这是一个jsonp请求,就会自动把返回的json对象转化为"jqueryxxx(json对象)"脚本的形式返回。
那为什么将dataType="jsonp"的形式,跨域请求就能成功呢?原因是因为当设置ajax请求的dataType="jsonp"时,实际上是在<html>的<head>中动态添加了一条<script>标签。
通过在开发者工具下的source下的jquery包中打断点,
刷新页面,可以看到dom元素中多了一条<script>标签,这条标签便是浏览器向服务端请求的JS脚本。
所以,jsonp向服务端发送的是script脚本,而普通的ajax请求发送的是xhr请求,这也是为什么使用jsonp能实现跨域的原因。
请求回来的js脚本(函数名称包裹的json对象)自动执行,默认success()函数作为回调函数。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<script src="jquery-3.5.1.min.js"></script>
</head>
<body>
<a href="#" onclick="get1()">发生get1请求</a>
<textarea id="text" style="width: 400px; height: 100px;"></textarea>
<script>
function get1() {
$.ajax({
url:"https://www.imooc.com/index/getstarlist",
type: "GET",
dataType:"jsonp",
success:function(data){
var result = JSON.stringify(data); //json对象转成字符串
$("#text").val(result);
}
})
}
</script>
</body>
</html>
页面展示效果:
Jsonp的弊端:
虽然jsonp解决了跨域问题,但是存在以下几点弊端:
(1)jsonp需要服务端配合修改,如果调用的接口不是我们自己的服务器上,jsonp就无能无力了.
(2)jsonp只能发送get请求
(3)jsonp发送的时script请求,并不是xhr请求,因此xhr的很多优势都不能使用(例如:异步,事件等)
3.3 跨域资源共享cors解决跨域
在了解cors解决跨域之前,我们先来了解一下什么是简单请求和非简单请求。
工作中比较常见的简单请求:
- 请求方法为:HEAD、GET、POST中的一种。
- HTTP请求头中字段不超过:Accept、Accept-Language、Content-Language、Last-Event-ID
- Content-Type字段值为application/x-www-form-urlencoded
、
multipart/form-data、
text/plain中的一种。
满足以上条件的即为简单请求,否则即为非简单请求。
工作中常见的非简单请求:
- 请求方法为put、delete.
- 发送JSON格式的ajax请求。
- http中带自定义请求头。
对于简单请求:
浏览器发现是跨域请求,就会自动在请求头中加上Origin字段,代表请求来自哪个域(协议+主机名+端口号)。服务器在收到请求后,根据请求头中Origin字段值来判断是否允许跨域请求通过。具体实现方法是:在响应头Access-Control-Allow-Origin字段中设置指定的域名,表示允许这些域名的跨域请求。如果请求头中Origin字段的域名包含在这些域名中,则可以实现跨域请求(当然有时候还需要结合其他字段来判断),否则不通过。例如:
请求头信息:
GET /cors HTTP/1.1
Origin: http://localhost:8080/
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Origin字段说明本次请求来自:http://localhost:8080/。
响应头信息:
Access-Control-Allow-Origin: http://localhost:8080/
Access-Control-Allow-Credentials: true
Content-Type: text/html; charset=utf-8
说明 http://localhost:8080/在服务器允许的范围内。
Access-Control-Allow-Credentials字段代表服务器允许cookie可以包含在请求中,一起发送给服务器,值为布尔类型。如果要把cookie一起发送到服务器,还需要在请求中打开withCredentials
属性。
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
注意:如果要发送cookie,Access-Control-Allow-Origin的值不能为“*”,只能是具体的域名。
非简单请求:
非简单请求在发送http请求时,会预先发送一次“预检”(OPTIONS)请求。预检请求会事先询问服务器,当前域名是否在服务器允许的范围内,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复后,浏览器才会真正发出http请求,否则就会报错。例如:
var url = 'http://localhost:8080/';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('Header-1', 'value');
xhr.send();
上述代码中,http发送了一条PUT请求,并且自定义请求头信息Header-1.
Access-Control-Allow-Origin: http://localhost:8080/
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Header-1
Content-Type: text/html; charset=utf-8
Access-Control-Max-Age: 3600
上述http响应中,说明服务器允许http://localhost:8080/请求数据,
Access-Control-Max-Age表示本次预检请求的有限期,单位为秒,在此期间内,不用发出另一条预检请求。
一旦服务器通过了“预检”请求,以后每次浏览器正常请求CORS请求,就跟简单请求一样。会有Origin字段,响应头里也会有对应的Access-Control-Allow-Origin字段。
CORS相比较JSONP的优势:
CORS支持所有类型的http请求,jsonp只支持get请求。JSONP的优势在于支持老式浏览器,以及向不支持CORS的网站请求数据。
3.4、nginx/apache/webpack中做相应配置
参考资料:
http://www.ruanyifeng.com/blog/2016/04/cors.html
https://www.cnblogs.com/chiangchou/p/jsonp.html
https://www.imooc.com/learn/947