你的位置:首页 > Java教程

[Java教程]浅析Cross Origin Resource Sharing


在前面我们已经简单介绍了如何利用

W3C的Cross Origin Resource Sharing,它的最核心的地方在于:通过一些Http Header信息,可以让浏览器跟服务器之间更加充分的‘认识’对方,继而决定是否支持这个跨域的资源请求。

这里简单的介绍一个一些常用的用于实现跨域资源访问的一些方法。

XDomainRequest

Microsoft公司在IE8+的浏览器中新添了XDomainRequest Object来支持跨域资源的共享策略,这个新生的对象跟

 - 使用XDomainRequest对象发送请求的时候,不能在请求当中发送Cookie信息,同时,在响应当中也不会返回Cookie信息

 - 除了Content-Type,不可以对其他的HTTP Request Header进行任何的操作 (例如XDomainRequest.contentType = 'application/x-www-form-urlencoded')

 - 不可以获取response的Header信息

 - 只能够支持GET跟POST方法

使用XDomainRequest进行跨域操作,跟

 - 创建一个XDomainRequest对象

 - 初始化请求: xDomainRequest.open(method, url); (注意:与XMLHttpRequest不同的是,该方法只有两个参数,只能够支持异步通信)

 - 发送请求: xDomainRequest.send(data/null);

使用XDomainRequest进行跨域资源访问的时候,我们可以通过这个创建的XDomainRequest所触发的一些event事件,捕获事件的处理结果:

 - load : 请求完成,我们可以通过responseText属性获取返回的结果; (注意:XDR的status跟statusText属性不可用)

 - error : 请求出现错误,但是遗憾的是除了触发这个error事件之外,没有其他任何的错误信息返回; (推荐:务必绑定此方法,因为导致XDomainRequest失败的原因各种各样,如果不绑定此事件,我们无法知道请求成功与否,failed siliently)

 - timeout : 我们可以对XDomainRequest对象设置一个timeout阀值(XDomainRequest.timeout=<timeout ms>),当请求超时时,会触发此事件

另外我们可以使用abort()方法强行终止请求。

下面是这个方法的一个具体使用代码片段:

function createCORSRequest() {  var xdr = new XDomainRequest();    xdr.onload = function () {    console.debug('Response : ' + xdr.responseText);  };    xdr.onerror = function () {    console.error('Failed to retrieve data!');  };    xdr.timeout = 5000;  xdr.ontimeout = function () {    console.debug('Request took too long!');  };    return xdr;}

使用XDomainRequest对象发送HTTP请求会在Header头部信息中添加Origin属性,例如Origin: http://www.cnblog.andycbluo,用以告诉服务器端这个跨域请求的发起站点信息,服务器端通过这个信息可以决定是否支持次跨域请求,如果通过,服务器就会返回一个Access-Control-Allow-Origin:http://www.cnblog.andycbluo或者*的头部信息最为响应。

注意:IE11当中已经抹掉了这个XDomainRequest对象,改用与其它浏览使用标准的

Firefox,Safari,Chrome等浏览器则是从一开始便是选择了通过改进传统的

使用

与传统的

 - 不可以通过setRequestHeader来设置请求头部信息

 - 请求与响应不带任何的Cookie信息

 - 响应的getALLResponseHeaders方法永远返回empty

与XDomainRequest相比较,

下面我们来看看具体的操作例子:

function createCORSRequest() {  var xhr = new = function () {    console.log('Response : ' + xdr.responseText);  };    xhr.onerror = function () {    console.log('Failed to retrieve data!');  };  return xhr;}

<!DOCTYPE html><html>  <head>    <title>Cross Origin Resource Sharing Demo</title>    <script type='text/javascript' src='cors.js'></script>  </head>  <body>    <script type='text/javascript'>      window.onload = function () {        var xhr = createCORSRequest();        xhr.open('GET', 'https://www.hsbc.com.hk/zh-cn/index.html', true);        xhr.send(null);      };    </script>  </body></html>

我们可以通过Fiddler很清楚的看到发送的HTTP Request Header信息中包含了请求的Origin站点信息

 1 GET https://www.hsbc.com.hk/zh-cn/index.html HTTP/1.1 2 Host: www.hsbc.com.hk 3 Connection: keep-alive 4 Cache-Control: max-age=0 5 User-Agent: Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727) 6 Origin: http://www.test.qualityassurance.ebanking.hsbc.com.hk 7 Accept: */* 8 Referer: http://www.test.qualityassurance.ebanking.hsbc.com.hk/mobile/cors.html 9 Accept-Encoding: gzip, deflate, sdch10 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.411 Range: bytes=13416-1341612 If-Range: "25d9d-17a36-52a75a284a570"

很遗憾的是,测试的对方站点不支持该跨域请求,在响应头部中我们并没有看到服务器返回任何Access-Control-Allow-Origin信息,所以这个跨域请求跪了

 1 HTTP/1.1 206 Partial Content 2 Date: Fri, 29 Jan 2016 11:17:42 GMT 3 Last-Modified: Fri, 29 Jan 2016 09:26:49 GMT 4 ETag: "25d9d-17a36-52a75a284a570" 5 Accept-Ranges: bytes 6 Cache-Control: max-age=43200 7 Expires: Fri, 29 Jan 2016 23:17:42 GMT 8 Vary: Accept-Encoding,User-Agent 9 S: hkp1v-pwsgw_tk02-hkp2vl003010 Content-Range: bytes 13416-13416/2022811 Content-Length: 012 Keep-Alive: timeout=5, max=10013 Connection: Keep-Alive14 Content-Type: text/html15 Set-Cookie: HKWGTK=175296522.17781.0000; path=/

Console输出:

Credentialed Request

默认的情况之下,利用

<!DOCTYPE html><html>  <head>    <title>Cross Origin Resource Sharing Demo</title>    <script type='text/javascript' src='cors.js'></script>  </head>  <body>    <script type='text/javascript'>      window.onload = function () {        var invocation = new = true;        invocation.onload = function () {          console.log(invocation.responseText);        };        invocation.onerror = function () {          console.log('Request failed : ' + invocation.status);        };        invocation.open('GET', 'https://infrequently.org/2006/03/comet-low-latency-data-for-the-browser/', true);        invocation.send(null);      };    </script>  </body></html>

注意: 当我们使用了Cedentialed Request进行跨域资源请求的时候,服务器端必须返回Access-Control-Allow-Credentials:true跟Access-Control-Allow-Origin:<your requested origin>,否则请求失败。

Preflighted Request (预请求)

HTTP 1.1规范当中标明,如果请求不是以GET/POST/Head的方式,或者是内容类型(content-type)不是常规的普通文本格式的时候,浏览器会通过HTTP 1.1规范当中的OPTIONS方法向服务器发送一个Preflighted Request(预请求)。

浏览器使用OPTIONS方法并且发送以下的Headers信息跟对方站点的服务器:

 - Origin : 请求源站点信息

 - Access-Control-Request-Method : 该跨域请求将要使用的方法

 - Access-Control-Request-Headers : 该跨域请求将会使用到的customized headers,这个信息并非必须的,当有多个header值时,用逗号,隔开

如果服务器支持该跨域请求,就会在response header中返回以下的信息:

 - Access-Control-Allow-Origin

 - Access-Control-Allow-Methods : 例如GET, POST

 - Access-Control-Allow-Headers : 例如Username, Password

 - Access-Control-Max-Age : 该预请求将会被Cache的时间(seconds)

下面的例子就会触发一个Preflighted Request:

<!DOCTYPE html><html>  <head>    <title>Cross Origin Resource Sharing Demo</title>    <script type='text/javascript' src='cors.js'></script>  </head>  <body>    <script type='text/javascript'>      window.onload = function () {        var invocation = new = true;        invocation.onload = function () {          console.log(invocation.responseText);        };        invocation.onerror = function () {          console.log('Request failed : ' + invocation.status);        };        invocation.setRequestHeader('content-type', 'application/');        invocation.open('POST', 'https://infrequently.org/2006/03/comet-low-latency-data-for-the-browser/', true);        invocation.send(null);      };    </script>  </body></html>

Cross Browsers - CORS

综上所述,由于不同的浏览器对于Cross Origin Resource Sharing的支持不同,我们可以开发一个跨浏览器的具体实现,以方便我们进行跨域资源请求操作,具体可见以下:

/**************************************************************************************************** * This method return the normonized cross origin resource sharing request object * ****************************************************************************************************/function createCORSRequest (method, url) {  var xhr = createif ('withCredentials' in xhr) {    // standar method    xhr.open(method, url, true);  } else if (typeof XDomainRequest !== undefined) {    // IE8+ use XDomainRequest    xhr = new XDomainRequest();    xhr.open(method, url);  } else {    throw new Error('Cross-Origin-Resource-Sharing is not suported for this type of browser!');  }    return xhr;};/**************************************************************************************************** * This method return the normonized */function createvar if (typeof  undefined) {    // standard method    new else if (typeof ActiveXObject !== undefined) {    // IE version : ActiveXObject to create the     var vers = ['MS2];    for (var i = 0; i < vers.length; i++) {      try {        = new ActiveXObject(vers[i]);      } catch (e) {        continue;      }    }  } else {    // not supported browser    throw new Error(');  }    return 

CORS之外的一些跨域资源访问的方法

除了上面我们所提到的W3C跨域资源共享的规范之外,我们还可以通过以下一些常用的方法来进行跨域资源的访问和通信。

  • Image Ping

Image Ping是我们常用的一种用于跨域资源访问的方法之一,它的实现依靠的是DOM的<img>标签可以任意的访问任何站点的资源而不受限制的特性,使用Image Ping有以下的有点:

 - 适用性广泛,所有的browser都支持这种方式

 - 通过设置<img>标签的src属性,可以任意访问其他站点的资源不受限制

 - 可以通过在src属性后面添加参数,向服务器端发送额外的data

 - 我们可以通过image.onload跟image.onerror来监听请求是否成功

Image Ping的使用也具有以下的局限性:

 - 只可以通过HTTP GET的方式发送HTTP请求,向服务器发送的数据的总长度有限

 - 数据传输的单向性,不可以对服务器返回的响应进行任何操作

通过以上的描述,Image Ping最常被用于网站站点数据的收集,特别是对某一些广告信息的收集,比如银行推出某一个理财产品,当由用户点击到这个理财广告的时候,客户端就把当前所点击的广告信息收集,通过Image Ping的方式发送给服务器后台,后台通过数据分析,就可以分析出哪一个系列的理财产品受众最广,哪一个系列的产品适用某一类人群等等。

Image Ping的实现方式可以参考以下例子:

var img = new Image();img.onload = function () {  console.debug('request successfully.');};img.onerror = function () {  console.debug('request failed');}img.src = 'http://www.somesite.com/test?username=AndyLuo&ad=wealth';

  • JSONP

另外一个最为常用的跨域资源共享的方法便是JSONP(JSON with Padding,使用这种方法,服务器端返回的数据是以JSON的格式,但是包含在一个Javascript的回调函数之中,例如callback({JSON_Data}))。该方法利用的是HTML的<script> DOM元素可以跨域加载资源的特性,通过指定script的src属性,实现资源的跨域共享。

使用该方法的好处:

 - 可以实现客户端/服务器双向数据通信

 - 所有的浏览器都支持<script>标签的跨域资源共享

当然,使用这种方法也有一些弊端:

 - 服务器返回的是一个回调可执行函数,有可能存在恶意代码的风险 (因此,当我们使用这种方法进行跨域资源请求时我们必须保证第三方是受信任的)

 - 我们没有一种有效的机制得知请求的结果,虽然HTML5规范中赋予了onerror属性,但是到目前为止,没有任何浏览器实现了这个接口 (我们可以通过设置timeout值来进行处理,当时间超过我们设置的阀值时,我们可以把它作为一个失败请求来处理)

JSONP的实现可以参考以下的例子:

var scriptElem = document.createElement('script');scriptElem.src = 'http://www.somesite.com/test?callback=handleResponse' // 我们通常把回调函数以请求parameter的方式发送给服务器,服务器接受后以该回调函数返回数据, handleResponse({JSON_Data});document.body.appendChild(scriptElem, document.body.firstChild);