Open
Description
- Version: v10.11.0
- Platform: Darwin localhost.local 15.6.0 Darwin Kernel Version 15.6.0: Thu Jun 21 20:07:40 PDT 2018; root:xnu-3248.73.11~1/RELEASE_X86_64 x86_64 (aka OSX 10.11.6, also happens on High Sierra)
- Subsystem: ???
In the following code, pem
is a module from NPM, and the rest is an HTTP proxy which serves CONNECT requests and pipes them to an HTTPS server defined in the same script:
const http = require('http');
const https = require('https');
const pem = require('pem');
const net = require('net');
const createHttpsServer = (callback) => {
pem.createCertificate({
days: 365,
selfSigned: true
}, (error, {serviceKey, certificate, csr}) => {
const server = https.createServer({
ca: csr,
cert: certificate,
key: serviceKey
}, (req, res) => {
setImmediate(() => {
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({
port: 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);
};
createHttpsServer((error, httpsServerPort) => {
if (error) {
console.error(error);
} else {
createProxy(httpsServerPort);
}
});
If you run curl --proxy http://localhost:9000 https://qweasd/ -k
, you'll see Curl receive the reply, and meanwhile the script will almost certainly fail with:
events.js:167
throw er; // Unhandled 'error' event
^
Error: read ECONNRESET
at TCP.onStreamRead (internal/stream_base_commons.js:111:27)
Emitted 'error' event at:
at Socket.onerror (_stream_readable.js:690: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)
This issue is dependent on several parameters:
- You're running OSX. El Capitan and High Sierra both exhibit this, an Ubuntu VM doesn't.
- The server is HTTPS: an HTTP server and
curl --proxytunnel
work alright. - The server is running in the same script as the proxy.
- The server answers with a
connection: 'close'
header. - The strangest one: the server request handler has
setImmediate
orsetTimeout
around the reply (with, I guess, any timeout value).process.nextTick
doesn't do it. And if the handler instead serves the reply immediately, the server continues to answer perfectly alright until you hit a seemingly different ECONNRESET.
If you adorn the sockets with error
event handlers, you'll see that the errors actually don't happen on each request, and when they do, it's primarily on the outgoing socket from the proxy to the HTTPS server; and often, but not necessarily, a second ECONNRESET occurs on the incoming socket to the proxy.