上接《再談風騷的跨源/域方案(昔日篇)》,本篇聊聊現代標準(HTML5以後)的跨源方案。
基礎概念都在昔日篇中,初學者請務必先看完昔日篇。
配套的演示案例傳送門。
本人我的能力有限,歡迎批評指正。javascript
該方案使用了 HTML5 新的 window.postMessage
接口,該方法是專門爲不一樣源頁面通訊設計的,是一個經典的「訂閱-通知」模型。前端
該方案原理與昔日篇的「子域代理」很類似,都是主頁面用 iframe 內非同源子頁面做爲代理去跟服務端交互獲取數據。不一樣之處在於,「子域代理」須要經過修改 document.domain
使主頁面獲取子頁面 document 操做權限,而 window.postMessage
已經原生提供了主頁面與子頁面通訊的辦法,故僅須要主頁面經過 window.postMessage
向子頁面下命令,子頁面請求完成後再以此通知主頁面便可實現跨源通訊,換句話說子頁面變成了一個相似轉發服務的存在。
不須要修改 document.domain
也意味着擺脫了「子域代理」嚴格的域限制,能夠更加自由的應用在第三方 API 上。 window.postMessage
是少有的不受同源限制的瀏覽器 API,準確來講是沒有調用權限的限制而已,它對發送和接收的目標仍是有嚴格限制的,這也是它安全性的體現。舉個例子:java
// 假設在 iframe 內頁面進行訂閱。 window.addEventListener('message', event => { // 驗證發送者,發送者不符合是能夠不理會的。 if (event.origin !== 'http://demo.com') return // 這就是發送過來的信息。 const data = event.data // 這是發送者的 window 實例,能夠調用上面的 postMessage 回傳信息。 const source = event.source })
// 主頁面通知。 // 第二個參數是接收者的源,須要源徹底匹配的頁面纔會接收到信息。(「源」的定義見昔日篇) // 設置爲 * 能夠實現廣播,不過通常不推薦。 iframe.contentWindow.postMessage('hello there!', 'http://demo.com')
iframe.contentWindow.postMessage
發送請求給代理頁;event.source.postMessage
傳遞給主頁面。前端git
服務端github
共享 iframe 的設計思路請參考昔日篇的「子域代理」。
前端「統一事件處理器」的設計思路:web
function initMessageListener() { // 保存回調對象的對象。 const cbStore = {} // 設置監聽,只需一個。 window.addEventListener('message', function (event) { // 驗證發送域。 if (event.origin !== targetOrigin) { return } // ... try { // 運行失敗分支。 if (...) { cbStore[msgId].reject(new Error(...)) return } // 運行成功分支。 cbStore[msgId].resolve(...) } finally { // 執行清理。 delete cbStore[msgId] } }) // 這裏造成了一個閉包,只能用特定方法操做 cbStore。 return { // 設置回調對象的方法。 set: function (msgId, resolve, reject) { // 回調對象包含成功和失敗兩個分支函數。 cbStore[msgId] = { resolve, reject } }, // 刪除回調對象的方法。 del: function (msgId) { delete cbStore[msgId] } } } // 初始化,每次請求都調用其 set 方法設置回調對象。 const messageListener = initMessageListener()
配合上面的「統一事件處理器」,msgId 其實不必傳遞到服務端,在代理頁處理便可:json
window.addEventListener('message', event => { // 驗證發送域。 if (event.origin !== targetOrigin) { return } // 這是主頁面 postMessage 的數據。 // 其中 msgId 與「統一事件處理器」有關,其餘參數與 Ajax 有關,按實際須要傳遞便可。 const { msgId, method, url, data } = event.data // 發送 Ajax。 xhr(...).then(res => { // 將 msgId 加入回傳數據,其他保留原樣。 res.response.data = { ...res.response.data, msgId } // 回傳給主頁面。 event.source.postMessage(res, targetOrigin) }) })
具體代碼請參考演示案例 PostMessage 部分源碼。canvas
優勢segmentfault
缺點api
CORS 全稱 Cross-origin resource sharing ,是 W3C 組織制訂的標準跨源方案(傳送門),也能夠說是跨源的官方終極解決方案,它讓現代的 web 開發方便很多。
簡單來講 CORS 是一套服務端與瀏覽器的協商機制,經過報文頭實現,瀏覽器告知服務端來源(origin)和但願容許的方法,服務端返回「白名單」(也是一組報文頭),瀏覽器依據「白名單」判斷是否容許此次請求,可應用與 Ajax、canvas 等的跨源狀況。
CORS 分爲 簡單請求(simple) 和 複雜請求(complex),他們最主要的區別就是需不須要預檢(preflight)。
簡單請求須要知足以下條件(只挑重點):
方法(method)爲以下之一
只容許設置以下報文頭(header)
Content-Type (只容許三個)
text/plain
multipart/form-data
application/x-www-form-urlencoded
不知足上面條件的都會被斷定爲複雜請求,就實際使用而言 form 發出的請求基本都是容許的,若是要使用 json 格式傳遞數據(即 Content-Type: application/json
),那一定是複雜請求。
複雜請求會先發出預檢請求,也就是先問問看服務端,若是返回的「白名單」符合要求再會發起正式的請求。
預檢請求是方法(method)爲 OPTION 的請求,它不須要攜帶任何業務數據,僅依照須要發送 CORS 相關請求報文頭給服務端,服務端也不須要響應任何業務數據,僅返回「白名單」,完成協商便可。
CORS 相關請求報文頭
CORS 相關響應報文頭(即「白名單」)
Vary: Origin
以避免當 API 給不一樣源頁面返回不一樣數據時,被緩存搞混;簡單請求與通常的 Ajax 流程徹底相同,僅需瀏覽器發送 Origin 請求報文頭,服務端返回 Access-Control-Allow-Origin 響應報文頭便可。
下面詳講複雜請求的狀況。
假設如今網頁源爲 http://demo.com
,服務端 API 源爲 http://api.demo.com
,需求請求的方法爲 POST ,數據類型是 json,自定義報文頭 token 。
發送預檢 OPTION 請求,有關 CORS 的報文頭設置以下:
Origin: http://demo.com
;Access-Control-Request-Method: POST
;Access-Control-Request-Headers: content-type, token
;服務器接收到預檢請求進行響應,有關 CORS 的報文頭設置以下:
Access-Control-Allow-Origin: http://demo.com
;Access-Control-Allow-Methods: POST, GET, OPTIONS
;Access-Control-Allow-Headers: Content-Type, token
;Vary: Origin
(上面有說明,它不屬於 CORS 報文頭,但必須)Origin: http://demo.com
,其他與正常請求一致;Access-Control-Allow-Origin: http://demo.com
和 Vary: Origin
,其他與正常響應一致;跨源相關的錯誤整體分兩類。
前端
服務端
不建議無腦添加 CORS 相關響應報文頭,要按需添加,以避免形成頭部冗餘,參考上面的流程,能夠大體可分爲兩組。
具體代碼請參考演示案例 CORS 部分源碼。
優勢
缺點