做爲前端開發,從入行起,應該就接觸過跨域的概念。工做中,在與服務端配合時,也常常須要處理跨域相關問題。若是你不能理解到底什麼是跨域,那在與服務端配合解決跨域問題時,你可能就要落入對方的掌控之中了(哈哈,開個玩笑)。html
爲了不這一尷尬的境地,今天我來帶着你們一塊兒重溫並鞏固一下如下內容:什麼是跨域,跨域有哪些實際的開發場景,有哪些方式能夠快速的處理跨域問題。前端
整篇內容沒有涵蓋網上全部的與跨域「沾邊」的知識,而是從實際場景出發,旨在解決工做中常常遇到的跨域問題。webpack
若是讓你直接定義什麼是跨域,你可能會發現很難定義。因此須要藉助與之對立的概念——同源策略(SOP,Same-Origin Policy)。只要不是同源的,那就是跨域的。nginx
同源策略是瀏覽器中一個極爲相當重要的安全機制,能夠用來限制某一源內的文檔或腳本與另外一源內的資源如何進行交互。其目的在於隔離潛在的惡意文檔,減小可能存在的攻擊。git
而對於同源的定義是:協議、主機名(host)以及端口三者均相同。github
維基百科中對於 URI 的結構組成說明以下:web
[協議名]://[用戶名]:[密碼]@[主機名]:[端口]/[路徑]?[查詢參數]#[片斷ID]
複製代碼
常規狀況下,咱們無需在 URI 中帶有用戶名密碼等信息用於驗證。這只是一個完整的 URI 組成示例。ajax
因此對於同源,只要 URI 中協議名、主機名、端口三者有其中一條不一樣,則視爲不一樣源。不一樣源之間請求資源,則爲跨域。其中主機名部分,主域和子域視爲不一樣、域名與其對應的 IP 也視爲不一樣,這就是說看着必須得同樣。後端
當存在跨域問題時,瀏覽器會作出必定的限制措施。主要包括如下三點:api
注:Cookie 獲取不檢測端口
撇開場景談概念,必定是晦澀難懂的,開始說了,本文旨在解決實際工做中遇到的跨域問題。下面咱們來一塊兒看看工做過程當中比較常見的跨域場景。
在先後端分離的開發模式下,開發環境應該用webpack
的居多(固然有的可能不是,以此爲例),與之相應的 web 服務器就是webpack-dev-server
。這類開發模式的架構通常以下:
這一架構下,dev-server 中的頁面若是經過 ajax 直接調用服務端的 API 會存在跨域問題。
與第一種方式類似,先後端分離的項目在開發完成後,每每經過 nginx 等做爲靜態資源服務器,前端頁面直接經過 ajax 發送請求,依然存在跨域請求問題。
架構以下:
有時候,咱們所要調用的接口層可能並不僅是給咱們提供服務,他們只會提供一些通用的數據,咱們須要對數據進行必定程度的二次加工;也可能咱們須要本身給前端頁面提供一些通用的功能,如圖片上傳等。這時,就須要在前端頁面和接口層之間增長一個 BFF 層(Backends For Frontends)。
BFF 層通常由前端維護,因此使用 Node.js 居多。
這一架構以下:
使用這種架構其實自己已經解決了跨域問題,是一種跨域解決方式,後面咱們再細說。
最後一種是最原始的 web 服務架構,html 頁面以及其餘靜態資源都直接從服務器獲取,接口也直接由所在服務器處理。這種方式不存在跨域問題。前端和服務端邏輯徹底綁定,互相支撐提供服務。
前面咱們提到,跨域是瀏覽器的限制。因此咱們想解決跨域問題能夠有兩個方向,第一是繞開瀏覽器限制,第二是經過瀏覽器支持的方式來容許跨域。
下面咱們分別會介紹三種繞開瀏覽器限制的解決方式,分別爲webpack-dev-server 代理/Nginx 代理轉發/服務器代理,以及瀏覽器自己支持的 CORS 方式。
沒有你們耳熟能詳的 JSONP,你們自行科普一下吧。
對於上面說到的「先後端分離:純前端 + 接口層 (開發模式)」這一場景,當咱們在http://co.com
的頁面上直接調用http://api.co.com
的接口時,會出現跨域問題。
咱們能夠將全部的接口請求都從http://co.com
發出,如http://co.com/api/getSomeData
(額外加了/api,方便統一轉發),最後經過 proxy 配置代理,轉發到最終的接口服務器http://api.co.com/getSomeData
。
proxy 配置以下:
devServer: {
proxy: {
'/api': {
target: 'http://api.co.com',
// 若是轉發後的pathname須要改變,能夠經過如下方式重寫
// 下面是把api前綴去掉
pathRewrite: {
'^/api/': '',
},
},
}
}
複製代碼
經過上述方式,咱們能夠在接口請求發起的時候,統一從當前所在源發起,最後經過 proxy 代理的方式轉發到真正的接口層。這樣就繞開了調用接口時瀏覽器的同源限制。
針對第二部分提到的「先後端分離:純前端 + 接口層(生產模式)」這一場景,這時咱們沒有webpack-dev-server
可用了,不過不要緊,咱們在使用 nginx 做爲靜態資源服務器時,也能夠作一些代理轉發。能夠將接口請求所有轉發到對應接口服務器。
配置以下:
location /api {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
# 轉發時重寫地址
rewrite ^/api/(.*)$ /$1 break;
# 轉發目的地
proxy_pass http://api.co.com;
}
複製代碼
這種方式其實與第一種相似,只不過是經過不一樣的方式進行代理。這種方式也是經過繞過瀏覽器限制的方式解決跨域的。
第二部分的第三種架構「先後端分離:純前端 + BFF + 接口層」,這種架構其實就已經解決了跨域的問題,前端頁面的全部接口都由 BFF 層進行管理。
對於 BFF 層,能夠經過添加中間件或者其餘的方式對於接口進行攔截。若是是靜態資源或者是當前服務所提供的接口,則直接處理。若是是調用 api 的接口請求,將其轉發到對應的服務便可。
不一樣的框架有不一樣的方式來處理接口攔截與轉發,因此此處沒有代碼。
跨域資源共享(CORS) 是一種機制,服務端能夠經過額外的 HTTP 頭來告訴瀏覽器容許某一源內的 Web 應用訪問不一樣源服務器上的指定資源。
CORS 使用通用的跨域解決方式,須要服務端配合進行實現。
這裏面會涉及到簡單請求以及預檢請求的概念。關於什麼是簡單請求,你們能夠移步MDN看下詳細的定義,這裏再也不詳述了。
這兩種請求的區別在於,對於預檢請求,瀏覽器必須首先使用 OPTIONS 方法發起一個預檢請求(preflight request),從而獲知服務端是否容許該跨域請求。服務器確認容許以後,才發起實際的 HTTP 請求。
爲何要區分簡單請求和預檢請求能夠參考賀老的 這篇文章 IE8/9 不支持 CORS,經過 XDomainRequest 來實現
對於簡單請求,服務端經過簡單的設置Access-Control-Allow-Origin: *
便可容許任意來源進行跨域請求。若是隻想容許來自http://co.com
的訪問,能夠設置Access-Control-Allow-Origin: http://co.com
。
通訊過程示意圖以下:
注:在發起跨域請求時,瀏覽器會在請求頭字段中自動帶上 Origin 字段,值爲當前所在域。
對於預檢請求,服務端須要額外再多作一些事情。以下步驟:
通訊過程示意圖以下:
須要注意的是,服務端在處理預檢請求時,若是容許跨域,服務端只須要設置對應的響應頭,而後直接返回便可,無需其餘處理。
常規來講,咱們的請求都須要帶有身份憑證(如 Cookie),這時服務器端的響應中須要額外設置Access-Control-Allow-Credentials: true
,若是未設置,瀏覽器將不會把響應內容返回給請求的發送者。
還有個別不是很經常使用的請求頭和響應頭字段,你們可前往MDN查看完整的列表。
如上,咱們從如下三個方面介紹了跨域:什麼是跨域,跨域有哪些實際的開發場景,有哪些方式能夠快速的處理跨域問題。
你們在遇到跨域問題時,能夠根據具體的場景選擇繞過跨域問題,仍是選擇通用的 CORS 模式來解決。
最後但願你們看完這篇文章以後,都會是『那些年咱們「跨」過的「域」(接口篇)』。而不是『那些年咱們都沒「跨」過去的「域」』🤣 🤣 🤣。