带有自定义 Http(s) 代理和 Connect.js 中间件的 Node.js 代理

Posted

技术标签:

【中文标题】带有自定义 Http(s) 代理和 Connect.js 中间件的 Node.js 代理【英文标题】:Node.js Proxy with custom Http(s) Agent and Connect.js Middleware 【发布时间】:2018-01-01 21:13:36 【问题描述】:

我在 Node 中建立了一个代理服务器,它需要能够通过 tls 传输 https 请求并且一切正常。使用以下两个包非常容易设置:proxy、https-proxy-agent。我的问题是我正在尝试使用connect 作为中间件层来捕获 HAR 文件,但出现以下错误:

_http_outgoing.js:357
throw new Error('Can\'t set headers after they are sent.');
^
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (_http_outgoing.js:357:11)
at ServerResponse.writeHead (_http_server.js:180:21)
at ClientRequest.<anonymous> (/.../node_modules/proxy/proxy.js:233:11)
at emitOne (events.js:96:13)
at ClientRequest.emit (events.js:188:7)
at HTTPParser.parserOnIncomingClient [as onIncoming] (_http_client.js:473:21)
at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
at Socket.socketOnData (_http_client.js:362:20)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)

如下所示,它似乎只在我使用connect 并通过我的本地浏览器进行代理时发生(此代理实际上与BrowserStackLocal 一起使用)。当我从本地机器浏览器以外的任何地方通过代理时,就好像它甚至不知道中间件的存在。

所以基本上,我只需要让连接在这种情况下工作,我不确定我是否需要暂停和恢复,或者什么......任何想法将不胜感激。基本代码如下:

const path = require('path');
const http = require('http');
const proxy = require('proxy');
const Agent = require('https-proxy-agent');
const connect = require('connect');
const har = require('./har');

const middleware = connect();

middleware.use(har(
  harOutputDir: path.resolve(process.cwd(), 'har/')
));

const server = proxy(http.createServer(middleware));
server.agent = new Agent('http://localhost:8081');

server.listen(8081)

谢谢!

编辑:请注意:har 中间件根本不修改标头。

【问题讨论】:

./har 中间件是否在响应对象上发送数据?如果是这样,将设置一个标题,并且第二个 setHeader 发生错误 @Myonara 不——它所做的只是从响应中读取数据,“next()”即使没有“./har”中间件也存在问题。 在客户端你能识别,发送的是什么而不是第二个标头答案? @Myonara 我不确定你的意思......似乎连接或代理服务器在对方有机会做任何事情之前正在发送标头。如果我不得不猜测,我会说代理首先发送它的标头,因为它在连接到位时失败。我想知道我是否可以暂停响应并让它等待连接到它的东西。我正在尝试这样做,而无需修改代理并提交 PR。谢谢,迈克 【参考方案1】:

proxy 有一段时间没有维护了。构建没有通过,最后一次提交没有通过测试。您提出的堆栈跟踪的来源来自here in Line 233 - 有缺陷的库代码。

编写类似的代理很简单。以下代码说明了如何创建一个。

const http = require('http');
const urlP = require('url');

const proxiedServer = 'http://localhost:8888';

// Proxy server
http.createServer((req, res) => 
  console.log(`Proxy: Got $req.url`);

  const _r = http.request(
    Object.assign(
      ,
      urlP.parse(proxiedServer),
      
        method: req.method,
        path: req.url
      
    ),
    _res => 
      res.writeHead(_res.statusCode, _res.headers);
      _res.pipe(res);
    
  );

  req.pipe(_r);

).listen(3124, () => 
  console.log("Listening on 3124");
);


// Regular server. Could be Express
http.createServer((req, res) => 
  console.log('Proxied server: ', req.url);
  let b = '';
  req.on('data', c => 
    b += c;
  );
  req.on('end', () => 
    console.log('Proxied server: ', b);
  );
  res.writeHead(200);
  res.end('ok');
).listen(8888, () => 
  console.log('proxied server listening on 8888');
);

使用您自己的自定义代理的代码如下所示:

const urlP = require('url');
const path = require('path');
const http = require('http');
const connect = require('connect');
const har = require('./har');

const proxiedServer = 'http://localhost:8888';

// Proxy server
http.createServer((req, res) => 
  console.log(`Proxy: Got $req.url`);

  const _r = http.request(
    Object.assign(
      ,
      urlP.parse(proxiedServer),
      
        method: req.method,
        path: req.url
      
    ),
    _res => 
      res.writeHead(_res.statusCode, _res.headers);
      _res.pipe(res);
    
  );

  req.pipe(_r);

).listen(3124, () => 
  console.log("Listening on 3124");
);

const middleware = connect();
middleware.use(har(
  harOutputDir: path.resolve(process.cwd(), 'har/')
));

middleware.use((req, res) => 
  console.log('Proxied server: ', req.url);
  let b = '';
  req.on('data', c => 
    b += c;
  );
  req.on('end', () => 
    console.log('Proxied server: ', b);
  );
  res.writeHead(200);
  res.end('ok');
);


http.createServer(middleware).listen(8888, () => 
  console.log('proxied server listening on 8888');
);

【讨论】:

好的-感谢您的帮助...我实际上是开始编写自己的,但在通过 http 隧道传输 https 时遇到了太多问题。我发现代理是解决该问题的解决方案,而不是尝试为通过代理的任何内容创建证书。感谢您的帮助! 答案中的 sn-p 有帮助吗? 是的,我实际上已经调查过了,并打算稍微清理一下并提交公关,但最后只是按照你的建议开始写我自己的……因为涉及更多我需要代理并在必要时切换到 TLS,但希望它可以帮助某人。我本质上是在编写一个 javascript 解决方案来替换。 BrowserMobb Procy。感谢您的帮助!

以上是关于带有自定义 Http(s) 代理和 Connect.js 中间件的 Node.js 代理的主要内容,如果未能解决你的问题,请参考以下文章

带有 @nguniversal 的 Angular s-s-r 和用于 PostCSS 支持的自定义 webpack

带有 Celery 的 redis 键的自定义前缀

CloudWatch 自定义 EC2 内存指标和带有 AutoScaling 策略的警报

Nginx正向代理配置

HTTP代理协议的CONNECT方法

Nginx Ingress TCP代理实现