summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/api/http.md56
-rw-r--r--lib/_http_agent.js1
-rw-r--r--lib/_http_client.js1
-rw-r--r--test/parallel/test-http-agent-keepalive.js9
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(() => {