跨域数据交互
在实际项目中,ajax 经常应用在跨域通信的场景下,如果采用 jsonp 来实现跨域,需要对前后端代码有很大的改动。而且, jsonp 仅支持 get 请求
跨域。为此我们必须寻找一个简单的跨域通信方式 - CORS。
什么是 CORS
CORS是一种允许当前域(domain)的资源(比如html/js/web service)被其他域(domain)的脚本请求访问的机制。使用CORS,可以通过普通的XMLHttpRequest发起请求和获得数据,并且支持各种类型请求。说白了就是利用XMLHttpRequest来实现跨站通信,而不是仅仅遵循同源策略,再也不用为了ajax能跨域写苦逼的jsonp了。 CORS技术现在已经被广泛的支持了。 兼容性如下:
CORS 兼容大部分浏览器, IE8 及其以下就自求多福吧。
CORS 使用方法及场景举例
所谓的跨域请求,从本质上来说并不是浏览器对其他域名的请求不能发送,而是请求可以正常发起,
但是浏览器在收到服务器信息后就屏蔽掉了,并向前端报错(跨域)。如图所示,跨域请求结果被屏蔽。
解决方案,服务器在响应HTTP头部加入 Access-Control-Allow-Origin:*
即可,这样就表示服务端同意任意域名的请求。
一般我们会指定可响应的域名如:Access-Control-Allow-Origin: http://b.com, http://c.com
等。这样浏览器在检测到服务端HTTP头部的时候就可以不再拦截响应。
请求头部:
响应头部:
就在我们以为轻松搞定的时候,我们又遇到了问题。前端要给后端传json格式的数据于是在http头部加入了content-type: application/json
, access_token
等信息。请求再次失败,通过观察控制台信息我们发现,多了一个请求options
:
原来在CORS中的请求分为两种:简单请求和复杂请求
简单请求
- 只使用GET,HEAD或者POST。如果使用POST来发送数据到服务器,那么使用HTTP POST请求发送到服务器的数据的Content-Type为以下几种之一:application/x-www-form-urlencoded,multipart/form-data以及text/plain。
- 不使用HTTP请求发送定制请求头(例如X-Modified等)
在简单请求下,我们只需要像上面所说设置Access-Control-Allow-Origin头部即可。
复杂请求
- 使用了除GET,HEAD和POST以外的方法。如果使用POST方法发送请求数据时的
Content-Type
不是application/x-www-form-urlencoded,multipart/form-data
或者text/plaint
。例如,如果POST请求向服务器使用application/xml或者text/xml向服务器发送请求,那么这个请求就是preflighted的。 - 设置了定制请求头的请求(例如,请求使用了例如X-PINGOTHER这样的请求头)。这类请求在发送正式请求之前会发送一个
Preflighted(预请求)
,Preflighted
请求首先通过HTTP OPTIONS方法请求其他域上的资源,以确定发送实际的请求是否安全。这样做,是因为跨站请求可能会对目的站点的数据造成破坏。我们第二次发的请求是一个复杂请求,服务端没有响应options的方法,导致预请求失败,之后的请求也就终止了。我们看一下复杂请求下的HTTP报文:
Prelignted请求头部:
可以看到报文头部信息为OPTIONS请求
预请求服务端响应报文
预请求响应只要返回一个2xx表示成功的响应信息即可说明服务端同意了跨域请求,
这里我们选择204的状态码作为响应状态。因为204
表示响应成功,并且没有结果返回的状态。这样节省了传输信息的事件,加快了预请求的处理。
预处理结束后,事情并没有就此解决。错误信息再次传来。
原来我们自定义了头部信息,还要在响应报文中加入Access-Control-Allow-Headers
来指出服务端允许的复杂请求需要的自定义头部信息。注意这里不能用*来表示所有,
只能一个个添加。例如:"Access-Control-Allow-Methods" : "PUT,POST,GET,DELETE,OPTIONS"
。
这样就齐活了,总算是数据可以流通了。这里我建议在服务端响应部分也加入 "Access-Control-Allow-Methods" : "PUT,POST,GET,DELETE,OPTIONS"
来指出可接受的请求类型。
最后我们的服务端响应头部信息:
附带凭证信息的请求
我手贱的在xhr中加入了 xhr.withCredentials = true
因为跨源请求默认请求头部中不提供凭据(cookie、HTTP认证及客户端SSL证明等)。
为了能把这些信息带上,我们设置了withCredentials为true。浏览器再次报错,通过检查我们发现,
如果你设置了withCredentials为true那么 Access-Control-Allow-Origin就不能用 * ,必须使用明确的域名。
而且还要为你的响应头加上"Access-Control-Allow-Credentials":"true"
信息。
最后我列出CORS中可能使用的头部信息供大家参考:
HTTP响应头:Access-Control-Allow-Origin,Access-Control-Expose-Headers,Access-Control-Max-Age,
Access-Control-Allow-Credentials,Access-Control-Allow-Methods,Access-Control-Allow-HeadersHTTP请求头:Origin,Access-Control-Request-Method,Access-Control-Request-Headers
附上用Node.js跨域请求服务端的简单配置:
|
|
跨域请求的其他方案
这里列举一下其他常用的跨域方案,仅供参考:
jsonp
:利用<script>
元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。postMessage
:postMessage()方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。websocket
:WebSocket实现了全双工通信,使WEB上的真正的实时通信成为可能。浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。SSE
:Server-Sent Events(SSE)功能,允许服务端推送数据到客户端(通常叫数据推送)。已经在浏览器上普遍支持,然而IE和Edge全系列不支持。ServiceWorker
:一个 service worker 是一段运行在浏览器后台进程里的脚本,它独立于当前页面,提供了那些不需要与web页面交互的功能在网页背后悄悄执行的能力。在将来,基于它可以实现消息推送,静默更新以及地理围栏等服务,但是目前它首先要具备的功能是拦截和处理网络请求,包括可编程的响应缓存管理。简而言之,这是让浏览器具有服务器功能的API,有了他还跨域个球球。不过这只是实验中API目前只有chrome和firfox高版本浏览器支持。