大家好,我是前端西瓜哥。
因为同源策略(Cross-Origin Policy)的存在,浏览器在一个域名下发送另一个域名的 Ajax 请求时,返回的数据通常会被浏览器拦截,让开发者无法拿到返回结果。
这里说 “通常”,是因为浏览器额外提供了一种可以正常使用 Ajax 请求非同源域名接口的机制,也就是我们接下来要说的 跨源资源共享(CORS, Cross-Origin Resource Sharing)。
CORS 会在上图中的第三步中发挥作用。
简单来说,就是请求 b.com 的 HTTP 响应头字段中的一些头字段符合一定规则,返回数据就不会被浏览器拦截,请求者能正常获得返回数据。
简单请求和非简单请求是浏览器发出跨域请求的 两种不同的请求方式,我们来了解一下。
简单请求
发送的请求符合下面的所有情况,就属于简单请求。
- 请求方法为其中一种:GET、POST、HEAD。
- 除了浏览器自动设置的字段(比如 Connection),仅能人为设置请求头字段 Accept、Accept-Language、Content-Language、Content-Type 这个集合内的值。
- 请求头字段 Content-Type 为其中一种:text/plain、 multipart/form-data、application/x-www-form-urlencoded。
(还有一些更多的琐碎的细节,比如脚本设置、特定浏览器相关的,这里不展开,具体可以查阅官方文档)
可能你会对这个规则的设计很感兴趣,其实它是为了 向后兼容,兼容一些之前没有 Ajax 时就可以发送的跨域请求,比如 form 元素能够产生的请求。
点击表单下的提交按钮,页面跳转然后发送请求。这种写法非常古老了,因为会自动跳转页面且不能拿到返回数据执行下一步的操作,实际开发基本上不会使用了
<form action="https://b.com/api/v1/book/get" method="get">
<input type="text" name="name" value="clean code" />
<input type="submit" value="提交"/>
</form>
CORS 机制下,HTTP 响应头字段需要使用头字段
Access-Control-Allow-Origin,将它的值设置为:
- *:任意域名都允许跨域请求。
- <origin>:具体的域名,这里不能提供多个域名,只能提供一个域名。这也是可以理解的,通过强制要求防止黑客得到服务端设置的白名单域名。我们不应该透露太多信息。服务端要实现跨域白名单功能,就需要根据请求头字段中动态返回该字段的值。
另外,如果请求中携带了身份信息,也就是 Cookie,不能使用 *,而需要指定具体域名。
这样设置后,我们就能正常地拿到其他域名接口返回的数据了。
下面我们再看看非简单请求。
非简单请求
不符合简单请求规定条件的请求,就是 非简单请求。
对于简单请求,为了兼容,浏览器会直接将请求发出去。但非简单请求没有兼容的需要,所以浏览器给它加上了严格的复杂机制。
在发出真正的请求前,浏览器会先发一个 OPTION 请求来探探路,这个请求称为 预检请求(preflight)。
浏览器发送的请求,会额外带下以下请求头字段:
- Access-Control-Request-Method:该字段告知服务端接下来要发起真正跨域请求使用的 HTTP 方法。
- Access-Control-Request-Headers:该字段告知服务端,当前请求使用的不符合简单请求规则的头字段,比如使用 JSON 结构来作为数据的格式,预检请求就会带上上 Access-Control-Request-Headers: content-type
下面是服务端的回合。
服务端拿到了足够多的请求方信息,然后就可以考虑是否允许跨域了。
服务端的响应头字段可以携带上以下字段:
- Access-Control-Allow-Origin:用法同上一节的简单请求说明。
- Access-Control-Allow-Methods:可以发送的请求方法。如 POST, GET, OPTIONS。
- Access-Control-Allow-Headers:可以使用的头字段,如 X-PINGOTHER, Content-Type
- Access-Control-Max-Age: 86400:缓存的有效时长,单位为秒,设置后,在这段时间内都可以免除预检请求的发送。浏览器自身预设了一个最大缓存时间,防止返回的缓存时间过长(比如好几年)导致的安全问题。
浏览器拿到这些字段后,就会进行对比,如果不符合规则,那么真正的跨域请求将不会发送。如果不符合,接下来就会发送真正的跨域请求。
需要注意的是,真正的请求和简单请求一样,需要提供Access-Control-Allow-Origin 头字段,否则依旧拿不到返回数据。
结尾
CORS 真正的形态是非简单请求,它不会立即发送真正的请求,而是额外发送 OPTION 方法的预检请求,让服务端来决定是否允许即将发送的请求。
简单请求是向后兼容的妥协产物,它其实直接向目标发起了真正请求,只是浏览器可能会将返回的数据拦截掉,如果响应头没有设置正确的
Access-Control-Allow-Origin 的话。