先後端分離的一個好處是能夠將前端和後端運行環境分開,各自進行管理和優化,增長了系統部署的靈活性和彈性。可是,這也帶來了瀏覽器跨域資源共享(CORS)問題,致使瀏覽器沒法訪問後端服務。本文搭建一個簡單示例(vue+json-server+nginx)說明該問題以及解決方法。html
跨域資源共享(CORS) 是一種機制,它使用額外的HTTP頭來告訴瀏覽器 讓運行在一個 origin (domain) 上的Web應用被准許訪問來自不一樣源服務器上的指定的資源。當一個資源從與該資源自己所在的服務器不一樣的域、協議或端口請求一個資源時,資源會發起一個跨域 HTTP 請求。前端
好比,站點 domain-a.com 的某 HTML 頁面經過 的 src 請求 domain-b.com/image.jpg。網…vue
出於安全緣由,瀏覽器限制從腳本內發起的跨源HTTP請求。 例如,XMLHttpRequest和Fetch API遵循同源策略。 這意味着使用這些API的Web應用程序只能從加載應用程序的同一個域請求HTTP資源,除非響應報文包含了正確CORS響應頭。ios
參考:developer.mozilla.org/zh-CN/docs/…nginx
vue create try-corsexpress
在項目下建立放測試數據的目錄json-server,在目錄下建立文件db.json文件。npm
{
"hello": {
"id": 1,
"msg": "你好,我是json-server"
}
}
複製代碼
啓動模擬API服務,經過參數指定測試數據的位置和服務的端口。json
npx json-server json-server/db.json --port 4001axios
在瀏覽器地址欄中輸入http://localhost:4001/hello,檢驗是否啓動成功。後端
cnpm i axios -S
修改App.vue文件,引入axios。
<template>
<div id="app">
<button @click="sendRequest">發送請求</button>
</div>
</template>
<script>
import axios from 'axios'
export default {
name: 'app',
methods: {
sendRequest() {
let api_url = 'http://localhost:4001/hello'
axios.get(api_url).then(rsp => {
alert(JSON.stringify(rsp.data))
})
}
}
}
</script>
複製代碼
在瀏覽器中打開頁面,點擊【發送請求】按鈕,成功返回結果。這時並無由於跨域的問題致使請求失敗,這是由於json-server默認是開啓支持CORS。
啓動json-server時添加參數--no-cors
或--nc
。
npx json-server json-server/db.json --port 4001 --nc
在瀏覽器中打開頁面,打開【開發者工具】,點擊【發送請求】按鈕,查看請求執行的結果。
Access to XMLHttpRequest at 'http://localhost:4001/hello' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
注意:測試瀏覽器有可能會緩存結果,須要關閉緩存。
有兩種解決方式:一、修改業務代碼,直接支持CORS;二、經過nginx進行代理,支持CORS。
json-server
經過指定參數開關CORS
就是一種後端解決方案。cors
是express
的中間件(koa用的是@koa/cors),能夠設置各類CORS選項。下面代碼是一種最簡單的設置。
cors({
origin: true,
credentials: true
})
複製代碼
json-server支持CORS後,咱們在瀏覽器的開發者工具中觀察響應頭,會看到以下內容:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: http://localhost:8080
服務器端添加這兩個頭就是告訴瀏覽器,「我支持CORS」。
關閉json-server
對CORS的支持,將端口改成4002。
npx json-server --port 4002 --nc json-server/db.json
啓用nginx,開啓4001端口,反向代理到4001端口。
server {
listen 4001;
server_name localhost;
location / {
proxy_pass http://localhost:4002;
}
}
複製代碼
在瀏覽器中調用請求,返回失敗。
修改nginx配置文件,直接加頭。
server {
listen 4001;
server_name localhost;
location / {
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin http://localhost:8080;
proxy_pass http://localhost:4002;
}
}
複製代碼
Access-Control-Allow-Credentials 響應頭表示是否能夠將對請求的響應暴露給頁面。返回true則能夠,其餘值均不能夠。
參考:developer.mozilla.org/zh-CN/docs/…
弄清楚了CORS,咱們研究一下真實的部署問題。
須要和先後端代碼分離一同考慮的問題是,後端代碼的微服務化,也就是一個前端要訪問多個獨立部署的後端服務,例如:獨立的登陸鑑權服務,獨立的日誌服務。
Server1到Server4是4個獨立的服務環境,前端代碼部署在Server1,不一樣的後端服務分別部署在Server2到Server4,用戶經過瀏覽器訪問Server1得到前端代碼,瀏覽器中執行前端代碼訪問Server2-Server4。Server2到Server4是3個不一樣地址,前端代碼須要分別指定,這就致使前端代碼依賴運行環境的問題。
除了因爲先後端代碼單獨部署致使的代碼依賴運行環境,另外一個問題是內外網隔離。業務服務器都部署在內網,只暴露一個出口(互聯網ip+端口),這種狀況下,若是前端代碼中寫的是內網服務的地址確定訪問不了。
綜合先後分離和內外隔離兩方面的需求,編寫前端代碼時必須實現一種機制,避免在代碼中硬編碼後端服務地址,應支持根據具體的部署環境進行指定。
用vue開發時,能夠經過指定環境變量的方式實現上面的要求。
在項目的根目錄下建立.env
,.env.local
等文件指定環境變量,注意環境變量必須以VUE_APP_開頭,例如:
VUE_APP_SERVER2=xxxx
VUE_APP_SERVER3=yyyy
VUE_APP_SERVER4=zzzz
複製代碼
在代碼中經過process.env
訪問:
console.log("VUE_APP_SERVER2", process.env.VUE_APP_SERVER2)
console.log("VUE_APP_SERVER3", process.env.VUE_APP_SERVER3)
console.log("VUE_APP_SERVER4", process.env.VUE_APP_SERVER4)
複製代碼
這種方式並非在運行時使用環境變量,而是在編譯時用定義的值替換代碼中的環境變量。因此,針對不一樣的部署環境(能夠給不一樣環境指定不一樣名稱的env文件,具體參考官網文檔),指定相應的環境變量後,須要進行編譯造成和環境綁定的發佈版本。這樣就實現了編碼階段不須要硬編碼服務地址,編譯時再根據須要指定。
利用nginx能夠很好地解決跨域和內外網隔離問題,因此建議優先考慮採用nginx。若是徹底是在內網環境,能夠在後端服務上增長CORS支持。
編寫前端代碼時,應規劃好須要訪問哪些服務,經過設置環境變量,避免對後端地址硬編碼。