一種方便的跨域開發解決方案

如今愈來愈多的 Web 項目都採起先後端分離的開發方式,也就是在開發過程當中前端工程運行在一個 node server 上,同時提供 REST API 的後端工程做爲獨立的服務運行在另外一個 server 上,這樣先後端經過 HTTP 請求進行通訊的時候就會遇到跨域的問題,跨域是怎麼一回事你們應該都瞭解,這裏就再也不贅述。以往解決跨域問題比較經常使用的方法就是給服務端配置 CORS,可是這種方式會帶來一些不便:

  1. 要修改後端代碼,若是後端項目運行在本身機器上還好,若是是運行在別的服務器上,甚至是其餘團隊在維護,那麼修改一段代碼就會比較麻煩,而且還要確保這些代碼不會被髮布到生產環境中去。
  2. 每次啓動瀏覽器的時候要輸入一長串代碼來關閉瀏覽器的安全策略,好比 Chrome 瀏覽器的命令以下: open -a /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir 不只麻煩,萬一一不當心在這種模式下訪問了一些敏感的數據,還會帶來安全隱患。
  3. 開發模式下前端調接口要帶上測試後端服務器的 URL,所以發佈到生產環境以前還要把它去掉。

總之就是比較麻煩,體會不到那種脫了褲子就上,完事提上褲子就走的爽快感,我說的是上廁所。因此今天就是要介紹一種更加簡單安全的解決方案,同時咱們會深刻去了解其中的原理是什麼。html

首先,用 create-react-app 建立一個前端項目,假如你的前端項目運行的地址是 http://localhost:3000,與此同時提供 API 的後端項目運行的地址是 http://localhost:4000,你要作的只是在前端工程的 package.json 文件中添加這樣一行配置:前端

"proxy": "http://localhost:4000"
複製代碼

而後你就會神奇地發現,從前端頁面發出的 HTTP 請求,雖然訪問的依然是 3000 端口,可是會被自動轉發到 4000 端口的後端服務器並獲得正確的響應,於此同時訪問頁面的請求卻不會被轉發,依然可以被前端路由捕獲,這樣咱們就徹底不須要再考慮如何處理跨域的問題了。問題是解決了,可是又出現了 2 個問題縈繞在個人心中:node

  1. package.json 中的 proxy 參數是做用在什麼地方的?
  2. 是怎麼樣作到把訪問頁面的請求和訪問 REST API 的請求區分開的?

帶着這樣的疑問咱們一塊兒去看看 create-react-app 的源碼是怎樣寫的,首先在前端項目中的 package.json 裏咱們能看到,項目啓動執行的腳本是 react-script start,因此咱們打開文件 create-react-app/packages/react-scripts/scripts/start.js(爲什麼直接能定位到這個文件,以及 react-script 這個命令是如何註冊的,屬於其餘知識點,本文不展開說明,有疑問的童鞋能夠去這裏 學習一個),咱們看到有如下代碼:react

// Load proxy config
const proxySetting = require(paths.appPackageJson).proxy;
const proxyConfig = prepareProxy(proxySetting, paths.appPublic);
// Serve webpack assets generated by the compiler over a web sever.
const serverConfig = createDevServerConfig(
    proxyConfig,
    urls.lanUrlForConfig
);
const devServer = new WebpackDevServer(compiler, serverConfig);
複製代碼

此處的 paths.appPackageJson 的聲明在 create-react-app/packages/react-scripts/config/paths.js 中:webpack

module.exports = {
  appPackageJson: resolveApp('package.json'),
};
複製代碼

所以咱們就知道,這裏的 proxySetting 就是咱們以前在前端工程的 package.json 中定義的 proxy 的值,而後咱們看到,proxySetting 被用來生成了 serverConfig,最終 serverConfig 做爲配置參數建立了 WebpackDevServer 實例。webpack-dev-server 是一個用於啓動 webpack 的測試服務器,而且提供了諸如 HMR 等方便開發的功能,所以咱們就得出第一個結論:前端工程的 package.json 中定義的 proxy 值,是做用於 WebpackDevServer,最終經過 WebpackDevServer 進行的轉發git

讓咱們繼續試圖解答第二個問題——是怎麼樣作到把訪問頁面的請求和訪問 REST API 的請求區分開的?咱們看到 proxySetting 首先是被傳入 prepareProxy 方法獲得 proxyConfig,而後在 createDevServerConfig 方法中返回了一個對象,而且對象的 proxy 字段的值爲 proxyConfig,最終該對象就是 webpack-dev-server 的配置項,在 webpack-dev-server 文檔 中能夠看到 proxy 的做用就是作一層代理,把從頁面來的請求轉發到另外一個地址,所以關鍵就在於 proxyConfig 的配置是怎麼樣的,因而目光轉移到 prepareProxy 方法,prepareProxy 方法的定義在 create-react-app/packages/react-dev-utils/WebpackDevServerUtils.js 中,在這裏咱們能夠看到首先是對 proxy 進行了類型和格式的檢測,而後若是 proxy 是一個格式正確的字符串,就返回一個只有一個對象元素的數組,在這個對象中的 context 字段中出現了以下的判斷:github

context: function(pathname, req) {
    return (
        req.method !== 'GET' ||
        (mayProxy(pathname) &&
         req.headers.accept &&
         req.headers.accept.indexOf('text/html') === -1)
    );
}
複製代碼

在這裏咱們看到有對 req.headers.accept 進行判斷,req.headers.accept 用於表示瀏覽器經過此次 HTTP 請求但願獲取到的內容類型,所以若是 accept 中帶有 text/html 則說明本次請求獲取的是一個 document,所以就不該該被轉發到後端,這一堆判斷邏輯用一幅圖表示出來以下:web

在這裏 context 的含義在 webpack-dev-server 文檔中是找不到的,它的說明出如今 http-proxy-middleware 中,context 支持傳入一個 function 用於自定義轉發的邏輯,只在返回值爲 true 時才轉發請求, 所以該代理將只會轉發 ajax 或者 fetch 發出的 HTTP 請求 。

除了文中提到的這種最簡單的配置,webpack-dev-server 的 proxy 還支持多種配置方式以同時知足多種代理規則,感興趣的同窗能夠去文檔裏面瞭解更多細節。ajax

相關文章
相關標籤/搜索