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: https://github.com/request/request/issues/3131

PR-URL: https://github.com/nodejs/node/pull/29715
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
Reviewed-By: Weijia Wang <starkwang@126.com>
This commit is contained in:
themez
2019-09-26 11:56:59 +08:00
committed by Weijia Wang
parent 075c7ebeb5
commit 8915b15f8c
4 changed files with 64 additions and 3 deletions

View File

@@ -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

View File

@@ -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();
};

View File

@@ -195,6 +195,7 @@ function ClientRequest(input, options, cb) {
this.upgradeOrConnect = false;
this.parser = null;
this.maxHeadersCount = null;
this.reusedSocket = false;
var called = false;

View File

@@ -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(() => {