一、概念
(1)什么是跨域?
域名:一串用点分隔名字组成的,通过与ip地址相互映射,来标识Internet上某一台计算机或计算机组(集群)的,详见
跨域:是指一个域下的文档(html、jsp)或脚本(js)试图去请求另一个域下的资源
(2)跨域的分类
1、广义的跨域
1.) 资源跳转: A链接、重定向、表单提交
2.) 资源嵌入:<link>、<script>、<img>、<frame>等dom标签,还有样式中background:url()、@font-face()等文件外链
3.) 脚本请求: js发起的ajax请求、dom和js对象的跨域操作等
2、狭义的跨域:是由浏览器同源策略限制的一类请求场景,那么什么是同源策略呢?
(3)同源策略
概念:同源策略/SOP(Same Origin Policy)是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。
注意:跨域的安全限制是针对浏览器端(前端的请求)来说的,服务器端是不存在跨域安全限制的。即:可以把请求发到自己的服务端,再通过后台代码发起请求,再将数据返回另一个域的前端页面,不受同源限制!
举例:当一个浏览器的两个tab页中分别打开来百度和谷歌的页面,当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源(同一个服务器下的应用)的脚本才会被执行。 如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
同源策略限制以下几种行为:
1) Cookie、LocalStorage 和 IndexDB 无法读取
2) DOM 和JS对象无法获得
3) Ajax 请求不能发送
(4)常见跨域场景(了解)
1.同一域名,不同文件或路径,允许访问(同一个应用中的交互---前提是在WEB-INF外,在web目录中)
http://www.domain.com/a.js
http://www.domain.com/b.js
http://www.domain.com/lab/c.js
2.同一域名,不同端口,不允许
http://www.domain.com:8000/a.js
http://www.domain.com/b.js
http://www.domain.com/a.js
3.同一域名,不同协议,不允许
https://www.domain.com/b.js
http://www.domain.com/a.js
4.域名和ip是映射关系,不允许(重点理解)
http://192.168.4.12/b.js
http://www.domain.com/a.js
5.主域相同,子域不同,不允许
http://x.domain.com/b.js
http://domain.com/c.js
6.不同域名,不允许
http://www.domain1.com/a.js
http://www.domain2.com/b.js
(5)跨域解决方案
思路:
(1) 让服务器来加载远程数据,然后在用户请求时提供给浏览器。
(2) 用<script>或是<iframe>标签加载外来文件,因为他们的src属性允许获得任何地方的资源。
(3) W3C制定的Cross-Origin Resource Sharing(CORS,跨域资源共享)。
(4)JSONP
(2)的机理:尽管不能访问非本域的的动态资源,但是类似的js文件(样式、图片)等静态资源还是可以访问的,通过此"漏洞"解决跨域问题!
1、 通过jsonp跨域---了解其原理!
2、 document.domain + iframe跨域
3、 location.hash + iframe
4、 window.name + iframe跨域
5、 postMessage跨域
6、 跨域资源共享(CORS)---重点掌握(主流发展趋势)!
7、 nginx代理跨域 ---后续补充(先了解一些概念)
8、 nodejs中间件代理跨域 ---前端的
9、 WebSocket协议跨域 ---比较新颖(未来的发展趋势--重要!)
(6) 通过jsonp跨域
需求:通常为了减轻web服务器的负载(压力),我们把会把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。但是由于同源策略的限制,无法跨域获取数据,怎么办呢?
我的灵感:引入jquery方式,可以通过在线引入(联网--跨域)和本地引入(下载后)
在线引入:跨域请求获取的是此js脚本,就可以使用浏览器解析此js脚本,使用jquery中的方法等!
实际上:凡是拥有src属性的标签都有跨域的能力----<script>、<img>、<frame>等
Jsonp的诞生:
(1)首先因为Ajax无法跨域(受同源限制),然后开发者就有所思考。。。为什么不能通过"动态"创建script来引入跨域的数据?
(2)开发者小王蹲茅厕的时候灵感突现,<script>标签的src属性是是不受跨域访问的(即而script标签src属性中的链接却可以访问跨域的js脚本。说明:script标签中的src 属性可以指向任何地址)
(3)小王决定尝试下,思路:A域的first.html页面去请求B域的login,由于json传输数据比xml更优秀(详见),决定用json传输
①服务器A域端的请求页面(原生的JS)
<script>
var script = document.createElement('script');
script.type = 'text/javascript';
// 传参并指定回调执行函数为onBack
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=onBack';
// &callback=onBack表明:把要显示数据的函数也动态的传入了,服务器只需要把数据封装进去
//然后按照遵循的jsonp非官方协议--格式:函数名(json格式的数据),
//就能回调函数,把数据取出来,实现跨域请求获取数据!
//跨域服务端提供的js脚本动态生成,这样调用者可以传一个参数过去告诉跨域服务端
//“我想要一段调用XXX函数的js代码,请你返回给我”,
//于是跨域服务器就可以按照客户端的需求来生成js脚本并响应了,然后像访问本地函数一样访问里面的存储的数据
document.head.appendChild(script);
// 回调执行函数
function onBack(res) {
alert(JSON.stringify(res));
}
</script>
②服务器B域端的响应(处理)
package org.wzj.com;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/demo1")
public class ServletDemo1 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("其它域的请求来了");
//2--获取参数--普通参数和回调函数的名字
String user = request.getParameter("user");
System.out.println(user);
String callback = request.getParameter("callback");
System.out.println(callback);
//3--设置回调函数--传递给script的时候必须转义(前台只能识别json的双引号),给Java时不用转义(外层是双引号,里层是单引号)
String json = "{\"username\":\"zhangsan\"}";
System.out.println(json);
//4--设置回掉函数,响应也以字符串的形式
callback=callback+"("+json+")";
//打印的结果hehe
System.out.println(callback);
//5--通过流的形式给浏览器响应
response.getWriter().write(callback);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
神奇的一幕出现了:弹出了zhangsan
(4)小王开始分析作用的机理(总得有支撑吧!)
机理:script标签src属性中的跨域的能力(服务器响应的script tags),利用这个特性让服务器端B返回一段调用某个函数的js代码(script tags不再返回json格式的数据),在服务器A的src中进行了调用,通过浏览器解析JavaScript callback的形式实现站点访问,从而实现了跨域获取数据。实质:把跨域访问服务器写成调用本地的函数,从服务器B响应的js代码的回调函数中获取数据!调用跨域服务器A上动态生成的js格式文件(不管是什么类型的地址,最终响应的都是一段js代码)
jsonp理解:域A的页面通过script的src属性传递一个callback参数给跨域服务端,然后跨域服务端返回数据时会将这个callback参数作为函数名来包裹住json数据即可,返回的是一个js脚本,浏览器通过script标签对js文档进行动态解析,绕过了浏览器的同源策略。jsonp格式:回调函数名(json格式的数据)。
如何绕过同源策略呢?利用js构造了一个script标签,把跨域的url赋给script的src属性,把script插入到dom里面,script标签加载完后,浏览器会立即把服务器的响应当作js去解析执行,如果不是合法的js语句则报错!很显然把json数据放到一个回调函数中作为响应(符合js语法)。由于服务器不知道客户端的回调函数是什么?所以请求时带一个QueryString(callback=showData)告诉服务器端回调方法是什么,方便服务器向里面封装数据,同时浏览器端通过此回调方法解析响应的js,获取数据!注意:QueryString的key要遵从服务端的约定,设置为callback,表明这是一个回调函数而不是一个普通的请求参数!
补充:一个页面的ajax只能获取和此页面同域的数据,所以当我们需要跨域获取数据的时候就需要使用到JSONP方法来获取了
二、json、jsonp的定义和区别(重要!!!)
(1)区别
1、json是一种数据交换格式,jsonp是一种非官方跨域数据交互协议。
2、 json描述的是信息的格式,而jsonp是信息传递双方约定的方法。
3、json返回的是一串数据,而 jsonp返回的是脚本代码(包含一个函数调用)---返回的形式
4、json是理想的数据交换格式,但是没有办法跨域直接获取,于是就将json包裹在一个合法的js语句中作为js文件传过去。
理解1: json是想要的东西,jsonp是获取这个东西采取的方法,所以最终获得和处理的还是json。
理解2:可以这么说,json是目的,jsonp只是手段。json总会用到,而jsonp只有在跨域数据获取的时候才会用到。
理解3:靠JSONP来跨域获取回传的JSON数据,jsonp就是一种获取跨域json数据的方法
(2)定义
json可以理解为是一种格式,它的键必须用双引号。json格式的对象必须写在{}花括号里面,json格式的数组写在[]大括号里面。
JSON.parse(字符串)可以将json字符串转换成json对象;JSON.stringify(jsonObj)可以将json对象转换成json字符串。
jsonp实现跨域的原理
简介:利用script标签没有跨域(同源)限制的特点来达成和第三方通信---获取后台资源自动执行回调函数的方式!即:允许用户传一个callback给服务端,服务端返回数据会将这个callback参数作为函数名来包裹json数据,这样客户端就可以定制自己的函数来处理返回的数据。
相关链接:点击打开链接(发展过程)
三、Jquery封装的Jsonp
引入:Ajax可以很容易的满足这种无须刷新整个页面就可以实现数据交换的需求。但是它的缺点:不允许跨域请求资源,上面我们引入了script标签的src跨域特性来绕过同源策略!
说明:通过前面的分析,我们既要自己写script标签,又要将url以及请求参数和callback封装到src中,同时还要自己写回调函数接受,太麻烦。Jquery的Ajax已经帮我们完成了。那么如果用jQuery的话,就不用自己命名函数并传递给参数了,因为这个函数名一点也不重要,他只是个代号而已,jQuery会帮我们自动生成一个函数名(也可以自定义),然后将得到的数据传给这个函数。jQuery还会帮我们创建script标签, 将url、data(请求参数)、以及callback自动添加到src中,如果用户不指定回调函数,还可以通过success属性的方法的匿名回调函数来获取数据,我们只要关心如何处理这个数据就好了。
回顾JSONP原理:如果一个页面加载了一个外来的JS文件(响应的js),浏览器就会自动执行这个文件中的代码
A域请求页面(内置的success属性的方法)
JSP---请求页面:
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<html>
<head>
<title>跨域测试</title>
<script src="js/jquery-3.3.1.js"></script>
<script>
$(function () {
$('input').click(function () {
$.ajax({
'type':'GET',//只能是GET方式
'url':'http://localhost:9090/TestAjax/demo',
'dataType':'jsonp', //指定响应的数据类型为jsonp类型就可以跨域请求,如果是JSON形式,内置解析成对象了!
'success':function (data) { //请求成功之后如果不指定回掉函数的话,就会回调函数(匿名函数)
//alert(data);------Object说明是JSON对象,String说明是JSON字符串
$('#jsonText').val(data.username);
}
});
});
});
</head>
<body>
<textarea cols="50" rows="20" id="jsonText"></textarea>
<input type="button" value="跨域请求"/>
</body>
</html>
B域的响应
package org.wzj.com;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/demo")
public class ServletDemo2 extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("请求来了");
//如果不指定回调函数------jsonp方式会默认的给予一个callback()函数
//测试--指定回掉函数和不指定回掉函数输入的结果
//补充:不管指不指定回掉函数,都得通过callback()传入响应的json---协议就是这样!
String callback = request.getParameter("callback");
System.out.println(callback);
//说明:必须转义,否则前台无法解析'的 这种形式,前台只能识别双引号,所以要转移!
//注意:JS中可以写成'{"username":"zhangsan"}';也是可以的,最终也是要识别里面的双引号!
String json = "{\"username\":\"zhangsan\"}";
System.out.println(json);
// 我要给回调函数--传入响应
callback = callback + "(" + json + ")";
System.out.println(callback);
//接口页面返回的数据格式“函数(参数)”的jsonp格式
response.getWriter().write(callback);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
Ajax封装好的(自定义回调函数)
<%@ page contentType="text/html;charset=UTF-8" language="java" pageEncoding="utf-8" %>
<html>
<head>
<title>跨域测试</title>
<script src="js/jquery-3.3.1.js"></script>
<script>
$(function () {
$('input').click(function () {
$.ajax({
'type':'GET',
'url':'http://localhost:9090/TestAjax/demo',
'dataType':'jsonp', //指定数据类型(预处理)为jsonp类型就可以跨域请求
'jsonpCallback': "showData", //可以指定我自己写的回调函数--覆盖自动创建的
'success':function (data) { //请求成功之后如果不指定回掉函数的话,执行此匿名函数
alert("系统内置的回掉函数");
$('#jsonText').val(data.username);
}
});
});
});
</script>
<script>
//请求完后台,成功之后的回调函数
function showData(data) {
alert("我自己的回调函数");
$('#jsonText').val("hello");
}
</script>
</head>
<body>
<textarea cols="50" rows="20" id="jsonText"></textarea>
<input type="button" value="跨域请求"/>
</body>
</html>
说明:dataType是指定服务器的响应类型,没有指定的话会根据响应的MIME信息自动获取,当值为jsonp时,表明会用jsonp加载JSON块,当使用JSONP格式的时候,必须在请求的URL后面额外添加(Jquery的Ajax自动完成)?callback=?
说明:如果自定义的和内置的回调函数都有,会先执行自定义的,然后执行success的方法!
判断依据:弹窗以及展示的内容,以及文本框的内容是否发生变化!
细节问题
(1)Ajax直接请求普通文件存在跨域无权限的问题,不管是静态网页还是动态网页,只要跨域请求,一概不准!
上面原生的非Ajax请求,通过script的src属性可以跨域获取(响应)静态资源(js文件)或img标签的src获取(图片资源等)
(2)ajax和jsonp是本质不同的问题,ajax的核心是通过XMLHttpRequest获取非本页的内容,而jsonp核心则是动态的加载!
(3)JSONP协议的要点:允许用户传递一个callback参数给服务端,服务端会把这个callback参数作为函数名来包裹住JSON数据(json被原生js支持),客户端可以制定自己的函数来处理返回的数据!
(4)jsonP并非XHR,所以jsonP只能使用GET传递参数
四、CORS
历史:在前面的例子中了我们知道,现代浏览器默认都会基于安全原因而阻止跨域的ajax请求,只能同源使用
禁止跨域目的:保证网页不会被别的网页的JS篡改,或者伪造,网页的AJAX的地址不会被别人滥用等等
需求:浏览器向跨源服务器,发出XMLHttpRequest请求
解决思路呢?在讲之前先来了解一些概念。
概念:CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),CORS需要浏览器和服务器同时支持。目前所有浏览器都支持该功能(IE浏览器不能低于IE10)。整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器,只要服务器实现了CORS接口,就可以跨源通信。
戏说:摆脱了以往偷偷摸摸的特性,光明正大的同服务器小姐姐示爱!
特点(克服原来的弊端):它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
CORS的思想:就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求响应是应该成功还是应该失败。
交互过程
1:当发起跨域的Ajax的请求(Http请求--POST或GET),会类似带着以下请求头信息:
Origin:http://www.csdnblog.com
2:请求过后,响应头也会夹带着类似如下信息:
Access-Control-Allow-Credentials:true
Access-Control-Allow-Origin:http://www.csdnblog.com
说明:一来一回的请求决定了该请求是否会被浏览器通过。如果响应头中没有这个头部,或者有头部但是源信息不匹配(就是说返回头%-Allow-Origin中没有当前请求站点的域名),那么浏览器就会帮我们驳回这次请求,同源策略在这里发挥了作用。不难发现其实浏览器判断是否驳回的标准就是返回头中是否有 Access-Control-Allow-% 这个信息,并且判断这个信息是否合法(即这个相应头信息是否是与请求头中的Origin对应的上),对应的上就通过,对应不上就驳回。
如果浏览器小姐姐允许所有的人示爱怎么办?大开门户
header('Access-Control-Allow-Origin:*')
那么只能自求多福了,多与小姐姐交互哟!
3:浏览器处理Ajax异步请求
浏览器将CORS请求分成两类:简单请求和非简单请求,对不同的请求处理方式不一样!
(1) 请求方法是以下三种方法之一:
HEAD、GET、POST
(2)HTTP的头信息不超出以下几种字段:
Accept、Accept-Language、Content-Language、Last-Event-ID、
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
说明:只要同时满足以下两大条件,就属于简单请求,凡是不同时满足上面两个条件,就属于非简单请求。
五、跨源AJAX请求是简单请求
浏览器直接发出CORS请求,具体来说就是在请求头信息之中,增加一个Origin字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
Origin头信息字段表明:本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。如果Origin指定的源,不在服务器此servlet的许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个响应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被XMLHttpRequest的error回调函数捕获。注意这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
分析:上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头,下面进行详解。
(1)Access-Control-Allow-Origin(掌握)
该字段是必选的。它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
(2)Access-Control-Allow-Credentials(掌握)
该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。如果服务器端设置为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器禁止浏览器发送Cookie,删除该字段即可。
(3)Access-Control-Expose-Headers(了解)
该字段可选。CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,异步请求对象会通过getResponseHeader('FooBar')可以返回FooBar字段的值。
补充:CORS请求默认不发送Cookie和HTTP认证信息。如果要把Cookie发到服务器,一方面要服务器同意,指定Access-Control-Allow-Credentials字段:
Access-Control-Allow-Credentials: true
另一方面,开发者必须在Ajax请求中打开withCredentials属性,表明浏览器允许跨域发送Cookie
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
注意:如果不设置此属性(大部分浏览器),即使服务器同意发送服务器Cookie,浏览器也不会发送。但是对于部分浏览器,如果省略withCredentials设置,有的浏览器还是会默认发送Cookie,这时可以显式关闭withCredentials属性。
设置:xhr.withCredentials = false;
补充:如果要发送Cookie,Access-Control-Allow-Origin就不能设为(*)星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源策略,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。
六、跨源AJAX请求是非简单请求:
6.1 预检请求
非简单请求是那种对服务器有特殊要求的请求
(1)比如请求方法是PUT或DELETE
(2)Content-Type字段的类型是application/json等。
特点:非简单请求的CORS请求,会在正式通信之前,增加一次称作Preflight的OPTIONS的查询请求,称为"预检"请求。浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中Origin,以及可以使用哪些额外的HTTP头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
下面是一段浏览器的JavaScript脚本。
var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true); //PUT的提交方式
xhr.setRequestHeader('X-Custom-Header', 'value');//自定义头信息
xhr.send();
说明:HTTP请求的方法是PUT,并且发送一个自定义头信息X-Custom-Header。
浏览器发现是一个非简单请求,自动发出一个"预检"请求,要求服务器确认可以这样请求,下面是这个"预检"请求的HTTP头信息。
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
"预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,。
除了Origin字段(表示请求来自哪个源),"预检"请求的头信息包括两个特殊字段。
(1)Access-Control-Request-Method
该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
(2)Access-Control-Request-Headers
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
6.2 预检请求的回应
服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method、Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段
说明:如果服务器响应否定了"预检"请求(与预请求的不符),会返回一个正常的HTTP回应,但是没有任何CORS相关的响应头信息字段(三个信息)。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的error回调函数捕获。控制台会打印出如下的报错信息。
XMLHttpRequest cannot load http://api.alice.com.
Origin http://api.bob.com is not allowed by Access-Control-Allow-Origin.
服务器同意"预检"请求,服务器响应相应的CORS相关字段:
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
(1)Access-Control-Allow-Methods
该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
(2)Access-Control-Allow-Headers
如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
(3)Access-Control-Allow-Credentials
该字段与简单请求时的含义相同。
(4)Access-Control-Max-Age
该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应20天,在此期间,不用发出另一条预检请求。
6.3 浏览器的正常请求和回应
一旦服务器通过了"预检"请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。
下面是"预检"请求之后,浏览器的正常CORS请求。
PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
上面头信息的Origin字段是浏览器自动添加的。
下面是服务器正常的回应。
Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
说明:上面头信息中,Access-Control-Allow-Origin字段是每次响应都必定包含的。
代码:
七、与JSONP的比较
使用目的:CORS与JSONP的使用目的相同,但是比JSONP更强大。
请求方式:JSONP只支持GET请求,CORS支持所有类型的HTTP请求。
JSONP优势:在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。