在通过隧道传输HTTPS请求并且远程服务器以“ connection:close”标头响应时,如何防止ECONNRESET?

时间:2018-08-10 18:56:33

标签: node.js 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) => {
      res.writeHead(200, {
        connection: 'close'
      });
      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) => {
    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);
    });

    requestSocket.on('error', (error) => {
      console.log('error', error);
    });
  });

  proxy.listen(9000);
};

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

main();

所描述的问题可以通过运行来复制:

$ node proxy.js &
$ curl --proxy http://localhost:9000 https://localhost:59194/foo.html -k

在上面的示例中,HTTPS服务器响应:

res.writeHead(200, {
  connection: 'close'
});
res.end('OK');

结果,请求套接字产生一个错误:

Error: read ECONNRESET
    at TCP.onread (net.js:660:25)
Emitted 'error' event at:
    at Socket.onerror (_stream_readable.js:687:12)
    at Socket.emit (events.js:182:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at process._tickCallback (internal/process/next_tick.js:63:19)

错误本身是由以下原因处理的:

requestSocket.on('error', (error) => {
  console.log('error', error);
});

但是,我不清楚如何处理“连接:关闭”标头而不会引发错误。

为调试该问题,我为所有重要事件设置了事件侦听器:

serverSocket.on('data', () => {
  console.log('serverSocket DATA');
});

serverSocket.on('end', () => {
  console.log('serverSocket END');
});

serverSocket.on('drain', () => {
  console.log('serverSocket DRAIN');
});

serverSocket.on('finish', () => {
  console.log('serverSocket FINISH');
});

serverSocket.on('close', () => {
  console.log('serverSocket CLOSE');
});

//

requestSocket.on('data', () => {
  console.log('requestSocket DATA');
});

requestSocket.on('end', () => {
  console.log('requestSocket END');
});

requestSocket.on('drain', () => {
  console.log('requestSocket DRAIN');
});

requestSocket.on('finish', () => {
  console.log('requestSocket FINISH');
});

requestSocket.on('close', () => {
  console.log('requestSocket CLOSE');
});

讲述以下故事:

requestSocket DATA
serverSocket DATA
requestSocket DATA
serverSocket DATA
requestSocket DATA
serverSocket DATA
serverSocket END
serverSocket FINISH
serverSocket CLOSE
requestSocket ERROR { Error: read ECONNRESET
    at TCP.onread (net.js:660:25) errno: 'ECONNRESET', code: 'ECONNRESET', syscall: 'read' }
requestSocket FINISH
requestSocket CLOSE

如果我正确解释它,则“ connection:close”标头会导致serverSocket立即关闭套接字。在那里,requestSocket FINISH事件发生在–之后,这意味着在套接字关闭之前并非所有数据都已刷新。

如何正确处理而不引起ECONNRESET错误?

1 个答案:

答案 0 :(得分:1)

以下使用.destroy()的建议导致某些HTTP客户端仅检索响应的一部分。

进一步检查后,我发现一个简单的配置使REQUEST_SOCKET接近预期:

const serverSocket = net.connect({
  allowHalfOpen: true,
  port
}, () => {});

最好了解需要allowHalfOpen: true的原因。


我一直在研究这个问题,并且意识到这个问题是特定于OS的。特别是,我意识到我可以使用而不是使用 node图片来持续复制此问题。

然后,我添加了utility来记录所有requestSocketserverSocket和HTTP服务器事件。

这些是在容器中记录的事件:

[HTTP_SERVER] listening
[SERVER_SOCKET] lookup
[HTTP_SERVER] connection
[SERVER_SOCKET] connect
[REQUEST_SOCKET] pipe
[SERVER_SOCKET] pipe
[SERVER_SOCKET] ready
[SERVER_SOCKET] resume
[REQUEST_SOCKET] resume
[REQUEST_SOCKET] data
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] secureConnection
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] request
[SERVER_SOCKET] data
[SERVER_SOCKET] readable
[SERVER_SOCKET] end
[SERVER_SOCKET] prefinish
[SERVER_SOCKET] finish
[SERVER_SOCKET] unpipe
[REQUEST_SOCKET] readable
[REQUEST_SOCKET] prefinish
[REQUEST_SOCKET] finish
[REQUEST_SOCKET] unpipe
[REQUEST_SOCKET] close
[SERVER_SOCKET] close

这些是在OSX环境中运行相同脚本时记录的事件:

[HTTP_SERVER] listening
[SERVER_SOCKET] lookup
[SERVER_SOCKET] connect
[REQUEST_SOCKET] pipe
[SERVER_SOCKET] pipe
[SERVER_SOCKET] ready
[SERVER_SOCKET] resume
[REQUEST_SOCKET] resume
[HTTP_SERVER] connection
[REQUEST_SOCKET] data
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] secureConnection
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] request
[SERVER_SOCKET] data
[SERVER_SOCKET] readable
[SERVER_SOCKET] end
[SERVER_SOCKET] prefinish
[SERVER_SOCKET] finish
[SERVER_SOCKET] unpipe
[REQUEST_SOCKET] readable
[SERVER_SOCKET] close
[REQUEST_SOCKET] error
[REQUEST_SOCKET] unpipe
[REQUEST_SOCKET] error
events.js:167
      throw er; // Unhandled 'error' event
      ^

Error: read ECONNRESET
    at TCP.onread (net.js:660:25)
Emitted 'error' event at:
    at Socket.emitter.emit.args [as emit] (/Users/gajus/Documents/dev/gajus/reproduce-econnreset/server.js:13:13)
    at Socket.onerror (_stream_readable.js:687:12)
    at Socket.emit (events.js:182:13)
    at Socket.emitter.emit.args [as emit] (/Users/gajus/Documents/dev/gajus/reproduce-econnreset/server.js:13:13)
    at emitErrorNT (internal/streams/destroy.js:82:8)
    at emitErrorAndCloseNT (internal/streams/destroy.js:50:3)
    at process._tickCallback (internal/process/next_tick.js:63:19)

我们可以看到在REQUEST_SOCKET发出“结束”事件后,SERVER_SOCKET正常关闭了。

基于此,我在代码中添加了以下内容,以在服务器套接字发出“ finish”事件后强制终止请求套接字:

serverSocket.on('finish', () => {
  requestSocket.destroy();
});

它已经解决了问题,它出现了。

现在在OSX上运行服务会产生:

[HTTP_SERVER] listening
[SERVER_SOCKET] lookup
[SERVER_SOCKET] connect
[REQUEST_SOCKET] pipe
[SERVER_SOCKET] pipe
[SERVER_SOCKET] ready
[SERVER_SOCKET] resume
[REQUEST_SOCKET] resume
[HTTP_SERVER] connection
[REQUEST_SOCKET] data
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] secureConnection
[SERVER_SOCKET] data
[REQUEST_SOCKET] data
[HTTP_SERVER] request
[SERVER_SOCKET] data
[SERVER_SOCKET] readable
[SERVER_SOCKET] end
[SERVER_SOCKET] prefinish
[SERVER_SOCKET] finish
[SERVER_SOCKET] unpipe
[REQUEST_SOCKET] prefinish
[REQUEST_SOCKET] finish
[REQUEST_SOCKET] unpipe
[REQUEST_SOCKET] close
[SERVER_SOCKET] close

也没有错误。

如果您可以解释OSX和linux平台之间行为不同的根本原因,以及在OSX上必须执行上述操作的原因,则请发表评论。