iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 22
1
Modern Web

前端三十 - 成為更好的前端工程師系列 第 22

22. [FE] 為什麼跨域請求會產生錯誤?如何處理?

header

如果讀者您在開發、建置網站的過程中,有嘗試透過任何套件、框架,或是瀏覽器的 fetchXHR 串接外部 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;而得到的結果如圖所示:

https://ithelp.ithome.com.tw/upload/images/20191008/20111380WLwoESnF6Z.png

這就是很常聽到的跨域請求問題,當開發者透過 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 中沒有跨域限制的標籤如 imgscript 等,再藉由指定 callback 函式,將回應內容介接回 JavaScript 中;或是透過 iframe,繞過跨域保護取得目標資源等等;本文接下來會說明兩種常見,也相對正規的解決方式。

CORS

最標準、正確的解決方法是透過 W3C 規範 的「跨來源資源共用(Cross-Origin Resource Sharing,CORS)」,透過 伺服器在 HTTP Header 的設定,讓瀏覽器能取得不同來源的資源。

CORS 規範中,清楚定義了跨域存取控制的運作方式。

首先,伺服器端需要在回應的 Header 加上如 Access-Control-Allow-OriginAccess-Control-Request-MethodAccess-Control-Request-Headers 等設定,限制伺服器能接受的來源、使用的方法、可攜帶的 Header 等等。

當瀏覽器發送資源請求時,如果是 簡單請求,便會直接送出請求;若不符合前述條件,則會透過預檢(Preflighted)請求先敲敲門,確認可以通過伺服器限制,才會發送正式的請求。

Penny

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

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
21. [FE] 用過 Webpack 之類的打包工具嗎?為什麼需要?
下一篇
23. [FE] 網頁的快取機制是怎麼運作的?
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言