一, 简介
CORS(Cross-Origin Resource Sharing, 跨域资源共享), 它允许浏览器向服务器发出 XMLHttpRequest 请求, 从而克服 AJAX 只能同源使用的限制.
PS: 同源: 协议相同, 域名相同, 端口相同.
http://www.example.com/dir/page.html
, 协议是 http://, 域名是:/www.example.com, 端口是
- 80
- (默认省略).
http://www.example.com/dir2/other.html: 同源
http://example.com/dir/other.html: 不同源(域名不同) http://v2.www.example.com/dir/other.html: 不同源(域名不同)
http://www.example.com:81/dir/other.html: 不同源(端口不同)
CORS 需要浏览器和服务器同时支持. 目前使用 XMLHttpRequest 实现的方式(仅支持异步), 所有浏览器都支持, 但 IE10+, 低版本的 IE 有其他实现方式.
CORS 的背后思想: 使用自定义的 HTTP 头部, 让浏览器与服务器进行沟通, 从而决定请求或响应是该成功还是应该失败. 在发送请求时, 浏览器自行附加一个 Origin 头部, 包含发出请求页面的原信息(协议, 域名和端口), 然后服务器根据这个头部信息来决定是否给予响应.
客户端发出
Origin:http://www.abc.com
, 如果服务器认为这个请求可以接受, 就返回
Access-Control-Allow-Origin:http://www.abc.com
, 如果没有该返回头部, 或者头部源信息不匹配, 浏览器就会驳回请求.
整个 CORS 通信过程, 都是浏览器自动完成, 不需要用户参与. 对于开发者来说, CORS 通信与同源的 AJAX 通信没有差别, 代码完全一样. 浏览器一旦发现 AJAX 请求跨源(请求的 url 跨域路径是绝对路径), 就会自动添加一些附加的头信息, 有时还会多出一次附加的请求, 但用户不会有感觉.
二, 两种请求
CORS 分为简单请求和非简单请求.
简单请求同时满足以下两大条件:
1. 请求方式是以下三种方法之一:
GEAD ,GET, POST
2.HTTP 的头部信息不超出以下几种字段:
Accept,Accept-Language,Content-Language,Last-Event-ID,Content-Type: 只限于三个值, application/x-www-from-urlencoded(表单序列化),multipart/form-data(上传文件),text/plain(纯文本的形式, 浏览器在获取到这种文件时并不会对其进行处理)
凡是不同时满足上面两个条件, 就是非简单请求. 浏览器的处理方式不一样.
三, 简单请求
1. 基础信息
简单请求, 浏览器直接发出 CORS 请求. 在头信息增加 Origin 字段
浏览器简单 AJAX 跨域请求:
- 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 指定的源, 不在许可范围内, 服务器会返回一个正常的 HTTP 响应. 浏览器发现, 这个回应的头信息没有包含
Access-Control-Allow-Origin
字段, 就知道出错了, 从而抛出一个错误, 被 XMLHttpRequest 的 onerror 回调函数捕获. 注意, 这种错误无法通过状态码识别, 因为 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 字段的值.
2.withCredentials 属性
CORS 请求默认不发送 Cookie 和 HTTP 认证信息. 如果要把 Cookie 发到服务器, 一方面要服务器同意, 指定
Access-Control-Allow-Credentials
字段.
Access-Control-Allow-Credentials:true
另一方面, 开发者必须在 AJAX 请求中打开 withCredentials 属性.
var xhr = new XMLHttpRequest(); xhr.withCredentials = true;
否则, 即使服务器同意发送 Cookie, 浏览器也不会发送. 或者, 服务器要求设置 Cookie, 浏览器也不会处理.
但是, 如果省略 withCredentials 设置, 有的浏览器还是会一起发送 Cookie. 这时, 可以显式关闭 withCredentials.
xhr.withCredentials = false;
需要注意的是, 如果要发送 Cookie,
Access-Control-Allow-Origin
就不能设为星号, 必须指定明确的, 与请求网页一致的域名. 同时, Cookie 依然遵循同源政策, 只有用服务器域名设置的 Cookie 才会上传, 其他域名的 Cookie 并不会上传, 且 (跨源) 原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie.
四, 非简单请求
1. 预检请求
非简单请求时对服务器有特殊要求的请求, 例如请求方法是, PUT(让服务器创建一个文件, 文件名是请求的 url, 文件内容是请求报文的主体)|DELETE(删除请求 url 指定的资源); 或者 Content-Type 字段是 application/json
非简单请求, 正式通信之前, 增加一次 HTTP 查询请求, 叫做 "预检请求"
浏览器先询问服务器, 当前网页所在的域名是否在服务器的许可名单之中, 以及可以使用那些 HTTP 动词 (请求方法) 和头部信息字段. 只有得到肯定的答复, 浏览完才会正式发出 XMLHttpRequest 请求, 否则就报错.
JS 脚本, PUT 请求, 且发送一个自定义头信息 X-Custom-Header
- > var url = 'http://api.alice.com/cors';
- > var xhr = new XMLHttpRequest();
- > xhr.open('PUT', url, true);
- > xhr.setRequestHeader('X-Custom-Header', 'value');
- > xhr.send();
浏览器发现, 这是一个非简单请求, 就自动发出一个 "预检请求", 要求服务器确认允许这样的请求. 下面是一个预检请求的头信息.
- 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.
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://api.bob.com 可以请求数据. 该字段也可以设为星号, 表示同意任意跨源请求.
如果浏览器否定了预检请求, 返回一个正常的 HTTP 回应, 但是么有任何 CROS 相关的头信息字段. 这时, 浏览器就会认定, 服务器不同意预检请求, 因此触发一个错误, 被 XMLHttpRequest 对象的 onerror 回调函数捕获.
服务器回应的其他 CORS 相关字段如下.
Access-Control-Allow-Methods: GET, POST, PUT
- Access-Control-Allow-Headers: X-Custom-Header
- Access-Control-Allow-Credentials: true
- (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 秒), 即允许缓存该条回应 1728000 秒(即 20 天), 在此期间, 不用发出另一条预检请求.
- Access-Control-Max-Age
- : 1728000
浏览器的正常请求和回应
- > 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
- > Access-Control-Allow-Origin: http://api.bob.com
- > Content-Type: text/html; charset=utf-8
- function creaeCORSRequest(method,url){
- var xhr=new XMLHttpRequest();
- if("withCredentials" in xhr){
- xhr.open(method,url,true);
- }else if(typeof XDomainRequest!="undefined"){
- xhr=new XDomainRequest();
- xhr.open(method,url);// 没有第三个参数
- }else{
- xhr=null;
- }
- return xhr;
- }
- var request=createCORSRequest("get","http://www.abc.com");
- if(request){
- request.onload=function(){
- console.log(request.responseText);// 处理响应信息
- };
- request.onerror=function(){
- // 处理错误
- }
- request.send();
- }
来源: http://www.jianshu.com/p/50b2a9fd9f78