javascript - 在建立TLS连接时,如何传递其他信息?

标签 javascript node.js http

我已经创建了一个简单的http代理,它使用HTTP CONNECT method执行http隧道。

const http = require('http');
const https = require('https');
const pem = require('pem');
const net = require('net');
const util = require('util');

const createHttpsServer = (callback) => {
  pem.createCertificate({
    days: 365,
    selfSigned: true
  }, (error, {serviceKey, certificate, csr}) => {
    const httpsOptions = {
      ca: csr,
      cert: certificate,
      key: serviceKey
    };

    const server = https.createServer(httpsOptions, (req, res) => {
      // How do I know I here whats the target server port?

      res.writeHead(200);
      res.end('OK');
    });

    server.listen((error) => {
      if (error) {
        console.error(error);
      } else {
        callback(null, server.address().port);
      }
    });
  });
};

const createProxy = (httpsServerPort) => {
  const proxy = http.createServer();

  proxy.on('connect', (request, requestSocket, head) => {
    // Here I know whats the target server PORT.
    const targetServerPort = Number(request.url.split(':')[1]);

    console.log('target server port', targetServerPort);

    const serverSocket = net.connect(httpsServerPort, 'localhost', () => {
      requestSocket.write(
        'HTTP/1.1 200 Connection established\r\n\r\n'
      );

      serverSocket.write(head);
      serverSocket.pipe(requestSocket);
      requestSocket.pipe(serverSocket);
    });
  });

  proxy.listen(9000);
};

const main = () => {
  createHttpsServer((error, httpsServerPort) => {
    if (error) {
      console.error(error);
    } else {
      createProxy(httpsServerPort);
    }
  });
};

main();

服务器接受一个https连接,并用“ok”消息响应,而不进一步转发请求。
正如您在代码中看到的(参见// Here I know whats the target server PORT.),我可以在http connect事件处理程序中获取目标服务器的端口。但是,我无法确定如何将此信息传递到http服务器路由器(请参见)。
在进行TLS连接时,如何传递附加信息?
以上代码可以通过运行:
$ node proxy.js &
$ curl --proxy http://localhost:9000 https://localhost:59194/foo.html -k

目标是用“ok localhost:59194”响应。

最佳答案

谢天谢地,除了在另一个协议-connect方法已经做到了这一点,您不能向tls流添加任何内容。但是,由于http代理和https服务器位于同一个代码库中,因此不需要在另一次通过网络抛出tls流。相反,您需要解析tls流,然后可以将任何变量传递给处理它的代码。
但是,在解析TLS之后,仍然会有一个原始的HTTP流,并且需要一个HTTP服务器将其转换为请求并处理响应。
快速而肮脏的方法是使用node的http s服务器来解码tls和解析http。但是服务器的api没有提供处理已经连接的套接字的功能,服务器的代码也没有与连接代码完全分离。因此,您需要劫持服务器的内部连接处理逻辑这当然很容易在将来发生更改时中断:

const http = require('http');
const https = require('https');
const pem = require('pem');

const createProxy = (httpsOptions) => {
    const proxy = http.createServer();

    proxy.on('connect', (request, requestSocket, head) => {
        const server = https.createServer(httpsOptions, (req, res) => {
            res.writeHead(200);
            res.end('OK');
        });
        server.emit('connection', requestSocket);
        requestSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
    });

    proxy.listen(9000);
};

const main = () => {
    pem.createCertificate({
        days: 365,
        selfSigned: true
    }, (error, {serviceKey, certificate, csr}) => {
        createProxy({
            ca: csr,
            cert: certificate,
            key: serviceKey
        });
    });
};

main();

为了避免在每个请求上创建一个https服务器实例,您可以将实例移出并将数据固定到socket对象上:
const server = https.createServer(httpsOptions, (req, res) => {
    res.writeHead(200);
    // here we reach to the net.Socket instance saved on the tls.TLSSocket object,
    // for extra dirtiness
    res.end('OK ' + req.socket._parent.marker + '\n');
});

proxy.on('connect', (request, requestSocket, head) => {
    requestSocket.marker = Math.random();
    server.emit('connection', requestSocket);
    requestSocket.write('HTTP/1.1 200 Connection established\r\n\r\n');
});

使用上述代码,如果您连续执行多个请求:
curl --proxy http://localhost:9000 https://localhost:59194/foo.html \
     https://localhost:59194/foo.html https://localhost:59194/foo.html \
     https://localhost:59194/foo.html https://localhost:59194/foo.html -k

然后您还会注意到它们是在单个连接上处理的,这很好:
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015
OK 0.6113572936982015

当代理服务器已经管理这个套接字时,我不能保证把它交给https服务器不会有任何损坏。The server has the presence of mind to not overwrite another instance on the socket object,但在其他方面似乎与套接字密切相关。您需要用更长的运行连接来测试它。
至于head参数,which can indeed contain initial data
您可以使用requestSocket.unshift(head)将其放回流中,但我不确定代理服务器是否不会立即使用它。
或者,您可以使用requestSocket.emit('data', head)将其切换到https服务器,因为the HTTP server seems to use the stream events,但是TLS socket source calls read() for whatever reason,这与事件是互斥的,所以我不确定它们之间是如何工作的。
一种解决方案是为您自己的包装,它将转发所有的调用和事件,除了在这个初始缓冲区存在时的stream.Duplex,然后使用这个包装代替read()。但随后还需要复制“data”事件,in accordance with the logic of Node's readable streams
最后,您可以尝试创建一个新的双工流,编写requestSocket并将套接字管道化到它,就像您最初所做的那样,并使用该流代替http s服务器的套接字,但不确定它是否与http服务器对套接字的相当霸道的管理兼容。
一种更干净的方法是解码tls流,并对生成的http流使用独立的解析器。谢天谢地,node有一个head模块,该模块被很好地隔离,并将tls套接字转换为常规套接字:
proxy.on('connect', (request, requestSocket, head) => {
    const httpSocket = new tls.TLSSocket(requestSocket, {
        isServer: true,
        // this var can be reused for all requests,
        // as it's normally saved on an HTTPS server instance
        secureContext: tls.createSecureContext(httpsOptions)
    });

    ...
});

See caveats on tls关于复制https服务器的行为。
唉,node的http解析器不太可用:它是一个c库,这需要在socket和解析器调用之间进行大量的遗留工作。而且api可以(而且确实)在不同版本之间进行更改,而不会发出警告,与上面使用的http服务器内部组件相比,它具有更大的不兼容性。
有用于解析http的npm模块:例如onetwo,但是没有一个看起来太成熟和维护。
我还对定制http服务器的可行性表示怀疑,因为网络套接字往往需要大量的培养,因为边缘情况、难以调试的超时问题等都应该在节点的http服务器中得到考虑。
P.S.一个可能的研究领域是集群模块如何处理连接:afaik集群中的父进程将连接套接字交给子进程,但它并没有在每个请求上分叉,这表明子进程以某种方式处理连接的套接字,代码位于http服务器实例之外。但是,由于集群模块现在是核心,它可能会利用非公共api。

关于javascript - 在建立TLS连接时,如何传递其他信息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51791732/

相关文章:

javascript - 如何使用 javascript/ecmascript 6 中的生成器方法创建具有值集合的类?

javascript - CSS - 将元素符号添加到事件选项卡、垂直对齐和留在选项卡内问题

node.js - 我可以在 Windows 中使用命令行升级 Node.js 吗?

javascript - "Converting circular structure to JSON"BigQuery 从云函数 NodeJs 插入

javascript - 可以触发 HTTP 请求的 HTML 元素

c++ - 使用 Qt 和 QNetworkRequest 恢复失败的 HTTP 下载

javascript - 如何使用 JavaScript/Jquery 在浏览器关闭选项卡上显示带有自定义 HTML 的弹出窗口?我有自定义 HTML 弹出窗口

javascript - PHP/Javascript 中实时更新时间

node.js - React Native 中的 Socket io

asp.net - 将 JSON 数据发送到 ASP.NET HTTP 处理程序