From 4b75767dd800c7bafce3959c26a76baff8823e82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=CC=81=20F=2E=20Romaniello?= Date: Thu, 31 Dec 2015 11:51:46 -0300 Subject: [PATCH] http: handle errors on idle sockets This change adds a new event handler to the `error` event of the socket after it has been used by the http_client. The purpose of this change is to catch errors on *keep alived* connections from idle sockets that otherwise will cause an uncaugh error event on the application. Closes #3595 --- lib/_http_client.js | 9 +++ .../parallel/test-http-agent-error-on-idle.js | 56 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/parallel/test-http-agent-error-on-idle.js diff --git a/lib/_http_client.js b/lib/_http_client.js index 4718cea9e6acb1..5ea0a119feff72 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -282,6 +282,13 @@ function socketErrorListener(err) { socket.destroy(); } +function freeSocketErrorListener(err) { + var socket = this; + debug('SOCKET ERROR on FREE socket:', err.message, err.stack); + socket.destroy(); + socket.emit('agentRemove'); +} + function socketOnEnd() { var socket = this; var req = this._httpMessage; @@ -448,6 +455,7 @@ function responseOnEnd() { } socket.removeListener('close', socketCloseListener); socket.removeListener('error', socketErrorListener); + socket.once('error', freeSocketErrorListener); // Mark this socket as available, AFTER user-added end // handlers have a chance to run. process.nextTick(emitFreeNT, socket); @@ -483,6 +491,7 @@ function tickOnSocket(req, socket) { } parser.onIncoming = parserOnIncomingClient; + socket.removeListener('error', freeSocketErrorListener); socket.on('error', socketErrorListener); socket.on('data', socketOnData); socket.on('end', socketOnEnd); diff --git a/test/parallel/test-http-agent-error-on-idle.js b/test/parallel/test-http-agent-error-on-idle.js new file mode 100644 index 00000000000000..b6e50f930c0cd5 --- /dev/null +++ b/test/parallel/test-http-agent-error-on-idle.js @@ -0,0 +1,56 @@ +'use strict'; +var common = require('../common'); +var assert = require('assert'); +var http = require('http'); +var Agent = http.Agent; + +var agent = new Agent({ + keepAlive: true, +}); + +var requestParams = { + host: 'localhost', + port: common.PORT, + agent: agent, + path: '/' +}; + +var socketKey = agent.getName(requestParams); + +function get(callback) { + return http.get(requestParams, callback); +} + +var destroy_queue = {}; +var server = http.createServer(function(req, res) { + res.end('hello world'); +}); + +server.listen(common.PORT, function() { + get(function(res) { + assert.equal(res.statusCode, 200); + res.resume(); + res.on('end', function() { + process.nextTick(function() { + var freeSockets = agent.freeSockets[socketKey]; + assert.equal(freeSockets.length, 1, + 'expect a free socket on ' + socketKey); + + //generate a random error on the free socket + var freeSocket = freeSockets[0]; + freeSocket.emit('error', new Error('ECONNRESET: test')); + + get(done); + }); + }); + }); +}); + +function done() { + assert.equal(Object.keys(agent.freeSockets).length, 0, + 'expect the freeSockets pool to be empty'); + + agent.destroy(); + server.close(); + process.exit(0); +}