diff --git a/index.js b/index.js index 2fc4daf..db13510 100644 --- a/index.js +++ b/index.js @@ -14,8 +14,28 @@ const createServer = (server, options) => { options.handleCommonErrors = true; } - function onError(err, source) { - // handle common socket errors + function prepareSocket(socket, name) { + socket.setKeepAlive(true); // prevent idle timeout ECONNRESET + if (options.setNoDelay) { + socket.setNoDelay(true); // disable nagle algorithm + } + if (server.timeout) { + socket.setTimeout(server.timeout, () => closeSocket(socket)); + } + socket.addListener('error', err => onError(err, name, socket)); + } + + function closeSocket(socket, err) { + // let the server destroy the connection + // https://github.com/nodejs/node/blob/c30ef3cbd2e42ac1d600f6bd78a601a5496b0877/lib/https.js#L69 + server.emit(server._sharedCreds?'tlsClientError':'clientError', err, socket); + } + + function onError(err, source, socket) { + if (socket) { + closeSocket(socket, err); + } + // handle common network errors if (options.handleCommonErrors) { const errCodes = new Set(['ECONNRESET', 'EPIPE', 'HPE_INVALID_EOF_STATE', 'HPE_HEADER_OVERFLOW']); if (err && err.code && errCodes.has(err.code)) { @@ -32,17 +52,19 @@ const createServer = (server, options) => { throw err; } + function listen(proxy, server) { + const port = server.address().port; + server.close(); + proxied.listen(port, () => console.log(`PROXY protocol parser listening to port ${port}`)); + } + // create proxy protocol processing server const proxied = require('net').createServer(socket => { const buf = []; let bytesRead = 0; let proxyProtoLength; let isProxyProto; - socket.setKeepAlive(true); // prevent idle timeout ECONNRESET - if (options.setNoDelay) { - socket.setNoDelay(true); // disable nagle algorithm - } - socket.addListener('error', err => onError(err, 'proxyproto socket')); + prepareSocket(socket, 'proxyproto socket'); socket.addListener('data', onData); function onData(buffer) { socket.pause(); @@ -99,23 +121,19 @@ const createServer = (server, options) => { configurable: true }); }); - socket.addListener('error', err => onError(err, 'secure socket')); - socket.setKeepAlive(true); // prevent idle timeout ECONNRESET - if (options.setNoDelay) { - socket.setNoDelay(true); // disable nagle algorithm - } + prepareSocket(socket, 'secure socket'); }); } else { server.on('connection', socket => { - socket.addListener('error', err => onError(err, 'socket')); + prepareSocket(socket, 'socket'); }); } + // listen to listening event + server.on('listening', () => listen(proxied, server)); // if server is already listening, use that port if (server.listening) { - const port = server.address().port; - server.close(); - proxied.listen(port, () => console.log(`PROXY protocol parser listening to port ${port}`)); + listen(proxied, server); } return proxied; diff --git a/package.json b/package.json index 8f9e76d..fab95b5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "proxyproto", - "version": "1.0.10", + "version": "1.1.0", "description": "Pre-process PROXY protocol headers from node tcp sockets", "main": "index.js", "scripts": { diff --git a/test/index.js b/test/index.js index ccb5d43..6553fa0 100644 --- a/test/index.js +++ b/test/index.js @@ -138,13 +138,32 @@ module.exports = async t => { }); t.test('listening port is re-used', async (t) => { - const server = http.createServer(); - server.listen(PORT); - const proxied = proxyproto.createServer(server); - t.ok(proxied.listening); - t.notOk(server.listening); - t.same(proxied.address().port, PORT); - proxied.close(); + await new Promise(resolve => { + const server = http.createServer(); + server.listen(PORT, () => { + const proxied = proxyproto.createServer(server); + t.ok(proxied.listening); + t.notOk(server.listening); + t.same(proxied.address().port, PORT); + proxied.close(); + resolve(); + }); + }); + }); + + t.test('listening event is listened to', async (t) => { + await new Promise(resolve => { + const server = http.createServer(); + const proxied = proxyproto.createServer(server); + proxied.on('listening', () => { + t.ok(proxied.listening); + t.notOk(server.listening); + t.same(proxied.address().port, PORT); + proxied.close(); + resolve(); + }); + server.listen(PORT); + }); }); // first load test has ~.2ms added latency @@ -304,6 +323,21 @@ module.exports = async t => { }); }); + t.test('closes the proxyproto socket', async (t) => { + await new Promise(resolve => { + const proxied = proxyproto.createServer(httpServer); + proxied.on('connection', socket => { + socket.on('close', err => { + t.notOk(err); + proxied.close(); + resolve(); + }); + }); + proxied.listen(PORT); + http.get(`http://localhost:${PORT}`, httpRequestOptions); + }); + }); + }; if (!module.parent) module.exports(require('tap'));