抽絲剝繭:詳述一次DevServer Proxy配置無效問題的細緻排查過程

一颗冰淇淋發表於2024-05-12

事情的起因是這樣的,在一個已上線的專案中,其中一個包含登入和獲取選單的介面因響應時間較長,後端讓我嘗試未經服務轉發的另一域名下的新介面,舊介面允許跨域請求,但新介面不允許本地訪問(只允許釋出測試/生產的域名訪問)。

問題

那麼問題來了,本地環境該如何成功訪問到新的介面並驗證業務功能是否生效呢?

嘗試過程

我首先就想到了直接在 webpack 專案中配置 devServer,並且修改介面地址(為了安全隱私,隱去公司實際域名,使用 xxxxx 來替代。)

devServer: {
  proxy: {
    '/': {
      target: 'https://xxxxx.cn',
      pathRewrite: {
        '/proxyApi': '',
      },
      changeOrigin: true,
    },
  },
} 

但返回的介面提示【登入態無效】,這下起碼不跨域了!本來以為已經代理成功,只需要找到後端看看報錯即可!

但後端反饋這個報錯是因為請求頭沒有攜帶指定引數,他也查不到該請求的詳細資訊。這時候我又開始有疑問了,明明檢視請求頭是有的呀。!

疑問

在 chrome 瀏覽器上看到的請求地址並不是後端提供的真實介面請求地址,而是加了我代理的欄位。在響應體上我也沒有找到 location 等欄位反饋到真實的請求介面。

此時的我懷疑,代理是真的生效了嗎,我請求的介面是真實的後端介面嗎?開始驗證 devServer 的 proxy 是否執行。在 proxy 處配置請求前後輸出的函式,結果發現 onProxyReq 和 onProxyRes 都沒有執行。

proxy: {
  '/proxyApi': {
    target: 'https://xxxxx.cn',
    pathRewrite: {
      '/proxyApi': '',
    },
    changeOrigin: true,
    onProxyReq(proxyReq, req, res) {
      console.log('>>>請求', req);
    },
    onProxyRes(proxyRes, req, res) {
      // 響應的鉤子函式
      console.log('>>>響應', res);
    },
  },
},

所以此時猜測是不是整個 devServer 都沒有生效,但如何證明它沒有生效呢?

證實

目前代理後端域名不受我們控制,我無從知曉它是否傳送到後端伺服器上,所以我打算自己用 nodejs 開啟一個服務,開啟服務的方式很簡單,使用核心模組 https 幾行程式碼搞定。

const http = require("http");

const server = http.createServer((req, res) => {
  console.log(">>req", req.url, req.rawHeaders );
  res.end("hello");
});

server.listen("3002", () => {
  console.log("3002埠啟動了");
});

透過 node 啟動服務後,首先驗證是否可攔截請求,直接透過瀏覽器視窗 輸入 localhost:3002

哎~ 服務啟動了,頁面也得到的響應,伺服器能獲取到剛剛 get 請求的資料

此時將專案中 proxy 的配置改為 3002 埠的服務,再次執行業務邏輯程式碼傳送請求,發現此時3002埠啟動的服務控制檯空空如也!也就是說,它根本沒有攔截到該請求。

proxy: {
  '/proxyApi': {
    target: 'http://localhost:3002',
},

猜想是否因為專案裡的介面請求工具導致無法攔截,於是直接在頁面上使用 fetch 傳送請求,此時發現 3002 埠的服務仍然沒有接收到請求。

fetch('https://xxxxx.cn/proxyApi/yyyyy/operateTargetNew')

本來以為是不是 proxy 字元匹配的問題,因為 /proxyApi 標識出現在整個url 中間,試圖修改為正規表示式 "**/proxyApi/*",也是無效的

proxy: {
  '**/proxyApi/*': {
},

再次嘗試

這時候我意識到一個問題,帶有域名的介面訪問好像不行,那我直接去掉域名呢?

此時直接使用 fetch 請求不包含域名的介面地址

fetch('/proxyApi/yyyyy/operateTargetNew')

這個時候,終於看到了問題即將解決的曙光!呼叫介面成功獲取到了 3002 埠返回的響應

也能在本地的 3002 埠服務上獲取到請求的詳細資訊。

撥開雲霧

查詢資料發現果然是介面地址的原因。Webpack DevServer的proxy配置主要用於開發環境中,針對的是由本地DevServer發出的API請求。

當你在前端程式碼中傳送請求時,通常會使用相對路徑(如/api/xxx/yyy),這樣它們就會被髮送到當前頁面所在的主機和埠,也就是Webpack DevServer。

這時,DevServer的proxy設定可以將請求轉發到配置的後端伺服器。

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    proxy: {
      '/api': {
        target: 'http://your-backend-server.com',
        changeOrigin: true,
      },
    },
  },
};

現在,如果你傳送一個請求到/api/xxx/yyy,DevServer會將它代理到http://your-backend-server.com/api/xxx/yyy。

然而,如果你在前端程式碼中直接使用了完整的URL(即包含域名https://www.xxxx.com/api/xxx/yyy),就繞過了Webpack DevServer,請求直接發往該完整URL對應的伺服器。DevServer的proxy配置不會和這個請求互動,因此無法將它代理到你配置的目標伺服器。

請求改造

於是再改回需要代理的介面,並對專案邏輯進行一些改造,因為預設的網路庫會拼接url,這裡做一個判斷,將需要代理的域名和代理的字元作為一組值儲存起來。

如果匹配中需要代理的需求,並用字首來替換。

// 本地環境,需要將代理的介面剔除域名,並拼接代理字首
  if (process.env.NODE_ENV === 'development') {
    const proxyObj = {
      'https://xxxx.cn': '/proxyApi',
    };
    const proxyKeys = Object.keys(proxyObj);
    for (let i = 0; i < proxyKeys.length; i++) {
      const host = proxyKeys[i];
      if (option.url.includes(host)) {
        const prefix = proxyObj[host];
        option.url = option.url.replace(host, prefix);
      }
    }
  }

這樣就可以將介面請求拼接為 https://xxxx.con 域名的全部替換為指定字首,這樣這部分的請求就都會走代理。!

很慚愧,雖然早就知道 webpack 的 proxy 配置解決本地跨域問題,但確實很少自己去配置,一般是後端解決掉跨域問題或者專案裡的自帶裡處理方案,所以真正到自己配置的時候多少有點迷糊了。

相關文章