from: https://segmentfault.com/a/1190000000709909
理由:在操作層面詳細的講解了跨域的操作。尤其是對于option請求的詳解。收藏。
在建構Public APIs的過程中,首先要解決的第一個問題就是跨域請求的問題。
網絡應用安全模型中很重要的一個概念是“同源準則”(same-origin policy)。該準則要求一個網站(由協定+主機名+端口号三者确定)的腳本(Script)、XMLHttpRequest和Websocket無權去通路另一個網站的内容。在未正确設定的情況下,跨域通路會提示如下錯誤:No 'Access-Control-Allow-Origin' header is present on the requested resource. 這項限制對于跨域的Ajax請求帶來了很多不便。
典型的對于跨域請求的解決方案如下:
document.domain property
Cross-Origin Resource Sharing (CORS)
Cross-document messaging
JSONP
本文重點講述的則是其中Cross-Origin Resource Sharing (CORS)的原理和在rails下的配置方式
CORS的基本原理是通過設定HTTP請求和傳回中header,告知浏覽器該請求是合法的。這涉及到伺服器端和浏覽器端雙方的設定:請求的發起(Http Request Header)和伺服器對請求正确的響應(Http response header)。
CORS相容以下浏覽器:
Internet Explorer 8+
Firefox 3.5+
Safari 4+
Chrome
根據請求内容的不同,浏覽器會需要添加對應的Header或者發起額外的請求。其中的細節都由浏覽器負責處理,對于使用者來講是透明的。我們隻需要了解如何針對差異的請求做出适當的響應即可。
我們将CORS請求分成以下兩種類型:
1、簡單請求
2、不是那麼簡單的請求
其中簡單請求要求:
請求類型必須是GET,POST,HEAD三者中的一種
請求頭(Header)中僅可以包含:
Accept
Accept Language
Content Language
Last Event ID
Content Type:僅接受application/x-www-form-urlencoded,multipart/form-data,text/plain
不滿足上述條件的所有請求,例如PUT,DELETE或者是Content Type是application/json,均為“不是那麼簡單的請求”。針對這種請求,浏覽器會在真實請求前,額外發起一次類型為OPTIONS的請求(Preflight request),隻有伺服器正确響應了OPTIONS請求後,浏覽器才會發起該請求。(參見下圖)
下文将針對b.com向a.com發起跨域請求說明伺服器如何正确響應這兩種類型的請求。
浏覽器在送出請求前為請求添加Origin來标明請求的來源,使用者不可更改此内容。但Header中是否有Origin并不能作為判斷是否是CORS請求的标準,因為不同浏覽器對于此内容的處理方式并不完全一緻,同源請求中也有可能出現Origin。
下面是一個b.com向a.com發起的一次GET請求。
正确響應的傳回如下,均由Access-Control-*開頭:
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請求:
Access-Control-Request-Method代表真實請求的類型。Access-Control-Request-Headers則代表真實請求的請求頭key内容。伺服器僅在驗證了這兩項内容的合法性之後才會同意浏覽器發起真實的請求。
此處并未列舉的一項傳回頭是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中如下配置:
對于簡單請求,由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。
Reference:
<a href="http://en.wikipedia.org/wiki/Same_origin_policy" target="_blank">http://en.wikipedia.org/wiki/Same_origin_policy</a>
<a href="http://www.html5rocks.com/en/tutorials/cors/" target="_blank">http://www.html5rocks.com/en/tutorials/cors/</a>
<a href="http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/" target="_blank">http://www.nczonline.net/blog/2010/05/25/cross-domain-ajax-with-cross-origin-resource-sharing/</a>
<a href="http://www.tsheffler.com/blog/?p=428" target="_blank">http://www.tsheffler.com/blog/?p=428</a>
<a href="http://blog.rudylee.com/2013/10/29/rails-4-cors/" target="_blank">http://blog.rudylee.com/2013/10/29/rails-4-cors/</a>
<a href="http://stackoverflow.com/questions/17858178/allow-anything-through-cors-policy" target="_blank">http://stackoverflow.com/questions/17858178/allow-anything-through-cors-policy</a>
<a href="https://segmentfault.com/a/1190000000709909">2014年10月06日釋出</a>
<a>更多</a>
唯有不斷學習方能改變!
-- <b>Ryan Miao</b>