最近在做的一個應用遷移項目,第一階段要将工作台頁面内的a應用所有連結,遷到b應用中,對應連結的域名也變為b應用的域名,是以産生了跨域問題。通常這種跨域問題可以采用jsonp請求解決,但是jsonp在本項目中會有幾個難點:一是前端改動較大,二是jsonp隻支援get請求,另外就是比較難執行灰階(jsonp和非jsonp的請求顯然是不好作為灰階的a/b項的)。
除了jsonp,還有一種比較新的技術就是cors了。cors是一種允許目前域(domain)的資源(比如html/js/web service)被其他域(domain)的腳本請求通路的機制。使用cors,可以通過普通的xmlhttprequest發起請求和獲得資料,并且支援各種類型請求。缺點就是因為屬于比較新的技術,是以浏覽器相容性有一定局限。
<b>由于這次改造的工作台隻支援火狐和谷歌浏覽器,是以cors的相容性也就不是問題。如果是其他pc端的系統,在選擇cors是就要考慮相容性的影響了。</b>
下面就是具體怎麼實作cors。首先,對于支援cors的浏覽器,發送請求時,如果我們觀察請求頭會發現帶有"origin:http://example.taobao.com" ,該辨別用來說明本次請求來自的域。要實作cors,隻要在服務端在響應頭部加上辨別“access-control-allow-origin:http://example.taobao.com”,這樣浏覽器發現access-control-allow-origin和請求來自的域一緻,就允許跨域通路資源example.taobao.com的資源了。對應服務端代碼:
rundata.getresponse().setheader("access-control-allow-origin", "http://example.taobao.com");
如果access-control-allow-origin設定為"*",那麼任何域的請求都可以通過cors通路服務資源,但是這樣顯然不夠安全。我們可以在服務端設定一個域白名單,收到請求時先取請求頭中origin辨別的域,判斷域是否在白名單中,如果在的話,再将該域設定到響應的access-control-allow-origin中。這樣就實作指定某些域cors請求服務資源。
服務端傳回的響應除了加允許的請求域辨別,同時對允許的請求方法也要辨別,我們一般固定寫為get和post就可以: rundata.getresponse().setheader("access-control-allow-methods","get,post");
同時需要設定:
rundata.getresponse().setheader("access-control-allow-headers", rundata.getrequest().getheader("access-control-request-headers"));//意思是請求的headers是什麼我就傳回允許的headers什麼
需要注意的時,按照上面方法做,此時的cors請求可以通路服務,但是請求不會帶cookie資訊。如果需要cookie資訊的話,需要請求js和服務端做一個通信驗證:
js請求: var invocation = new xmlhttprequest(); invocation.withcredentials = true;
服務端:rundata.getresponse().setheader("access-control-allow-credentials", "true");
該驗證ie并不支援,即使是高版本支援cors的ie10,11,是以這也是個坑。需要相容ie的應用基本不用考慮cors了。
最後,需要說的是cors的一個重要特性,處理不好也是一個坑,就是探測請求。當跨域請求時,如果請求較為“複雜”(定義複雜的标準在最後會附上),則浏覽器會先發一個options類型請求,域名和uri不變,但是不會帶任何參數和cookie資訊。這個請求的目的就是獲得傳回的響應後驗證響應頭,判斷服務端是否支援該域的cors通路,支援的話才會發送真正的請求。是以叫探測請求。那麼我們在服務端對這種options類型方法請求,顯然要特殊處理,首先按照前面介紹設定響應頭相關内容以支援cors,其次要讓請求跳過執行業務方法直接傳回結束,因為這隻是個探尋請求。這塊我們可以做一個公用子產品,或攔截器來統一處理:
if("options".equals(rundata.getrequest().getmethod())){
//設定access-control-allow相關頭
rundata.getresponse().getwriter().flush();
return true;
}
如果不希望每次請求都多一次options請求,可以設定傳回響應頭資訊:
rundata.getresponse().setheader("access-control-max-age","60"); //緩存options結果60秒
最後附上cors“複雜”請求标準(滿足一條即是):
1、http請求方法不是head、get、post其中之一
2、http請求頭沒有包含下列所有:accept、accept-language、content-language、last-event-id、content-type
3、content-type不是下列類型:application/x-www-form-urlencoded、multipart/form-data、text/plain