Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
http: add reusedSocket property on client request
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 BethGriggs committed Feb 6, 2020
1 parent 7fc6984 commit b9ffca1
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 @@ -673,6 +673,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 @@ -340,6 +340,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 @@ -204,6 +204,7 @@ function ClientRequest(input, options, cb) {
this.upgradeOrConnect = false;
this.parser = null;
this.maxHeadersCount = null;
this.reusedSocket = false;

let 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 b9ffca1

Please sign in to comment.