// 正文共:1400 字// 預計閱讀時間:6 分鐘
跨域請求
如果你沒有沒有遇過,可以試著在瀏覽器的 console 頁輸入下面的程式碼:
const xhr = new XMLHttpRequest()xhr.onreadystatechange = () => { if (xhr.readyState === 4) { console.log(xhr.status === 200 ? xhr.responseText : 'error') }}xhr.open('GET', 'https://google.com')xhr.send()
這段程式碼透過呼叫瀏覽器的 XMLHttpRequest 對 Google 發出請求,而得到的結果如圖所示:
這就是跨域請求問題,當透過 JavaScript 對不同的來源傳送請求時,這個請求的響應就會被瀏覽器攔截,不交給 JavaScript 處理。這裡的“不同來源”指的是目標資源與當前網頁的域(domain)、通訊協議(protocol)或網路埠(port)只要有任一項不同,就算是不同來源。例如下面這幾個例子:
假設當前使用者在:https://example.com :[✅] https://example.com/test -> 同域[❌] https://m.example.com -> 不同域[❌] https://example.com:3000 -> 埠不同[❌] http://example.com -> 通訊協議不同
理解什麼是跨域了,那為什麼瀏覽器要把跨域請求資源攔截掉呢?
其實這是考慮到使用者的資訊保安。
假設小黑是一個惡意開發者,他編寫的網站會嘗試透過 XHR 打向百度、微博等目標網站;如果使用者原先就有目標網站的登入狀態,小黑便能窺探他的隱私,得到不該取得的資料。再想想看,如果目標網站換成 Email、銀行、電商,如果沒有瀏覽器限制跨域請求的保護,惡意開發者便能為所欲為。
❝
注意:跨域請求雖然會被瀏覽器攔截下來,但攔截的是響應(Response)而不是請求(Request)。
❞
解決方案關於跨域請求的解決方案有很多,例如 JSONP,也就是透過 HTML 中沒有跨域限制的標籤如 img、script 等,再透過指定回撥函式,將響應的內容介接回 JavaScript 中;或是透過 iframe,繞過跨域保護獲取目標資源等。下面僅說明兩種常見也相對正規的解決方式。
CORS 規範中,清楚定義了跨域存取控制的運作方式。
首先伺服器端需要在響應頭中加上如 Access-Control-Allow-Origin、Access-Control-Request-Method、Access-Control-Request-Headers 等設定,來限制伺服器所能接受的來源、請求的方法、可攜帶的頭等等。
當瀏覽器傳送資源請求時,如果是簡單請求便會直接送出請求;若不符合前述條件,則會透過預檢(Preflighted)請求先敲敲門,確認是否可以透過伺服器的限制,然後才會傳送正式的請求。
❝
CORS 除了上述內容外,也有關於 Cookies 的傳送方式,如何允許跨域寫入 Cookies 等內容。
❞
代理伺服器由於 CORS 的頭設定是在伺服器端,如果伺服器是自己的,那麼可以輕易的調整伺服器設定,讓前端能取得必要的資源;但如果你請求的是外部 API,總不能每次遇到 CORS 錯誤,就要求別人去修改頭設定吧。
簡單暴力的方法就是透過代理伺服器幫我們獲取資源;由於跨域保護的限制是瀏覽器的規範,只要不透過瀏覽器傳送請求,自然也就不會有限制。
常見的作法是透過 nginx 做簡單的反向代理;例如在自己的開發環境,前後端分離的架構,前後端服務分別啟動在 3000 和 5000 埠,則可以用這樣的配置:
server{ listen 3000; server_name localhost; location ^~ /api { proxy_pass http://localhost:5000; }}
當前端需要傳送 API 請求時,可以直接請求 localhost:3000/api/...,這個請求會被 nginx 攔截,並轉發給後端所在的 localhost:5000,這樣就能簡單的繞過跨域保護了。
總結跨域是前端常見的需求,CORS 的錯誤資訊也是我們很容易被卡住的地方;其實只要清楚 CORS 規範中的 HTTP 頭設定,並在伺服器端做對應的調整,就可以順利的完成跨域請求。