浅析Node.js中http代理的实现

文章目录

前言

起因是狼书卷1中的API Proxy例子,代码如下:

js 复制代码
const http = require('http')
const fs = require('fs')

const app = http.createServer((req, res) => {
    if ('/remote' === req.url) {
        res.writeHead(200, { 'Content-Type': 'text/plain' })
        return res.end('Hello remote page!\n')
    } else {
        proxy(req, res)
    }
})


function proxy(req, res) {
    const options = {
        host: req.host,
        port: 3000,
        path: 'remote',
        method: req.method, //原文是GET
        headers: req.headers,
        agent: false
    }

    let httpProxy = http.request(options, (response) => {
        response.pipe(res)
    })

    req.pipe(httpProxy)
}

app.listen(3000, function () {
    const PORT = app.address().port
    console.log(`Server running at http://127.0.0.1:${PORT}/}`)
})

例子没有什么问题,很好的实现了一个简单的http代理. 但读代码的过程中,还是发现了一些可以略做深究的点,记录如下。

ReadableStream和WritableStream

我们从代码说起,这个proxy的核心方法是:

js 复制代码
function proxy(req, res) {
    const options = {
        host: req.host,
        port: 3000,
        path: 'remote',
        method: req.method, //原文是GET
        headers: req.headers,
        agent: false
    }

    let httpProxy = http.request(options, (response) => {
        response.pipe(res)
    })

    req.pipe(httpProxy)
}

这个方法中,创建了转发请求需要的options, 其中包含目标服务器的信息,请求地址,以及请求头headers.
proxy方法的两个参数分别是req,res. req是一个Readable Stream,res是一个Writable Stream. 这里要注意,readable还是writable是在server 的角度来看的:server需要从req中读取请求信息,把返回的内容写入到res中.

在整个代理的过程中,依靠的是pipe来连接,pipe实现的功能是连接Readable Stream到Writable Stream,反之亦然.
req和res的读写属性我们刚才分析了,现在来看httpProxy和方法回调中的response,但是这时,要从client 角度来看了,response是远程服务返回的信息,是一个ReadableStream. httpProxy是http.request返回的值,类型是http.ClientRequest, 继承自OutgoingMessage,也是一个Writable stream.

我们整理一下

Object Read/Write
req Readable
res Writable
httpProxy Writable
response Readable

到这里,流程非常清楚了

req(readable) ⇒ httpProxy(writable)
response(readable) ⇒ res(writable)

整个代理的流程厘清了.

req.pipe

读代码的时候,还想到了一个问题, httpProxy = http.request 这一行,不是已经发起请求吗, 为什么最后还要req.pipe?

这里涉及到http.request的请求过程,在调用这个方法的时候,实际上只是发出了请求头,此时并不能认为这个请求已经完成,例如POST请求就可能会要写入其它的数据到stream中。所以,这里req.pipe是将原始请求的流定向到了代理请求中,确保所有数据都写入。

用一个简单的例子就能看清楚

js 复制代码
const http = require('http');

// POST 请求选项
const options = {
  hostname: 'www.example.com',
  port: 80,
  path: '/submit-form',
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
};

// 发送POST请求
const req = http.request(options, (res) => {
  let data = '';

  res.on('data', (chunk) => {
    data += chunk;
  });

  res.on('end', () => {
    console.log(data);
  });
});

// 发送请求数据
req.write('key1=value1&key2=value2');
req.end();

例子中,req 就要通过write 进行请求数据的写入.

所以req.pipe 是必须的,因为需要保证请求的stream中所有数据被转发.

小结

本文简单分析了Node.js实现proxy的一些容易忽略的知识点,涉及可读可写流,以及http请求的发起过程. 欢迎交流。

相关推荐
步行cgn32 分钟前
HTTP 请求报文结构详解:请求行、请求头、请求体各自的作用
网络·网络协议·http
YIAN43 分钟前
RAG 学到最后,我真正学会的不是“检索”,而是让系统知道自己缺什么
langchain·node.js·llm
用户11977079480731 小时前
GitHub 电商开源项目分享:用 sharp 批量出饰品主图,试了裁剪、细节图和色彩配置文件三个坑
前端·node.js
donecoding1 小时前
pnpm 配好了,为什么执行的还是另一份?命令入口与运行时排查
node.js·前端工程化
江屿风1 小时前
【Plain Language Large Model】【从浏览器到全栈:一课精通Node.js三剑客】流食般投喂
笔记·前端框架·npm·node.js·js·npx
SomeOtherTime5 小时前
node.js基础(AI回答)
node.js
明天也要努力学习13 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
cpolar技术支持18 小时前
网关加 gzip,同时开启 chunked,下载回来的包解不开:把两层编码的解压顺序与长度核对做成只读页
python·http·网络编程·socket·内网穿透
雨季mo浅忆1 天前
记录Node.js安全漏洞及其内置Brotli解码器问题
node.js·安全性测试
tommyjiatong1 天前
资安底座连环雷:从 PHP2Shell 到 Node2Shell
node.js·php