这是用于复制问题的服务器/代理设置:
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
错误?
答案 0 :(得分:1)
以下使用.destroy()
的建议导致某些HTTP客户端仅检索响应的一部分。
进一步检查后,我发现一个简单的配置使REQUEST_SOCKET接近预期:
const serverSocket = net.connect({
allowHalfOpen: true,
port
}, () => {});
最好了解需要allowHalfOpen: true
的原因。
我一直在研究这个问题,并且意识到这个问题是特定于OS的。特别是,我意识到我可以使用osx-high-sierra而不是使用docker node图片来持续复制此问题。
然后,我添加了utility来记录所有requestSocket
,serverSocket
和HTTP服务器事件。
这些是在docker容器中记录的事件:
[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上必须执行上述操作的原因,则请发表评论。