nodejs搭建http(s)中转服务

1. 背景

本文主要介绍如何使用 nodejs 搭建 http(s)中转服务,本文所写内容仅以学习为目的,请勿作为其他用途。阅读本文之前,请先了解:

  1. 如何自建证书,可以参考这里
  2. 如何使用 nodejs 创建 http(s) 服务器
  3. 如何使用 nodejs 创建 tls 服务器
  4. http 协议的格式及请求类型
  5. http 请求数据体的格式(在转发请求的之前,需要解析获取完整的请求数据包,请尤其注意transfer-encoding: chunked格式的请求数据解析)
  6. http 响应数据的压缩方式(在向真实的客户端发送数据的时候,尽量按照原本的content-encodingtransfer-encoding所要求的数据格式将数据返回给客户端)

2. 实现步骤

2.1 http 中转思路

  1. 创建 http(s) 服务器作为 proxy server
  2. 监听 request 请求
  3. 通过 nodejs 网路请求相关的三方库(比如:axios、node-fetch、got 等)转发请求
  4. 获取到请求对应的数据之后将其返回给客户端即可

2.3 https 中转思路

  1. 创建 http(s) 服务器作为 proxy server
  2. 劫持所有请求到 proxy server
  3. 监听 connect 请求
  4. 解析 connect 请求,得到 host 和 port,使用 net 模块转发
  5. 将使用 net 转发时的 socket 与 connect 请求的客户端 socket 关联在一起即可实现请求中转

2.3 https 解密思路

  1. 创建 http(s) 服务器作为 proxy server
  2. 创建 tls 服务器,tls server
  3. 劫持所有请求到 proxy server
  4. 监听 connect 请求
  5. 将所有 connect 请求,全部发送到 tls server,请使用 net 模块转发,如果使用 tls 模块,将导致在 tls server 监听数据的时候,获取到的是 https 加密后的数据
  6. tls server 监听客户端转发过来的数据(需是解密之后的数据,使用 net 模块转发时,会自动解密),使用 nodejs 网路请求相关的三方库(比如:axios、node-fetch、got 等)转发请求到目标服务器
  7. 通过 nodejs 转发请求并取到数据之后,将其返回给 proxy server 的 socket,这样客户端便能拿到数据
  8. 客户端拿到了目标服务器的内容,然后正常显示其内容

2.4 局限

对于做了证书固定(SSL Pinnig)处理的 App,处理起来非常麻烦(对于此类情况,笔者放弃 https 解密)。关于安全相关的描述,请查阅下文第四节的内容。

3 常见问题

3.1 websocket

  • 请先学习websocket 协议
  • 当我们设置浏览器的 proxy 为我们自己写的服务时,ws 协议将通过 connect 请求转发,此时 http server 的upgrade事件将不会触发,因此在 connect 请求里正常转发请求(直连模式)即可实现 ws 的转发。

3.2 二次转发(仅 http/https)

3.2.1 http

只需要使用常规的代理方式转发请求即可。

3.2.2 https

3.2.2.1 仅转发
  • 请学习 connect 请求的格式。
  • 获取 connect 请求的数据
  • 与二级代理建立 socket 连接
  • 单独组装 connect 请求,将其发送给 targetSockect(与二级代理建立连接时的 socket)
  • 然后将真实的 clientSocket 和 targetSockect 关联起来(就是将 targetSockect 获取到的数据,原封不动转给 clientSocket)
  • 在 targetSockect 的 end 事件触发的时候,关闭 clientSocket,至此,该 https 请求便已完成转发
3.2.2.2 转发+数据解密

请参见“2.3 https 解密思路”部分的内容,只是在转发请求的时候,使用代理发送请求即可。此时请注意存在“2.4 局限”和“4. 关于安全”里所描述的问题。

3.3 内容被截断

请关注 content-length 响应头,在执行解密操作时,可以将响应头里的 content-length 字段删除,以避免出现 content-length 错误的问题。

3.4 关于响应数据的压缩

请学习 zlib 库的使用方法。

示例:

const zlib = require('zlib')

async function compressResonpseData (contentEncoding, respBuffer) {
    const NAME = 'compressResonpseData'
    return new Promise((resolve, reject) => {
      let zlibStream = null
      const respDataStream = Readable.from([respBuffer])
      let compressedData = Buffer.alloc(0)
      const zlibCallback = err => {
        if (err) return reject(`${NAME} 管道传送失败: ${err.message}`)
      }
      if (/\bdeflate\b/.test(contentEncoding)) {
        zlibStream = pipeline(
          respDataStream,
          zlib.createDeflate(),
          zlibCallback
        )
      } else if (/\bgzip\b/.test(contentEncoding)) {
        zlibStream = pipeline(respDataStream, zlib.createGzip(), zlibCallback)
      } else if (/\bbr\b/.test(contentEncoding)) {
        zlibStream = pipeline(
          respDataStream,
          zlib.createBrotliCompress(),
          zlibCallback
        )
      }
      if (zlibStream) {
        zlibStream
          .on('data', data => {
            compressedData = Buffer.concat([compressedData, data])
          })
          .on('end', () => {
            resolve(compressedData)
          })
      } else {
        resolve(respBuffer)
      }
    })
  }

3.5 关于二级转发的限制

3.5.1 以下的三方库不支持 proxy

以下内容将列表不支持 proxy 或者对 proxy 的支持存在问题的库。

3.5.2 proxy 的组合方案

3.6 https 解密时,如何处理重定向

在使用fetch转发请求时,redirect有两种模式,请使用manual模式。

3.7 仅当使用转发时,状态码 403

当不使用转发时,请求正常;使用服务转发,提示 403 时,请检查 request header,转发请求时请将request header里的host属性删除。

3.8 curl 可以正常请求,node-fetch 403

当使用 curl 时,请求正常;使用 node-fetch 转发,提示 403 时,请使用浏览器正常访问服务,检查是否是 http2(h2),node-fetch(笔者使用的是 v2.6.1)不支持 http2,可以使用node-libcurlfetch-h2got代替,此处推荐使用fetch-h2(因为它与 fetch 最接近,后续替换的成本最低,它也有局限性,具体请参见3.5.1)。

3.9 fetch-h2 Header guard error

类似于:Header guard error: Cannot set forbidden header for requests (dnt)之类的错误,请在转发的时候增加allowForbiddenHeaders: true处理该问题。

4. 关于安全

4.1 防解密

以下仅介绍方案名称,关于方案的具体细节请自行查找资料学习。

  • 证书固定 > 每次请求对比证书是否一致。
  • 公钥固定 > 每次请求对比公钥是否一致。
  • 双向认证
  • 证书签名校验
  • 使用自定义的加解密规则对数据做加解密处理

4.2 防篡改

  • 对数据做签名
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿祥_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值