Vue 項目代理設置的優化
Vue 類的項目開發中項目結構基本都是類似於 Vue-cli 生成的方式,
這種方式開發中,最常用到的模式是開啟代理進行 mock 調試或遠程調試,
也就是使用了 Vue-cli 設置的配置 proxyTable
或者直接使用 Webpack-dev-server
提供的 proxy 選項。它是采用了 http-proxy 庫,所以具體配置可查看:
https://github.com/nodejitsu/node-http-proxy#options
利用配置的這些參數我們可以做更為靈活的配置,達到更好的效果
使用需求
假設我們本地開發目前以下幾種狀態:
- 本地開發,數據使用本地的 mock Server
- 涉及權限接口使用本地 mock 數據,其他全部使用指定的一台遠程機器
- 涉及權限接口使用本地 mock 數據,其他數據分接口使用不同的遠程機器
- 所有接口使用同一台遠程機器
方案
先看下經典的proxyTable 寫法:
proxyTable: {
'/authui/': {
target: target,
changeOrigin: true
},
'/vendor/': {
target: target,
changeOrigin: true
}
}
其中用到了 changeOrigin
字段,主要是用於改變請求的 header。細化下需求:
- 本地開發:target 指向 localhost 的某個端口即可。至於 host 的驗證肯定是不需要的
- 部分本地,其他固定的一台遠程機器:需要配置 localhost 和遠程的地址,遠程地址多半是需要驗證 host 的
- 同二,但機器有多台:需要手動配置多台機器
- 同一台遠程機器,此時機器可能要嚴格驗證,即 IP 也必須使用域名,配置好系統 host 才可使用
說明:嚴格驗證 host 和普通驗證 host 區別主要在於嚴格驗證時,請求的 url 必須是遠程機器的域名,
不能直接修改請求的 header 的 host 實現,即必須在系統 host 層面配置好域名。
分析完成具體需求好,就開始准備實現的方式。原有開發方式是執行 npm run dev
,如果我們需要在命令行層面添加配置,
就需要設置為 npm run dev --param=paramvalue
的方式。對於使用 npm 的 script 腳本執行的命令,
它參數的獲取無法通過 process.env
獲得,而且通過 process.env.npm_config_paramName
的方式獲取,
使用現成的命令行參數解析庫也不是很方便,但為了省事,暫時還是使用 npm 自帶的解析。
請求發起過程中需要以下幾個參數:
- host: 發起請求需要指向的 host,可能每台機器驗證並不相同
- port: 代理轉發的端口
- receiver: 用於 push 的遠程地址,內包含了 ip 地址,為了省事,沒有單獨列出 ip 地址
然后定義代理請求自定義類型,用於配置:
- local: 本地地址,即 localhost
- remote: 指定的遠程機器
- 其他自定義類型:用於在配置文件中已經指定的其他類型
- 原版本的請求,如 'http://xxx' 或者 Object 類型的配置,此類代理永不處理
根據需要,我們添加以下幾個參數用於控制代理指向地址:
- rd: 遠程機器的地址
- focus: 嚴格模式,所有自定義類型的代理轉換為指定的 rd 機器,只在存在 rd 參數時可用
- allLocal:自定義類型代理全部指向本地
- host:請求發現是否使用 host,而不是 IP 地址
總結一下(序號指向前面的需求):
- 需要使用 host 進行訪問的情形:4
- 需要更改 host:除 localhost 外都需要更改
- 需要對已有類型進行轉換:1: 需要將所有自定義類型都轉換為 local, 2和3:什么也不轉換,4:所有的自定義類型全部轉換為 remote 類型
這么一看,貌似 host 是不需要的,它的存在主要是針對某些 機器可能需要使用 host 的方式,所以還是保留一下。
實現
邏輯理清了就很簡單了,配置文件設置為:
module.export = {
rd1: {
host: 'dev1.example.com',
port: 8838,
receiver: 'http://1.1.1.1:8888/receiver'
},
rd2: {
host: 'dev2.example.com',
port: 8838,
receiver: 'http://1.1.1.1:8888/receiver'
}
}
proxyTable 配置方式
{
proxyTable: {
'/api1': 'remote',
'/api2': 'rd2',
'/auth/xx': 'local',
'/other': 'http://example.com'
}
}
獲取 proxyTable 的代碼:
// 處理 proxyTable
const releaseConfig = require('../config/release.conf.js')
const rdConfig = releaseConfig[process.env.npm_config_rd]
const isAllRemote = process.env.npm_config_focus
const useHost = isAllRemote || process.env.npm_config_host
// 是否本機開發,本機開發 remote 會指向 local
const isAllLocal = process.env.npm_config_allLocal
module.exports = function (proxy) {
const localUrl = `http://localhost:${proxy.localProxyPort}`
const defaultHost = proxy.defaultRdHost || 'dev-example.com'
const localProxyPort = proxy.localProxyPort || 8787
const finalConfig = formatReleaseConfig(releaseConfig)
const remote = finalConfig.remote || {}
if (process.env.npm_config_rd) {
if (!rdConfig) {
throw new TypeError('RD 機器名稱不存在,請在 config/release.conf.js 中進行配置')
}
if (!remote.ip) {
throw new Error('請配置 rd 機器的 receiver')
}
}
if (isAllRemote && !rdConfig) {
throw new TypeError('focus 只能在提供了 rd 名稱后可設置')
}
function formatReleaseConfig (config) {
const result = {}
Object.keys(config).map((key) => {
const value = config[key]
const ipMatch = (value.receiver || '').match(/:\/\/(.*?):\d/)
const ip = ipMatch && ipMatch[1]
result[key] = {
ip,
host: value.host || defaultHost,
port: value.port || '8391'
}
})
// 設置 remote
if (rdConfig) {
const ipMatch = (rdConfig.receiver || '').match(/:\/\/(.*?):\d/)
const ip = ipMatch && ipMatch[1]
result.remote = {
ip,
host: rdConfig.host || defaultHost,
port: rdConfig.port || '8391'
}
}
// 設置 local
result.local = {
ip: 'localhost',
host: 'localhost',
port: localProxyPort
}
return result
}
function setProxy (proxyTable) {
const result = {}
Object.keys(proxyTable).forEach((api) => {
let type = proxyTable[api]
const isCustomType = typeof type === 'string' && !/^http/.test(type)
if (isCustomType && type !== 'remote' && type !== 'local' && !finalConfig[type]) {
throw new TypeError(`代理類型${type}不正確,請提供 http 或 https 類型的接口,或者指定正確的 release 機器名稱`)
}
if (type === 'remote' && !finalConfig.remote) {
type = 'local'
}
if (isCustomType) {
if (isAllRemote && type !== 'remote') {
type = 'remote'
}
if (isAllLocal && type !== 'local') {
type = 'local'
}
}
const targetConfig = finalConfig[type]
let target = type
if (targetConfig) {
target = {
target: `http://${useHost ? targetConfig.host : targetConfig.ip}:${targetConfig.port}`,
// 使用 host 時需要轉換,其他不需要轉換
headers: {
host: `${targetConfig.host}:${targetConfig.port}`
}
}
}
result[api] = target
})
return result
}
return {
proxyTable: setProxy(proxy.proxyTable),
host: remote.host || defaultHost
}
}
用法
用法中需要配置兩種指向:系統 host 和瀏覽器代理 Host。
之所以要兩種 host, 本質上是因為接口使用的域名
和我們的本地訪問的域名是相同的,同一域名無法指向兩個地址,所以相當於對瀏覽器端進行了攔截。
系統 host 推薦使用 switchHost 進行切換,瀏覽器推薦使用 whistle 進行切換。
本地開發
host 配置:無
whistle 配置:默認的域名
127.0.0.1 dev.example.com
啟動命令:
npm run dev
npm run dev --allLocal
注: 此時 proxyTable 中配置的 remote 全部轉換為 local,在 allLocal 參數時將所有自定義類型轉換為 local
本地 + 1 台遠程
host 配置:無
whistle 配置:默認的域名
127.0.0.1 dev1.example.com
127.0.0.1 dev2.example.com
啟動命令:
npm run dev --rd=rd1
npm run dev --rd=rd1 --host
注: --host 表示使用訪問使用 host 而非 ip,使用時需要 host 地址
本地 + n 台遠程
host 配置:無
whistle 配置:默認的域名
127.0.0.1 dev1.example.com
127.0.0.1 dev2.example.com
proxyTable 配置:
{
proxyTable: {
'/api1': 'rd1',
'/api2': 'rd2',
'/auth/xx': 'local',
'/other': 'http://example.com'
}
}
啟動命令:
npm run dev
遠程 1 台機器
host 配置:
1.1.1.1 dev1.example.com
1.1.1.1 dev2.example.com
whistle 配置:默認的域名
127.0.0.1 dev1.example.com
127.0.0.1 dev2.example.com
啟動命令:
npm run dev --rd=rd1 --focus
總結
細挖需求,可能還有更簡單的方式,在大部分情況下能夠減少代碼修改,
是 webpack 配置型的實現吧。當然,方式並不完美,尤其在 mac 下,
居然不能支持 --rd xx
這種形式,可以有類似的庫吧,后續可以做為深入的內容