源碼解讀系列之 node-http-proxy

目的

在平常的開發中,常常使用 Nginx 做爲反向代理服務器進行 http 請求代理。既然 Nginx 能夠進行請求反向代理 NodeJs 確定也是能夠的。開源社區上,比較出名的項目就是 node-http-proxy 了,嘗試對該項目進行一次源碼解讀,藉此機會了解一下 NodeJs 代理過程當中的一些細節javascript

版本

1.17.0java

程序結構

解釋

  • http-proxy:程序入口,對外輸出三個方法:createProxyServer,createServer,createProxy;三個方法只是名稱不同,實際內容統一是內部的 createProxyServer 方法,createProxyServer 接收 options 配置並建立 proxyServer 對象
  • Index:主程序,對外輸出 createProxyServer 方法
  • web-incoming:負責代理 http 請求
  • ws-incoming:負責代理 web-socker 請求
  • web-outgoing:負責封裝代理響應(http response)

模塊概述

http-proxynode

定義關鍵方法 createRightProxy,該方法功能以下:web

  • 返回 nodejs http 和 https 模塊的請求回調;
  • 根據接收的請求參數,獲取對應 web 或者 ws 的處理器隊列(passes),順序運行全部處理器;

聲明 ProxyServer 類,ProxyServer 類繼承 eventemitter3 的功能,並自定義方法:正則表達式

  • constructor:初始化配置,經過 createRightProxy 方法 分別爲 web 和 ws 兩種類型請求初始化處理器(pass),默認的處理器由 web-incoming,ws-incoming 兩個文件提供;服務器

  • onError:當事件監聽器獲取錯誤事件(error)時,向外拋出錯誤;websocket

  • listen:經過原生模塊 http 和 https 開啓 server 監聽特定端口,當接收的請求方法名爲 upgrade 時,轉爲 websocket 處理請求;併發

  • close:關閉 server;socket

  • before:針對 處理器(pass)添加處理器運行前的回調函數函數

  • after:針對 處理器(pass)添加處理器運行後的回調函數

關鍵流程:

web-incomingweb-outgoing

web-incoming 關鍵方法爲 stream,功能概述爲 獲取原始請求、響應對象 req 和 res 後,建立對應的 proxyReq 對象,經過 proxyReq 對象轉發請求達到代理的效果,並在 proxyReq 的請求響應 proxyRes 返回時,經過 web-outgoing 封裝方法更新 res 的 header,最後將 proxyRes 的結果內容經過 pipe 方法更新到 res 上

關鍵流程以下:

請求響應關鍵流程:

關鍵代碼:

proxyReq.on('response', function(proxyRes) {
  if(!res.headersSent && !options.selfHandleResponse) {
    // 複製 res header
  }
  if (!res.finished) {
    proxyRes.pipe(res);
  }
});
複製代碼

ws-incoming

該文件主要負責對 web-socket 進行代理

關鍵方法爲 stream,經過 req 建立 proxyReq,proxyReq 向目標服務發出 http 請求(GET),等待 upgrade 響應,獲得 upgrade 響應後,便可得到代理套接字 proxySocket,原套接字寫回轉換協議響應給客戶端,最後經過 pipe 方法將 socket 和 proxySocket 的內容綁定起來

關鍵代碼:

  • 原套接字 socket 寫回轉換協議響應給客戶端
socket.write(createHttpHeader('HTTP/1.1 101 Switching Protocols', proxyRes.headers));
複製代碼
  • 經過 pipe 方法將 socket 和 proxySocket 的數據流進行綁定
proxySocket.pipe(socket).pipe(proxySocket);
複製代碼

關鍵細節

checkMethodAndHeader

checkMethodAndHeader : function checkMethodAndHeader(req, socket) {
    if (req.method !== 'GET' || !req.headers.upgrade) {
      socket.destroy();
      return true;
    }

    if (req.headers.upgrade.toLowerCase() !== 'websocket') {
      socket.destroy();
      return true;
    }
  }
複製代碼
  • 檢查 ws 請求是否正確:websocket 的請求必須爲 GET 方法並且 header 中包含 "upgrade:websocket"

XHeaders

XHeaders: function XHeaders(req, res, options) {
    if(!options.xfwd) return;

    var encrypted = req.isSpdy || common.hasEncryptedConnection(req);
    var values = {
      for  : req.connection.remoteAddress || req.socket.remoteAddress,
      port : common.getPort(req),
      proto: encrypted ? 'https' : 'http'
    };

    ['for', 'port', 'proto'].forEach(function(header) {
      req.headers['x-forwarded-' + header] =
        (req.headers['x-forwarded-' + header] || '') +
        (req.headers['x-forwarded-' + header] ? ',' : '') +
        values[header];
    });

    req.headers['x-forwarded-host'] = req.headers['x-forwarded-host'] || req.headers['host'] || '';
  }
複製代碼
  • 根據要求,爲代理請求頭帶上 x-forward-* 系列,以便服務器能夠知道請求被代理和請求來源
  • x-forwarded-for 來源於原始請求 req 的 connection.remoteAddress 或者 socket.remoteAddress
  • x-forwarded-port 來源於 req 的 header 的 host 解析,經過正則表達式(/:(\d+)/)獲取對應的 port 數值
  • x-forwarded-proto 來源於 req 的判斷,若是爲 enctypted 則爲 https 協議,不然爲 http 協議,判斷標準以下:
req.connection.encrypted
req.connection.pair
req.isSpdy
複製代碼

​ 上述三個條件任一成立,判斷爲 https 協議

  • x-forwarded-host 來源於 req 頭部的 x-forwarded-host 或者 host

createHttpHeaders

var createHttpHeader = function(line, headers) {
      return Object.keys(headers).reduce(function (head, key) {
        var value = headers[key];

        if (!Array.isArray(value)) {
          head.push(key + ': ' + value);
          return head;
        }

        for (var i = 0; i < value.length; i++) {
          head.push(key + ': ' + value[i]);
        }
        return head;
      }, [line])
      .join('\r\n') + '\r\n\r\n';
    }
複製代碼
  • 若是想直接經過 socket 寫 stream 的方式輸出 header,須要該方法,組合成字符串,而後 socket.write 方法調用的過程當中,會默認將字符串經過 utf8 編碼方式編碼成二進制流併發送出去

總結

  1. 響應的 pipe 方法使用是關鍵
  2. 請求代理轉發過程當中 請求頭部 按照設計好的規則進行復制和定製設置
  3. websocket 的代理,須要先經過一次 http 代理請求,在得到 upgrade 響應後,將 proxySocket 和 socket 進行 pipe 對接操做
相關文章
相關標籤/搜索