【问题标题】:HTTPS Proxy Server in node.jsnode.js 中的 HTTPS 代理服务器
【发布时间】:2011-12-31 05:20:15
【问题描述】:

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

我目前正在像这样使用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);

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

PS

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

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
});

任何帮助将不胜感激。

【问题讨论】:

    标签: http node.js ssl https proxy


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      截至目前的代码:

      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);
        });
      

      【讨论】:

      • 要点的链接已过时。我已将其更新为gist.github.com/ncthis/6863947
      • @david 又过时了
      • httpProxy.HttpProxy is not a function
      • 如何添加ssl证书?
      【解决方案3】:

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

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

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

      //  Install npm dependencies first
      //  npm init
      //  npm install --save url@0.10.3
      //  npm install --save http-proxy@1.11.1
      
      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();
        });
      
      });
      

      【讨论】:

      • 爱你的榜样。我想知道如何增强它以处理 websocket 升级连接
      • @y3sh 你能在这里检查我的问题stackoverflow.com/questions/43908640/…吗?我认为您的示例可以帮助我,但我不知道如何实现它..
      • 如何添加用户和密码验证,以便只转发来自我的请求和我的动态 IP 地址?
      • @y3sh 你应该写一个 npm 包
      • 为什么“连接”监听器只为 https 触发?根据文档,connect 是 http 上的事件之一,所以我不确定这是如何划分协议的。
      【解决方案4】:

      这是我的无依赖解决方案(纯 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()
        }
      })
      

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

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

      curl -x http://username:password@127.0.0.1:9191 https://www.google.com/
      

      【讨论】:

      • 通过这个你可以知道一些信息,比如最终url的主机名和端口,但不能知道完整的url(带有搜索路径)和请求方法。知道如何获取这些信息吗?
      • 什么意思?你想知道目的地查询参数还是什么?
      • 是否可以从服务器读取响应数据?我试图通过 socketServer 监听数据事件,但结果不可读。我只是得到这样的东西:buړp��������3&
      • 只有 HTTP 才有可能。 HTTPS 是不可能的,因为它是“加密科学”!在这种情况下,您的 NodeJS 代理将是“中间人”。而“加密科学”主要是针对任何“中间人”的防御浏览器-服务器连接......
      • 这太棒了 Алексей,我能问一下您是如何/在哪里学习如何编写代理代码的吗?我有兴趣扩展它来做一个正向代理。
      猜你喜欢
      • 2013-04-18
      • 1970-01-01
      • 2015-09-19
      • 1970-01-01
      • 2016-01-14
      • 1970-01-01
      • 1970-01-01
      • 2020-03-23
      • 2012-06-21
      相关资源
      最近更新 更多