[转]利用CORS实现跨域请求

from:http://newhtml.net/using-cors/html

跨域请求一直是网页编程中的一个难题,在过去,绝大多数人都倾向于使用JSONP来解决这一问题。不过如今,咱们能够考虑一下W3C中一项新的特性——CORS(Cross-Origin Resource Sharing)了。html5

 

本文的全部代码均来自http://www.html5rocks.com/en/tutorials/cors/,若是您对其中的任何技术细节存在疑问,请以原文为准。web

客户端

建立XmlHttpRequest对象

对于CORS,Chrome、FireFox以及Safari,须要使用XmlHttpRequest2对象;而对于IE,则须要使用XDomainRequest;Opera目前还不支持这一特性,但很快就会支持。编程

所以,在对象的建立上,咱们不得不首先针对不一样的浏览器而进行一下预处理:json

 

 

事件处理

原先的XmlHttpRequest对象仅仅只有一个事件——onreadystatechange,用以通知全部的事件,而如今,咱们除了这个事件以外又多了不少新的。api

事件 说明
onloadstart* 当请求发生时触发
onprogress 读取及发送数据时触发
onabort* 当请求被停止时触发,如使用abort()方法
onerror 当请求失败时触发
onload 当请求成功时触发
ontimeout 当调用者设定的超时时间已过而仍未成功时触发
onloadend* 请求结束时触发(不管成功与否)

注:带星号的表示IE的XDomainRequest仍不支持。
数据来自http://www.w3.org/TR/XMLHttpRequest2/#events
跨域

绝大多数状况下,咱们只须要和onloadonerror打交道,就像下面这样:浏览器

 

 

这儿有一点小异样。尽管咱们能够经过onerror得知请求发生了错误,但在事件处理时,咱们没法从代码上获知失败的任何缘由。好比,FireFox在失败时将responseText置空并返回一个0值做为状态,这当中并不包含任何错误的具体状况。缓存

withCredentials

标准的CORS请求不对cookies作任何事情,既不发送也不改变。若是但愿改变这一状况,就须要将withCredentials设置为true安全

 

 

另外,服务端在处理这一请求时,也须要将Access-Control-Allow-Credentials设置为true。这一点咱们稍后来讲。

withCredentials属性使得请求包含了远程域的全部cookies,但值得注意的是,这些cookies仍旧遵照“同域”的准则,所以从代码上你并不能从document.cookies或者回应HTTP头当中进行读取。

发送请求

请求经过一个简单的send()方法进行发送,若是请求当中须要包含任何内容,也只须要将其做为一个参数传递给send()便可。一旦服务端配置OK,那么你将只须要处理后续的onload事件,这正像咱们平时所熟悉的XHR同样。

来看一段完整的小代码:

 

 

服务端

一个CORS请求可能包含多个HTTP头,甚至有多个请求实际发送,这对于客户端的开发者来讲一般是透明的。由于浏览器已经负责实现了CORS最关键的部分;可是服务端的后台脚本则须要咱们本身进行处理,所以咱们还须要了解到服务端到底从浏览器那里收到了怎样的内容。

先来看看流程图吧。

CORS流程图

CORS分类

CORS能够分红两种:

  • 简单请求
  • 复杂请求

一个简单的请求大体以下:

  • HTTP方法是下列之一
    • HEAD
    • GET
    • POST
  • HTTP头包含
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type,但仅能是下列之一
      • application/x-www-form-urlencoded
      • multipart/form-data
      • text/plain

任何一个不知足上述要求的请求,即被认为是复杂请求。一个复杂请求不只有包含通讯内容的请求,同时也包含预请求(preflight request)。

简单请求

为了搞清楚复杂请求与简单请求有何区别,咱们首先来看看简单请求是怎样处理的。

JavaScript:

 

 

HTTP请求:

 

 

简单请求的发送从代码上来看和普通的XHR没太大区别,可是HTTP头当中要求老是包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口。不过这一项实际上由浏览器代为发送,并非开发者代码能够触及到的。

HTTP回应:

 

 

在回应中,COR相关的项目全都是以“Access-Control-”做为前缀的,其意义分列以下:

  • Access-Control-Allow-Origin(必含)- 不可省略,不然请求按失败处理。该项控制数据的可见范围,若是但愿数据对任何人均可见,能够填写“*”。
  • Access-Control-Allow-Credentials(可选) – 该项标志着请求当中是否包含cookies信息,只有一个可选值:true(必为小写)。若是不包含cookies,请略去该项,而不是填写false。这一项与XmlHttpRequest2对象当中的withCredentials属性应保持一致,即withCredentialstrue时该项也为truewithCredentialsfalse时,省略该项不写。反之则致使请求失败。
  • Access-Control-Expose-Headers(可选) – 该项肯定XmlHttpRequest2对象当中getResponseHeader()方法所能得到的额外信息。一般状况下,getResponseHeader()方法只能得到以下的信息:
    • Cache-Control
    • Content-Language
    • Content-Type
    • Expires
    • Last-Modified
    • Pragma

    当你须要访问额外的信息时,就须要在这一项当中填写并以逗号进行分隔。不过目前浏览器对这一项的实现仍然有一些问题,具体请见文尾的BUG一节。

复杂请求

若是仅仅是简单请求,那么即使不用CORS也没有什么大不了,但CORS的复杂请求就令CORS显得更加有用了。简单来讲,任何不知足上述简单请求要求的请求,都属于复杂请求。好比说你须要发送PUTDELETE等HTTP动做,或者发送Content-Type: application/json的内容。

复杂请求表面上看起来和简单请求使用上差很少,但实际上浏览器发送了不止一个请求。其中最早发送的是一种“预请求”,此时做为服务端,也须要返回“预回应”做为响应。预请求其实是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。

JavaScript:

 

 

预请求:

 

 

预请求以OPTIONS形式发送,当中一样包含域,而且还包含了两项CORS特有的内容:

  • Access-Control-Request-Method – 该项内容是实际请求的种类,能够是GET、POST之类的简单请求,也能够是PUTDELETE等等。
  • Access-Control-Request-Headers – 该项是一个以逗号分隔的列表,当中是复杂请求所使用的头部。

显而易见,这个预请求实际上就是在为以后的实际请求发送一个权限请求,在预回应返回的内容当中,服务端应当对这两项进行回复,以让浏览器肯定请求是否可以成功完成。例如,刚才的预请求可能得到服务端以下的回应:

 

 

来看看预回应当中可能的项目:

  • Access-Control-Allow-Origin(必含) – 和简单请求同样的,必须包含一个域。
  • Access-Control-Allow-Methods(必含) – 这是对预请求当中Access-Control-Request-Method的回复,这一回复将是一个以逗号分隔的列表。尽管客户端或许只请求某一方法,但服务端仍然能够返回全部容许的方法,以便客户端将其缓存。
  • Access-Control-Allow-Headers(当预请求中包含Access-Control-Request-Headers时必须包含) – 这是对预请求当中Access-Control-Request-Headers的回复,和上面同样是以逗号分隔的列表,能够返回全部支持的头部。
  • Access-Control-Allow-Credentials(可选) – 和简单请求当中做用相同。
  • Access-Control-Max-Age(可选) – 以秒为单位的缓存时间。预请求的的发送并不是免费午饭,容许时应当尽量缓存。

一旦预回应如期而至,所请求的权限也都已知足,则实际请求开始发送。

实际请求:

 

 

实际回应:

 

 

若是预请求所要求的权限服务端不容许,那么服务端能够直接返回一个普通的HTTP回应,好比:

 

 

这样的返回由于不符合客户端的需求,于是客户端会直接将请求以失败计,虽然不是很美气,不过正符合咱们的实际。此时若是客户端的onerror事件有监听函数,那么将会触发,而浏览器的console窗口也会输出:

 

 

不过很惋惜,浏览器并不会给出详细的错误状况,仅仅是告知咱们出错而已。

安全问题

跨域请求始终是网页安全中一个比较头疼的问题,CORS提供了一种跨域请求方案,但没有为安全访问提供足够的保障机制,若是你须要信息的绝对安全,不要依赖CORS当中的权限制度,应当使用更多其它的措施来保障,好比OAuth2。

已知问题

CORS是W3C中一项较“新”的方案,以致于各大网页解析引擎尚未对其进行完美的实现。下面是截至2011年11月13日时的已知问题:

    • getAllResponseHeaders()方法没法获取Access-Control-Expose-Headers当中要求的信息。在Chrome/Safari当中,仅仅只有简单的头部可以读取,其余没法获取;在FireFox当中,没法得到任何信息。(FireFox Bugzilla/Webkit Bugzilla
    • 在Safari当中,使用GETPOST方法的复杂请求发送时没有发送预请求的环节。
    • onerror触发时statusText获取不到任何内容。
    • Opera截至11.60仍旧不支持CORS,但在12当中会支持(Opera Core Concerns – CORS goes mainline)。
相关文章
相关标签/搜索