如果讀者您在開發、建置網站的過程中,有嘗試透過任何套件、框架,或是瀏覽器的 fetch
、XHR
串接外部 API,相信對於「跨域請求」這個名詞一定不會陌生,更別說是那個怵目驚心的 CORS 錯誤訊息了;今天我們就來討論跨域問題的原因,以及解決方法。
本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!
如果前述的狀況讀者沒有遇過,可以嘗試著在瀏覽器 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 送出 Request;而得到的結果如圖所示:
這就是很常聽到的跨域請求問題,當開發者透過 JavaScript 針對不同於當前位置的來源發送請求,這個請求的回應就會被瀏覽器攔截掉,不交給 JavaScript 處理。這邊的「不同來源」,指的是目標資源與當前網頁的網域(domain)、通訊協定(protocol)或通訊埠(port)只要有任一項不同,就算是不同來源。例如以下幾個例子:
假設目前使用者在:https://example.com :
[O] https://example.com/test -> 同域
[X] https://m.example.com -> 網域不同
[X] https://example.com:3000 -> PORT 不同
[X] http://example.com -> 通訊協定不同
理解什麼是跨域了,那為什麼瀏覽器要這麼雞婆把跨域請求資源擋掉呢?
其實這是考量到使用者的資訊安全。
假設 Alice 是一個惡意開發者,他撰寫的網站在運作的過程中,會嘗試透過 XHR 打向 FaceBook、Instagram 等目標網站;如果使用者原先就有目標網站的登入狀態,Alice 便能窺探該使用者的隱私,取得不該取得的資訊。再設想看看,如果目標網站換成Email、銀行、電商,甚至 GitHub,若沒有瀏覽器限制跨域請求的保護,惡意開發者便能為所欲為。
值得注意的是,跨域請求雖然會被瀏覽器擋下來,但攔截的是回應(Response),不是請求(Request)喔!請求指定的內容仍然會完成,開發者要特別注意這點!
關於跨域請求,解決方案有不少種,例如鼎鼎大名的 JSONP,也就是透過 HTML 中沒有跨域限制的標籤如 img
、script
等,再藉由指定 callback 函式,將回應內容介接回 JavaScript 中;或是透過 iframe
,繞過跨域保護取得目標資源等等;本文接下來會說明兩種常見,也相對正規的解決方式。
最標準、正確的解決方法是透過 W3C 規範 的「跨來源資源共用(Cross-Origin Resource Sharing,CORS)」,透過 伺服器在 HTTP Header 的設定,讓瀏覽器能取得不同來源的資源。
CORS 規範中,清楚定義了跨域存取控制的運作方式。
首先,伺服器端需要在回應的 Header 加上如 Access-Control-Allow-Origin
、Access-Control-Request-Method
、Access-Control-Request-Headers
等設定,限制伺服器能接受的來源、使用的方法、可攜帶的 Header 等等。
當瀏覽器發送資源請求時,如果是 簡單請求,便會直接送出請求;若不符合前述條件,則會透過預檢(Preflighted)請求先敲敲門,確認可以通過伺服器限制,才會發送正式的請求。
Penny, Penny, Penny...
CORS 除了前述的內容外,也有關於 Cookies 的傳遞方式,如何允許跨域寫入 Cookies 等等;更詳細的 CORS 與 Cookies 說明,可以參考 這篇。
由於 CORS 的 Header 設定是在伺服器端,若是自家產品,可以輕易的調整伺服器設定,讓前端能取得必要的資源;但如果是介接外部 API,總不能每次遇到 CORS 錯誤,就要求外部修改 Header 設定吧?
一個簡單暴力的作法,是透過代理伺服器幫我們取得資源;由於跨域保護的限制是瀏覽器的規範,只要不透過瀏覽器發送請求,自然也就不會有限制。
常見的作法,可以打向另外架構的中介伺服器,或是透過 nginx
做簡易的反向代理;例如在自己的開發環境,前後端分離的架構,前後端伺服器分別起在 3000 & 5000 PORT,可以透過這樣的設定:
server{
listen 3000;
server_name localhost;
location ^~ /api {
proxy_pass http://localhost:5000;
}
}
當前端需要發送 API Request 時,可以直接打向 localhost:3000/api/...
,這個請求就會被 nginx
攔截,並轉送給後端所在的 localhost:5000
;這樣就能簡單的規避掉跨域保護囉。
跨域是實務上很常遇到的需求,CORS 的錯誤訊息也是前端初學者很容易卡住的地方;其實只要清楚 CORS 規範中的 Headers,並在伺服器做相對應的調整,就可以順利的完成跨域請求囉!
那麼今天的文章就到這邊啦,如果有任何關於本文內容的問題,都非常歡迎您一同交流討論!
筆者
Gary
半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。相信一切安排都是最好的路。