Stories

Detail Return Return

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

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

問題

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

嘗試過程

我首先就想到了直接在 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 啓動服務後,首先驗證是否可攔截請求,直接通過瀏覽器窗口 輸入

哎~ 服務啓動了,頁面也得到的響應,服務器能獲取到剛剛 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 配置解決本地跨域問題,但確實很少自己去配置,一般是後端解決掉跨域問題或者項目裏的自帶裏處理方案,所以真正到自己配置的時候多少有點迷糊了。

user avatar anchen_5c17815319fb5 Avatar esunr Avatar evans_bo Avatar
Favorites 3 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.