跨域這個問題你們並不陌生,這也是面試的高頻問題,不少人都背過,什麼由於同源策略啊,CORS
啊等等,跨域的標緻就是瀏覽器控制檯出現 Access to XMLHttpRequest at 'https://xxx.xxx.com' from origin 'https://xxx.xxx.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
。html
首先,只有 Web 瀏覽器纔會產生跨域,這是由於瀏覽器的同源策略在限制。同源策略就是 [域名(又稱爲主機 host),端口(port),協議(protocol)]
要統一,不然就會被瀏覽器斷定爲跨域,而後拒絕請求。可是嚴格的說,瀏覽器並非拒絕全部的跨域請求,實際上拒絕的是跨域的讀操做。前端
同源策略並非很差,它在必定程度上保證了瀏覽器的安全,只是沒法適應現代的潮流,在工程服務化後,不一樣職責的服務分散在不一樣的工程中,每每這些工程的域名是不一樣的,但一個需求可能須要對應到多個服務,這時便須要調用不一樣服務的接口。node
URL 說明 是否容許通訊
http://www.a.com/a.js
http://www.a.com/b.js 同一域名下 容許
http://www.a.com/lab/a.js
http://www.a.com/script/b.js 同一域名下不一樣文件夾 容許
http://www.a.com:8000/a.js
http://www.a.com/b.js 同一域名,不一樣端口 不容許
http://www.a.com/a.js
https://www.a.com/b.js 同一域名,不一樣協議 不容許
http://www.a.com/a.js
http://70.32.92.74/b.js 域名和域名對應ip 不容許
http://www.a.com/a.js
http://script.a.com/b.js 主域相同,子域不一樣 不容許(cookie這種狀況下也不容許訪問)
http://www.a.com/a.js
http://a.com/b.js 同一域名,不一樣二級域名(同上) 不容許(cookie這種狀況下也不容許訪問)
http://www.cnblogs.com/a.js
http://www.a.com/b.js 不一樣域名 不容許
複製代碼
CORS
?跨域資源共享(CORS) 是一種機制,它使用額外的 HTTP 頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的 Web 應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。 好比,站點
http://domain-a.com
的某 HTML 頁面經過<img>
的 src 請求http://domain-b.com/image.jpg
。網絡上的許多頁面都會加載來自不一樣域的 CSS 樣式表,圖像和腳本等資源。
出於安全緣由,瀏覽器限制從腳本內發起的跨源 HTTP 請求。 例如,XMLHttpRequest 和 Fetch API 遵循同源策略。 這意味着使用這些 API 的 Web 應用程序只能從加載應用程序的同一個域請求 HTTP 資源,除非響應報文包含了正確 CORS 響應頭。ios
跨域資源共享( CORS )機制容許 Web 應用服務器進行跨域訪問控制,從而使跨域數據傳輸得以安全進行。現代瀏覽器支持在 API 容器中(例如 XMLHttpRequest 或 Fetch )使用 CORS,以下降跨域 HTTP 請求所帶來的風險。nginx
不過呢,並不必定是瀏覽器限制了發起跨站請求,也多是跨站請求能夠正常發起,可是返回結果被瀏覽器攔截了。面試
在「上古時代」,解決跨域有不少黑科技,什麼 JSONP 啊,window.name
啊,document.domain
啊等等都用上了,但現代用的基本都是CORS
跨域,因此上述方法在這裏就不討論了。後端
由上述可知,實現 CORS
通訊的關鍵是服務器。只要服務器實現了 CORS
接口,就能夠跨源通訊。跨域
雖然工做重心在後端,可是做爲前端,也要了解這方面的知識,不然就會出現204
預請求,而後後端沒處理,就甩鍋給前端的狀況。瀏覽器
下面介紹一些 CORS
過程當中常見的 HTTP 響應頭(Response Headers)。安全
Access-Control-Allow-Origin
?這是 HTTP 響應首部中的一個字段,
具體格式是: Access-Control-Allow-Origin: <origin> | *
。
其中,origin
參數的值指定了容許訪問該資源的外域 URI。對於不須要攜帶身份憑證的請求,服務器能夠指定該字段的值爲通配符,表示容許來自全部域的請求。
若是服務端指定了具體的域名而非*
,那麼響應首部中的 Vary 字段的值必須包含 Origin。這將告訴客戶端:服務器對不一樣的源站返回不一樣的內容。例如:
// 只響應來自 http://mozilla.com 的請求
Access-Control-Allow-Origin: http://mozilla.com
複製代碼
Access-Control-Allow-Methods
?該字段必需,它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。
具體格式是:Access-Control-Allow-Methods: <method>[, <method>]*
Access-Control-Allow-Headers
?可支持的請求首部名字。請求頭會列出全部支持的首部列表,用逗號隔開。
若是瀏覽器請求包括Access-Control-Request-Headers
字段,則Access-Control-Allow-Headers
字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。
具體格式是:Access-Control-Allow-Headers: <header-name>[, <header-name>]*
Access-Control-Allow-Credentials
?該字段可選。它的值是一個布爾值,表示是否容許發送 Cookie。默認狀況下,Cookie 不包括在 CORS 請求之中。設爲 true,即表示服務器明確許可,Cookie 能夠包含在請求中,一塊兒發給服務器。
一旦服務器經過了"預檢"請求,之後每次瀏覽器正常的 CORS 請求,就都跟簡單請求同樣,會有一個 Origin 頭信息字段。服務器的迴應,也都會有一個 Access-Control-Allow-Origin
頭信息字段。
好了,鋪墊完成,接下來要說說我踩的坑了。
公司因歷史遺留,同時存在3種後端語言:Java,PHP,Node.js,由於後端同事都不懂 Node.js,因此 Node 項目一直是前端維護。老項目用的是 Koa
框架,以前我在上面寫邏輯,上傳圖片是沒有問題的,直到我用了 Nest
框架重構,將後臺管理系統的邏輯拆分解耦出來。
新項目一切請求都正常,本地跑的時候也正常,可是到了線上,只有上傳圖片不正常。每次上傳都會預請求一次 204 OPTIONS
,這一步沒問題,但接下來的 POST
請求就有問題了:
我一看控制檯的信息,結合多年的開發經驗(其實並無),這不就是跨域嘛,因而看代碼,main.ts
中已經有了 app.enableCors()
,百思不得其解,因而 Google,發現也有人遇到過相似的問題,說是 Nest 某些版本在線上環境沒法正確使用 CORS
,而後就造成了下列代碼:
可是,並無什麼卵用,調試了好一下子才注意到響應頭是 nginx 返回的,而後就像發現新大陸同樣屁顛屁顛的找運維老大:代碼裏面已經設置了 CORS
跨域了,是否是被 nginx 攔截了?(公司的服務器經過 nginx 作負載均衡,而後纔到 node)
而後運維老大很配合的幫我配置了 nginx 的跨域,而後就悲劇了,連 OPTIONS
都過不去😂:
控制檯的大體意思是 CORS 規則衝突了,只能使用一種。
因此這個問題其實和 nginx 沒有什麼關係,運維大佬看了訪問日誌,說是 OPTIONS 請求是響應了的,可是到 POST 請求的時候就斷開鏈接了,而後讓我試試直接訪問端口,因而控制檯又出現了以下信息:
唔,大體意思是,源頭是 https 協議的話,就不能請求 http 協議的資源。
因而我把網站上的 https 換成 http,就出現了以下信息:
我就以爲很奇怪,由於本地開發的時候,是能正常上傳圖片的。惟一不一樣的地方就在於,線上使用的是 pm2
進程管理工具,而我本地用的是自帶的 nodemon
。爲了驗證個人猜想,因而本身的電腦上也裝了 pm2
,而後跑起來,而後就。。。Bug 果真復現了。
因而讓運維大佬不用 pm2 直接用 nodemon 啓動試試,而後折騰我好久的跨域問題就「解決了」。爲何打引號呢,由於運維大佬並非很想用 nodemon 來管理進程,主要是若是服務器宕機,不能自動拉起,戰役還遠沒有結束。
而後我就圍繞着 pm2 繼續探索,把官方文檔看了個遍,也沒找到關鍵點,鬱悶之下,只好檢查的 pm2 啓動配置,而後注意到這個:
這是當初我爲了輸出日誌的時候,更新了文件,防止被 pm2 監聽到,致使服務一直重啓所作的措施。而後就想到了,我上傳圖片的時候,是先在硬盤保存,而後讀取 Buffer 流,而後再上傳到 oss,最後刪掉硬盤的圖片,核心代碼以下:
因此我就在想,是否是由於上傳的時候,存本地的圖片觸發了 pm2 的監聽,致使服務重啓,因此就會報 net::ERR_CONNECTION_RESET
,因而我改爲了臨時目錄 const uploadCachePath = '/tmp/assets/uploads';
(Mac OS、Linux 都有這個目錄),而後 pm2 啓動,上傳,成功。
至此,折磨了我近一週的 Bug 終於修復,結果和跨域沒有半毛錢關係。
有讀者可能注意到 /tmp/assets/uploads
路徑,要是同事用的是 Windows 系統開發咋辦?這個我天然也想到了,因而改了 pm2 的啓動項:
但是不管怎樣改,依然會觸發上述 Bug,由於服務器同時跑着 2 個 Node 項目,另外一個項目也有本身的 pm2 啓動項,因此感受這個配置被另外一個覆蓋了,pm2 彷佛是全局的。若是有其餘大神深刻了解過 pm2 的能夠指點一下。
因此和運維大佬討論了一下,給我開了權限,就暫時用這個臨時目錄,待之後找到更好的解決方案再優化,反正目前這個項目也只有我一人在維護。
就在我剛找到解決方案的時候,我帶的小弟跑過來問我是否是動了配置文件,老項目怎麼都跨域了。我去看他的控制檯,確實有 Access to ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
的信息,因爲剛踩的坑,況且我也沒動過配置文件,因此以爲這確定不是跨域問題。而後我看了服務器的日誌,發現一直在重啓,由於有個模塊他沒同步上去,只同步了路由文件,致使路由找不到對應的函數,服務就一直在報錯重啓,根本就沒處理請求。因而讓他把代碼從新上傳,問題解決。
因此我的猜想(由於沒有權限看服務器的配置),此次跨域是 nginx 代理的時候,由於訪問不到 Node 服務,因此天然而然地就讀不到 CORS 配置,而後報跨域錯誤。
Axios 建立實例時,有個字段須要注意:
若是不設爲 false,則會獲得下面報錯:
緣由就是前面提到的 Access-Control-Allow-Credentials
字段,CORS 請求默認不發送 Cookie 和 HTTP 認證信息。若是要把 Cookie 發到服務器,一方面要服務器贊成:
Access-Control-Allow-Credentials: true
另外一方面,開發者必須在 AJAX 請求中打開 withCredentials
屬性,也就是 Axios 默認打開的 withCredentials: true
。
不然,即便服務器贊成發送 Cookie,瀏覽器也不會發送。或者,服務器要求設置 Cookie,瀏覽器也不會處理。
可是,若是省略withCredentials
設置,有的瀏覽器仍是會一塊兒發送 Cookie。這時,須要顯示關閉 withCredentials: false
。
須要注意的是,若是要發送 Cookie,Access-Control-Allow-Origin
就不能設爲星號,必須指定明確的、與請求網頁一致的域名。同時,Cookie 依然遵循同源政策,只有用服務器域名設置的 Cookie 纔會上傳,其餘域名的 Cookie 並不會上傳,且(跨源)原網頁代碼中的 document.cookie
也沒法讀取服務器域名下的 Cookie。
由上述能夠總結,在後端配置了 CORS 的狀況下,還會形成 Access to ... has been blocked by CORS policy ...
的狀況大體有:
withCredentials: true
,致使先行驗證並攔截了請求;有些時候,瀏覽器控制檯給出的錯誤信息,不必定能真正地指出問題的所在,作爲前端,還須要多去了解一些更本質的東西。
《HTTP 權威指南 - O'REILLY》
·