node.js 中的 HTTPS 代理服务器

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8165570/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 14:47:12  来源:igfitidea点击:

HTTPS Proxy Server in node.js

httpnode.jssslhttpsproxy

提问by user1051478

I am developing a node.jsproxy server application and I want it to support HTTPand HTTPS(SSL)protocols (as server).

我正在开发一个node.js代理服务器应用程序,我希望它支持HTTPHTTPS(SSL)协议(作为服务器)。

I'm currently using node-http-proxylike this:

我目前正在使用node-http-proxy这样的:

const httpProxy = require('http-proxy'),
      http = require('http');

var server = httpProxy.createServer(9000, 'localhost', function(req, res, proxy) {
    console.log(req.url);
    proxy.proxyRequest(req, res);
});

http.createServer(function(req, res) {
    res.end('hello!');
}).listen(9000);

server.listen(8000);

I setup my browser to use HTTPproxy on localhost:8000and it works. I also want to catch HTTPSrequests (ie. setup my browser to use localhost:8000as HTTPSproxy as well and catch the requests in my application). Could you please help me how can I do that?

我将浏览器设置为使用HTTP代理,localhost:8000并且可以正常工作。我还想捕获HTTPS请求(即,将我的浏览器设置为也localhost:8000用作HTTPS代理并在我的应用程序中捕获请求)。你能帮我吗,我该怎么做?

PS:

附注

If I subscribe to upgradeevent of httpProxyserver object I can get the requests but I don't know how to forward the request and send response to client:

如果我订阅服务器对象的upgrade事件,httpProxy我可以获得请求,但我不知道如何转发请求并向客户端发送响应:

server.on('upgrade', function(req, socket, head) {
    console.log(req.url);
    // I don't know how to forward the request and send the response to client
});

Any helps would be appreciated.

任何帮助将不胜感激。

回答by y3sh

Solutions barely exist for this, and the documentation is poor at best for supporting bothon one server. The trick here is to understand that client proxy configurations may send https requests to an http proxy server. This is true for Firefox if you specify an HTTP proxy and then check "same for all protocols".

几乎没有解决方案,并且文档充其量只能在一台服务器上支持两者。这里的技巧是了解客户端代理配置可能会将 https 请求发送到 http 代理服务器。如果您指定 HTTP 代理,然后选中“所有协议都相同”,则这对于 Firefox 是正确的。

You can handle https connections sent to an HTTP server by listening for the "connect" event. Note that you won't have access to the response object on the connect event, only the socket and bodyhead. Data sent over this socket will remain encrypted to you as the proxy server.

您可以通过侦听“connect”事件来处理发送到 HTTP 服务器的 https 连接。请注意,您将无法访问连接事件上的响应对象,只能访问套接字和 bodyhead。作为代理服务器,通过此套接字发送的数据将保持加密状态。

In this solution, you don't have to make your own certificates, and you won't have certificate conflicts as a result. The traffic is simply proxied, not intercepted and rewritten with different certificates.

在此解决方案中,您不必制作自己的证书,因此不会出现证书冲突。流量只是被代理,不会被拦截并用不同的证书重写。

//  Install npm dependencies first
//  npm init
//  npm install --save [email protected]
//  npm install --save [email protected]

var httpProxy = require("http-proxy");
var http = require("http");
var url = require("url");
var net = require('net');

var server = http.createServer(function (req, res) {
  var urlObj = url.parse(req.url);
  var target = urlObj.protocol + "//" + urlObj.host;

  console.log("Proxy HTTP request for:", target);

  var proxy = httpProxy.createProxyServer({});
  proxy.on("error", function (err, req, res) {
    console.log("proxy error", err);
    res.end();
  });

  proxy.web(req, res, {target: target});
}).listen(8080);  //this is the port your clients will connect to

var regex_hostport = /^([^:]+)(:([0-9]+))?$/;

var getHostPortFromString = function (hostString, defaultPort) {
  var host = hostString;
  var port = defaultPort;

  var result = regex_hostport.exec(hostString);
  if (result != null) {
    host = result[1];
    if (result[2] != null) {
      port = result[3];
    }
  }

  return ( [host, port] );
};

server.addListener('connect', function (req, socket, bodyhead) {
  var hostPort = getHostPortFromString(req.url, 443);
  var hostDomain = hostPort[0];
  var port = parseInt(hostPort[1]);
  console.log("Proxying HTTPS request for:", hostDomain, port);

  var proxySocket = new net.Socket();
  proxySocket.connect(port, hostDomain, function () {
      proxySocket.write(bodyhead);
      socket.write("HTTP/" + req.httpVersion + " 200 Connection established\r\n\r\n");
    }
  );

  proxySocket.on('data', function (chunk) {
    socket.write(chunk);
  });

  proxySocket.on('end', function () {
    socket.end();
  });

  proxySocket.on('error', function () {
    socket.write("HTTP/" + req.httpVersion + " 500 Connection error\r\n\r\n");
    socket.end();
  });

  socket.on('data', function (chunk) {
    proxySocket.write(chunk);
  });

  socket.on('end', function () {
    proxySocket.end();
  });

  socket.on('error', function () {
    proxySocket.end();
  });

});

回答by Alexey Volodko

Here is my NO-dependencies solution (pure NodeJS system libraries):

这是我的无依赖项解决方案(纯 NodeJS 系统库):

const http = require('http')
const port = process.env.PORT || 9191
const net = require('net')
const url = require('url')

const requestHandler = (req, res) => { // discard all request to proxy server except HTTP/1.1 CONNECT method
  res.writeHead(405, {'Content-Type': 'text/plain'})
  res.end('Method not allowed')
}

const server = http.createServer(requestHandler)

const listener = server.listen(port, (err) => {
  if (err) {
    return console.error(err)
  }
  const info = listener.address()
  console.log(`Server is listening on address ${info.address} port ${info.port}`)
})

server.on('connect', (req, clientSocket, head) => { // listen only for HTTP/1.1 CONNECT method
  console.log(clientSocket.remoteAddress, clientSocket.remotePort, req.method, req.url)
  if (!req.headers['proxy-authorization']) { // here you can add check for any username/password, I just check that this header must exist!
    clientSocket.write([
      'HTTP/1.1 407 Proxy Authentication Required',
      'Proxy-Authenticate: Basic realm="proxy"',
      'Proxy-Connection: close',
    ].join('\r\n'))
    clientSocket.end('\r\n\r\n')  // empty body
    return
  }
  const {port, hostname} = url.parse(`//${req.url}`, false, true) // extract destination host and port from CONNECT request
  if (hostname && port) {
    const serverErrorHandler = (err) => {
      console.error(err.message)
      if (clientSocket) {
        clientSocket.end(`HTTP/1.1 500 ${err.message}\r\n`)
      }
    }
    const serverEndHandler = () => {
      if (clientSocket) {
        clientSocket.end(`HTTP/1.1 500 External Server End\r\n`)
      }
    }
    const serverSocket = net.connect(port, hostname) // connect to destination host and port
    const clientErrorHandler = (err) => {
      console.error(err.message)
      if (serverSocket) {
        serverSocket.end()
      }
    }
    const clientEndHandler = () => {
      if (serverSocket) {
        serverSocket.end()
      }
    }
    clientSocket.on('error', clientErrorHandler)
    clientSocket.on('end', clientEndHandler)
    serverSocket.on('error', serverErrorHandler)
    serverSocket.on('end', serverEndHandler)
    serverSocket.on('connect', () => {
      clientSocket.write([
        'HTTP/1.1 200 Connection Established',
        'Proxy-agent: Node-VPN',
      ].join('\r\n'))
      clientSocket.write('\r\n\r\n') // empty body
      // "blindly" (for performance) pipe client socket and destination socket between each other
      serverSocket.pipe(clientSocket, {end: false})
      clientSocket.pipe(serverSocket, {end: false})
    })
  } else {
    clientSocket.end('HTTP/1.1 400 Bad Request\r\n')
    clientSocket.destroy()
  }
})

I tested this code with Firefox Proxy Settings (it even asks for username and password!). I entered IP address of machine where this code is runned and 9191 port as you can see in the code. I also set "Use this proxy server for all protocols". I run this code locally and on VPS - in both cases works!

我使用 Firefox 代理设置测试了这段代码(它甚至要求输入用户名和密码!)。我输入了运行此代码的机器的 IP 地址和 9191 端口,如您在代码中所见。我还设置了“将此代理服务器用于所有协议”。我在本地和 VPS 上运行此代码 - 在这两种情况下都有效!

You can test your NodeJS proxy with curl:

您可以使用 curl 测试您的 NodeJS 代理:

curl -x http://username:[email protected]:9191 https://www.google.com/

回答by ncabral

I have created a http/https proxy with the aid of the http-proxymodule: https://gist.github.com/ncthis/6863947

我在http-proxy模块的帮助下创建了一个 http/https 代理:https: //gist.github.com/ncthis/6863947

Code as of now:

目前的代码:

var fs = require('fs'),
  http = require('http'),
  https = require('https'),
  httpProxy = require('http-proxy');

var isHttps = true; // do you want a https proxy?

var options = {
  https: {
    key: fs.readFileSync('key.pem'),
    cert: fs.readFileSync('key-cert.pem')
  }
};

// this is the target server
var proxy = new httpProxy.HttpProxy({
  target: {
    host: '127.0.0.1',
    port: 8080
  }
});

if (isHttps)
  https.createServer(options.https, function(req, res) {
    console.log('Proxying https request at %s', new Date());
    proxy.proxyRequest(req, res);
  }).listen(443, function(err) {
    if (err)
      console.log('Error serving https proxy request: %s', req);

    console.log('Created https proxy. Forwarding requests from %s to %s:%s', '443', proxy.target.host, proxy.target.port);
  });
else
  http.createServer(options.https, function(req, res) {
    console.log('Proxying http request at %s', new Date());
    console.log(req);
    proxy.proxyRequest(req, res);
  }).listen(80, function(err) {
    if (err)
      console.log('Error serving http proxy request: %s', req);

    console.log('Created http proxy. Forwarding requests from %s to %s:%s', '80', proxy.target.host, proxy.target.port);
  });

回答by Michael Brundage

The node-http-proxy docs contain examples of this. Look for "Proxying to HTTPS from HTTPS" at https://github.com/nodejitsu/node-http-proxyThe configuration process is slightly different in every browser. Some have the option to use your proxy settings for all protocols; some you need to configure the SSL proxy separately.

node-http-proxy 文档包含这方面的示例。在https://github.com/nodejitsu/node-http-proxy查找“从 HTTPS 代理到 HTTPS” 每个浏览器的配置过程略有不同。有些可以选择将您的代理设置用于所有协议;有些需要单独配置 SSL 代理。