通过 XHR 实现 Ajax 通信的主要限制,来源于跨域安全策略(同源策略)。XHR 对象只能访问与包含它的页面同源(同一个协议,同一个域名,同一个端口)的资源。虽然这个策略确实可以预防某些恶意行为,但是,实现合理的跨域请求对于开发某些浏览器应用程序也是至关重要的。
下面介绍几种利用 Ajax 实现跨域请求的解决方案:
1. CORS 跨域资源共享
CORS 是一个 W3C 标准,是 Cross-Origin Resource Sharing 的首字母缩写,翻译成中文是“跨域资源共享”。
它定义了在必须进行跨域访问时,浏览器和服务器应该怎么沟通。
1.1基本原理
使用自定义的 HTTP 头部,让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。
CORS 需要浏览器和服务器同时支持。目前所有浏览器都支持该功能,IE 不能低于 IE10。
CORS 通信与同源的 Ajax 通信,代码是一模一样的。不同的是,当浏览器发现 Ajax 跨源请求,就会 自动 添加一些 附加的头信息;有时,还会多出一些 附加的请求。
所以,浏览器在发现这是一个跨域请求的时候会自动帮我们处理一些事,只要服务端提供支持,前端是不需要做额外的事情的。
浏览器将 CORS 请求分为两类:简单请求(simple request)和非简单请求(not-so-simple)。我们来详细介绍一下这两种请求:
1.2 简单请求
只要同时满足以下两大条件,就属于简单请求:
- 采用以下一种请求方法
- HEAD
- GET
- POST
- HTTP 的请求头信息不超过一下的几个字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type: 只限三个值(application/x-www-form-urlencoded, multipart/form-data, text/plain)
1.2.1 流程
对于简单请求,浏览器直接发出 CORS 请求,具体来说就是在请求头信息中添加一个 Origin
字段,用来说明本次请求来自哪个源(协议+域名+端口):
服务器根据这个 Origin 的值,决定是否同意这次请求。
1.2.2 请求成功
如果 Origin 指定的源,在服务器的许可范围内,服务器返回的响应中,会多出几个头信息字段:
- Access-Control-Allow-Origin
该字段是必须的,它的字段要么是请求时 Origin 字段的值,要么是一个 *,表示接受任意域名的请求。 Access-Control-Allow-Credentials
该字段是可选的,它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包含在 CORS 请求中,设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发送给服务器。这个值也只能设为 true,如果服务器不要 Cookie,需要删除这个字段。
如果浏览器希望能带着 Cookie 一起发到服务器,就必须将 XHR 的withCredentials
属性值设为 true。var xhr = new XMLHttpRequest();xhr.withCredentials = true;如果省略这个属性的设置,有的浏览器还是会带 Cookie 一起发送。这时,可以显式地关闭:
var xhr = new XMLHttpRequest();xhr.withCredentials = false;Access-Control-Expose-Headers
该字段可选。CORS 请求时,XHR 对象只能读取到响应头的部分字段,如果需要拿到额外的字段,就需要在这个字段指定。
1.2.3 请求失败
如果 Origin 指定的源,不在服务器的许可范围内,那么服务器会返回一个正常的 HTTP 响应。浏览器没有在响应头中发现 Access-Control-Allow-Origin 这个字段,就知道出错了,从而抛出一个错误。这个错误能被 XHR 的 onerror 回调函数捕获。
这种错误无法根据 HTTP 状态码识别,回应的状态码有可能是200。
1.3 非简单请求
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT
、DELETE
,或者 Content-Type
字段的类型是 application/json
.
非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”(preflight)。
浏览器通过这次请求,询问服务器,当前网页所在的源是否在服务器的许可名单之中,以及可以使用哪些头信息字段。
只有得到肯定答复,浏览器才会发出正式的 XHR 请求,否则就报错。
|
上面的代码中,url 是异源,而且方法是 PUT,Content-Type
字段的类型是 application/json
,还增加了一个自定义字段。
浏览器发现,这是一个非简单 CORS 请求,就会自动发送一个预检请求,要求服务器确认可以这样请求。
一旦服务器通过了预检请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器回应,也都会有一个 Access-Control-Allow-Origin 头信息字段。
1.4 优缺点
优点:
- 功能强大,支持所有的 HTTP 请求方法
- 开发者可以使用普通的 XHR 发起请求和获得数据
- 能有效地进行错误处理
缺点:
- 兼容性问题,只有现代浏览器才支持
2. JSONP
JSONP 是 JSON with padding 的缩写,中文可以翻译成“填充式 JSON”或“参数式 JSON”,是应用 JSON 的一种新方法。
JSONP 看起来和 JSON 差不多,只不过是被包含在回调函数中的 JSON。
JSONP 由两部分组成,回调函数和数据。
回调函数的名字一般是在请求中指定的,而数据就是传入回调函数的 JSON。
下面是一个典型的 JSONP 请求:
上面的代码中通过查询字符串的形式指定了回调函数。
2.1 基本原理
浏览器请求脚本资源不受同源策略限制,并且请求到脚本资源后将其立即执行。
实现思路是,动态地向网页中添加一个 <script>
元素,通过这个元素的 src 特性,向异源服务器请求 JSON 数据;服务器收到请求后,将数据作为参数,放在一个指定名字的回调函数里传回来。
2.2 浏览器端
发送一个 JSONP 请求,在浏览器端的操作如下:
- 注册一个全局回调函数,这个函数接收一个参数,参数是期望得到的服务器端返回数据,函数的具体内容是处理这个数据。
- 向网页中动态插入
<script>
元素,由它向跨源网址发出请求
以下代码展示了浏览器端的操作:function foo(data) {alert("your girlfriend likes " + data.present);}function addScriptTag(src) {var script = document.createElement("script");script.setAttribute("type", "text/javascript");script.setAttribute("src", src);document.body.appendChild(script);}window.onload = function() {addScriptTag("http://test.com/present?callback=foo");}
2.3 服务器端
异源服务器接收到这个请求后,会将数据放在回调函数的参数位置返回。
由于<script>
元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了 foo 函数,该函数就会立即被调用执行。
另外,作为参数的 JSON 数据被视为 JavaScript对象,而不是字符串,因此避免了 JSON.parse 的步骤。
2.4 优缺点
优点:
- 兼容性很好,所有浏览器都支持
- 简单易用,能直接访问响应文本,支持在浏览器和服务器之间双向通信
缺点: - 只支持 GET 方法
- 要确定 JSONP 请求是否失败并不容易。虽然 HTML5给
<script>
元素新增了一个 onerror 事件处理程序,但目前还没有得到浏览器支持。开发人员不得不使用计时器检测指定时间内是否接收到了响应。
3. 搭建中间转发层(代理服务器)
思路是,通过在同源域名的服务器端架设一个代理服务器转发网页的请求,来实现同源策略。
你从客户端向你的服务器端的代理发出请求,而不是直接向异源服务器发出请求,然后代理把请求传递到异源服务器上,随后也是由代理向你的客户端传递返回数据。
也就是说,你的客户端始终在和你的服务器端打交道,这就符合了同源策略。
缺点是,你需要在服务器端进行额外的开发,而且因为中间经过了转发,所以网络开销和性能负载都有影响。