在构建Public APIs的过程当中,首先要解决的第一个问题就是跨域请求的问题。html
网络应用安全模型中很重要的一个概念是“同源准则”(same-origin policy)。该准则要求一个网站(由协议+主机名+端口号三者肯定)的脚本(Script)、XMLHttpRequest和Websocket无权去访问另外一个网站的内容。在未正确设置的状况下,跨域访问会提示以下错误:No 'Access-Control-Allow-Origin' header is present on the requested resource. 这项限制对于跨域的Ajax请求带来了不少不便。html5
典型的对于跨域请求的解决方案以下:jquery
本文重点讲述的则是其中Cross-Origin Resource Sharing (CORS)的原理和在rails下的配置方式
ajax
CORS的基本原理是经过设置HTTP请求和返回中header,告知浏览器该请求是合法的。这涉及到服务器端和浏览器端双方的设置:请求的发起(Http Request Header)和服务器对请求正确的响应(Http response header)。chrome
CORS兼容如下浏览器:json
原生Javascript能够经过XMLHttpRequest Object或XDomainRequest发起请求,详细的方式能够参见这篇文章:http://www.html5rocks.com/en/tutorials/cors/api
JQuery的$.ajax()能够用来发起XHR或者CORS请求。然而该方法不支持IE下的XDomainRequest,须要使用JQuery的插件来实现IE下的兼容性(http://bugs.jquery.com/ticket/8283)跨域
$.ajax({ // The 'type' property sets the HTTP method. // A value of 'PUT' or 'DELETE' will trigger a preflight request. type: 'GET', // The URL to make the request to. url: 'http://updates.html5rocks.com', // The 'contentType' property sets the 'Content-Type' header. // The JQuery default for this property is // 'application/x-www-form-urlencoded; charset=UTF-8', which does not trigger // a preflight. If you set this value to anything other than // application/x-www-form-urlencoded, multipart/form-data, or text/plain, // you will trigger a preflight request. contentType: 'text/plain', xhrFields: { // The 'xhrFields' property sets additional fields on the XMLHttpRequest. // This can be used to set the 'withCredentials' property. // Set the value to 'true' if you'd like to pass cookies to the server. // If this is enabled, your server must respond with the header // 'Access-Control-Allow-Credentials: true'. withCredentials: false }, headers: { // Set any custom headers here. // If you set any non-simple headers, your server must include these // headers in the 'Access-Control-Allow-Headers' response header. }, success: function() { // Here's where you handle a successful response. }, error: function() { // Here's where you handle an error response. // Note that if the error was due to a CORS issue, // this function will still fire, but there won't be any additional // information about the error. } });
根据请求内容的不一样,浏览器会须要添加对应的Header或者发起额外的请求。其中的细节都由浏览器负责处理,对于用户来说是透明的。咱们只须要了解如何针对差别的请求作出适当的响应便可。
咱们将CORS请求分红如下两种类型:
一、简单请求
二、不是那么简单的请求浏览器
其中简单请求要求:
请求类型必须是GET,POST,HEAD三者中的一种
请求头(Header)中仅能够包含:缓存
不知足上述条件的全部请求,例如PUT,DELETE或者是Content Type是application/json,均为“不是那么简单的请求”。针对这种请求,浏览器会在真实请求前,额外发起一次类型为OPTIONS的请求(Preflight request),只有服务器正确响应了OPTIONS请求后,浏览器才会发起该请求。(参见下图)
下文将针对b.com向a.com发起跨域请求说明服务器如何正确响应这两种类型的请求。
浏览器在发出请求前为请求添加Origin来标明请求的来源,用户不可更改此内容。但Header中是否有Origin并不能做为判断是不是CORS请求的标准,由于不一样浏览器对于此内容的处理方式并不彻底一致,同源请求中也有可能出现Origin。
下面是一个b.com向a.com发起的一次GET请求。
GET /cors HTTP/1.1 Origin: http://b.com Host: a.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
正确响应的返回以下,均由Access-Control-*开头:
Access-Control-Allow-Origin: http://b.com Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: FooBar Content-Type: text/html; charset=utf-8
Access-Control-Allow-origin: 此处是Server赞成跨域访问的域名列表。若是容许任意网站请求资源,此处能够写为'*'
Access-Control-Expose-Headers: 能够设置返回的Header以传递数据。简单请求中容许使用的Header包括:Cache-Control,Content-Language,Content-Type,Expires,Last-Modified,Pragma。
若是但愿使用PUT,DELETE等RESTful等超出了简单请求的范围的请求,浏览器则会在发起真实请求前先向服务器发起一次称做Preflight的OPTIONS的请求,以确保服务器接受该类型请求。其后才会发起真实要求的请求。请求的发起与简单请求并没有差别,而服务器端则要针对Preflight Request作额外的响应。
下面是一次典型的Preflight请求:
OPTIONS /cors HTTP/1.1 Origin: http://b.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: X-Custom-Header Host: a.com Accept-Language: en-US Connection: keep-alive User-Agent: Mozilla/5.0...
Access-Control-Request-Method表明真实请求的类型。Access-Control-Request-Headers则表明真实请求的请求头key内容。服务器仅在验证了这两项内容的合法性以后才会赞成浏览器发起真实的请求。
Access-Control-Allow-Origin: http://b.com Access-Control-Allow-Methods: GET, POST, PUT Access-Control-Allow-Headers: X-Custom-Header Content-Type: text/html; charset=utf-8
此处并未列举的一项返回头是Access-Control-Max-Age。由于每次请求均要发起一次额外的OPTIONS请求是很是低效的,所以能够为浏览器保存该返回头设置一个缓存的时间,单位为秒。在缓存过时之前,浏览器无需再次验证同一类型的请求是否合法。
真实请求的内容则和简单请求的内容彻底一致,此处再也不赘述。
下图很是详细的再次描述了服务器对于不一样类型的请求如何作出正确的响应。
首先要确保在Routes.rb中加上对于OPTIONS请求的正确响应。
OPTIONS请求会发至真实请求的同一位置。若是未正确设置route,则会出现404没法找到请求地址的错误。
响应该请求的Controller的action方法能够设置为空,由于该请求的关键仅是正确返回请求头。
例如:真实请求/api/trips PUT,OPTIONS请求将发送至/api/trips OPTIONS。
match '/trips', to: 'trips#index', via: [:options]
或者可使用:
match '*all' => 'application#cor', :constraints => {:method => 'OPTIONS'}
确保了OPTIONS请求能够正确被响应以后,在applicationController.rb中以下配置:
before_filter :cors_preflight_check after_filter :cors_set_access_control_headers def cors_set_access_control_headers headers['Access-Control-Allow-Origin'] = '*' headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS' headers['Access-Control-Max-Age'] = '1728000' end def cors_preflight_check if request.method == 'OPTIONS' headers['Access-Control-Allow-Origin'] = '*' headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS' headers['Access-Control-Request-Method'] = '*' headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization' headers['Access-Control-Max-Age'] = '1728000' render :text => '', :content_type => 'text/plain' end end
对于简单请求,由cors_set_access_control_headers作出正确的响应。对于不是那么简单的请求,cors_preflight_check则会发现若请求是OPTIONS的时候,在实际执行cors_set_access_control_headers以前,拦截下该请求并返回text/plain的内容和正确的请求头。
CORS请求做为构建Public API中很重要的一环,理解其大体的工做原理仍是很是有意义的。不过在Chrome中,时常会出现provision header shown这样奇怪的错误,而这个错误出现的缘由说法不一,基本上能够理解为跨域访问过程当中若是请求出现问题chrome并无办法很好的了解错误缘由,没法准确的给出错误状态。另外,CORS调试也是一个问题,基于非浏览器的POSTMAN调试,有时不可以准确的反映出请求在浏览器下的真实工做状态,不知道如何才能更有效果的测试和调试CORS。
有兴趣的话,还能够进一步经过https://dvcs.w3.org/hg/cors/raw-file/tip/Overview.html了解W3C的CORS协议内容。http://arunranga.com/examples/access-control/
Reference:
http://en.wikipedia.org/wiki/Same_origin_policy
http://www.html5rocks.com/en/tutorials/cors/
http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/
http://www.tsheffler.com/blog/?p=428
http://blog.rudylee.com/2013/10/29/rails-4-cors/
http://stackoverflow.com/questions/17858178/allow-anything-through-cors-policy