diff options
-rw-r--r-- | doc/api/http.md | 56 | ||||
-rw-r--r-- | lib/_http_agent.js | 1 | ||||
-rw-r--r-- | lib/_http_client.js | 1 | ||||
-rw-r--r-- | test/parallel/test-http-agent-keepalive.js | 9 |
4 files changed, 64 insertions, 3 deletions
diff --git a/doc/api/http.md b/doc/api/http.md index 351add59b5..2f1b37064b 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -676,6 +676,62 @@ Removes a header that's already defined into headers object. request.removeHeader('Content-Type'); ``` +### request.reusedSocket + +<!-- YAML +added: REPLACEME +--> + +* {boolean} Whether the request is send through a reused socket. + +When sending request through a keep-alive enabled agent, the underlying socket +might be reused. But if server closes connection at unfortunate time, client +may run into a 'ECONNRESET' error. + +```js +const http = require('http'); + +// Server has a 5 seconds keep-alive timeout by default +http + .createServer((req, res) => { + res.write('hello\n'); + res.end(); + }) + .listen(3000); + +setInterval(() => { + // Adapting a keep-alive agent + http.get('http://localhost:3000', { agent }, (res) => { + res.on('data', (data) => { + // Do nothing + }); + }); +}, 5000); // Sending request on 5s interval so it's easy to hit idle timeout +``` + +By marking a request whether it reused socket or not, we can do +automatic error retry base on it. + +```js +const http = require('http'); +const agent = new http.Agent({ keepAlive: true }); + +function retriableRequest() { + const req = http + .get('http://localhost:3000', { agent }, (res) => { + // ... + }) + .on('error', (err) => { + // Check if retry is needed + if (req.reusedSocket && err.code === 'ECONNRESET') { + retriableRequest(); + } + }); +} + +retriableRequest(); +``` + ### request.setHeader(name, value) <!-- YAML added: v1.6.0 diff --git a/lib/_http_agent.js b/lib/_http_agent.js index 25ff16fea1..dcb5ed376d 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -341,6 +341,7 @@ Agent.prototype.keepSocketAlive = function keepSocketAlive(socket) { Agent.prototype.reuseSocket = function reuseSocket(socket, req) { debug('have free socket'); + req.reusedSocket = true; socket.ref(); }; diff --git a/lib/_http_client.js b/lib/_http_client.js index 3eccf6ac10..957d2d6403 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -195,6 +195,7 @@ function ClientRequest(input, options, cb) { this.upgradeOrConnect = false; this.parser = null; this.maxHeadersCount = null; + this.reusedSocket = false; var called = false; diff --git a/test/parallel/test-http-agent-keepalive.js b/test/parallel/test-http-agent-keepalive.js index 8cfc568b1e..0a85b30b0a 100644 --- a/test/parallel/test-http-agent-keepalive.js +++ b/test/parallel/test-http-agent-keepalive.js @@ -63,7 +63,8 @@ function checkDataAndSockets(body) { function second() { // Request second, use the same socket - get('/second', common.mustCall((res) => { + const req = get('/second', common.mustCall((res) => { + assert.strictEqual(req.reusedSocket, true); assert.strictEqual(res.statusCode, 200); res.on('data', checkDataAndSockets); res.on('end', common.mustCall(() => { @@ -80,7 +81,8 @@ function second() { function remoteClose() { // Mock remote server close the socket - get('/remote_close', common.mustCall((res) => { + const req = get('/remote_close', common.mustCall((res) => { + assert.deepStrictEqual(req.reusedSocket, true); assert.deepStrictEqual(res.statusCode, 200); res.on('data', checkDataAndSockets); res.on('end', common.mustCall(() => { @@ -120,7 +122,8 @@ function remoteError() { server.listen(0, common.mustCall(() => { name = `localhost:${server.address().port}:`; // Request first, and keep alive - get('/first', common.mustCall((res) => { + const req = get('/first', common.mustCall((res) => { + assert.strictEqual(req.reusedSocket, false); assert.strictEqual(res.statusCode, 200); res.on('data', checkDataAndSockets); res.on('end', common.mustCall(() => { |