源码解读系列之 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 对接操做
相关文章
相关标签/搜索