Skip to content

Commit

Permalink
http: add reusedSocket property on client request
Browse files Browse the repository at this point in the history
Set ClientRequest.reusedSocket property when reusing socket for request,
so user can handle retry base on wether the request is reusing a socket.

Refs: request/request#3131

PR-URL: #29715
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Weijia Wang <starkwang@126.com>
  • Loading branch information
themez authored and starkwang committed Oct 12, 2019
1 parent 075c7eb commit 8915b15
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 3 deletions.
56 changes: 56 additions & 0 deletions doc/api/http.md
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lib/_http_agent.js
Expand Up @@ -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();
};

Expand Down
1 change: 1 addition & 0 deletions lib/_http_client.js
Expand Up @@ -195,6 +195,7 @@ function ClientRequest(input, options, cb) {
this.upgradeOrConnect = false;
this.parser = null;
this.maxHeadersCount = null;
this.reusedSocket = false;

var called = false;

Expand Down
9 changes: 6 additions & 3 deletions test/parallel/test-http-agent-keepalive.js
Expand Up @@ -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(() => {
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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(() => {
Expand Down

0 comments on commit 8915b15

Please sign in to comment.