Skip to content

Commit bba500d

Browse files
killagutargos
authored andcommitted
http: fix request with option timeout and agent
When request with both timeout and agent, timeout not work. This patch will fix it, socket timeout will set to request timeout before socket is connected, and socket timeout will reset to agent timeout after response end. Fixes: #21185 PR-URL: #21204 Reviewed-By: Khaidi Chu <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Ujjwal Sharma <[email protected]>
1 parent 3d93273 commit bba500d

File tree

6 files changed

+169
-42
lines changed

6 files changed

+169
-42
lines changed

‎doc/api/http.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ added: v0.3.4
126126
*`maxFreeSockets`{number} Maximum number of sockets to leave open
127127
in a free state. Only relevant if `keepAlive` is set to `true`.
128128
**Default:**`256`.
129+
*`timeout`{number} Socket timeout in milliseconds.
130+
This will set the timeout after the socket is connected.
129131

130132
The default [`http.globalAgent`][] that is used by [`http.request()`][] has all
131133
of these values set to their respective defaults.

‎lib/_http_agent.js‎

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ function Agent(options){
6666

6767
if(socket.writable&&
6868
this.requests[name]&&this.requests[name].length){
69-
this.requests[name].shift().onSocket(socket);
69+
constreq=this.requests[name].shift();
70+
setRequestSocket(this,req,socket);
7071
if(this.requests[name].length===0){
7172
// don't leak
7273
deletethis.requests[name];
@@ -176,12 +177,12 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */,
176177
deletethis.freeSockets[name];
177178

178179
this.reuseSocket(socket,req);
179-
req.onSocket(socket);
180+
setRequestSocket(this,req,socket);
180181
this.sockets[name].push(socket);
181182
}elseif(sockLen<this.maxSockets){
182183
debug('call onSocket',sockLen,freeLen);
183184
// If we are under maxSockets create a new one.
184-
this.createSocket(req,options,handleSocketCreation(req,true));
185+
this.createSocket(req,options,handleSocketCreation(this,req,true));
185186
}else{
186187
debug('wait for socket');
187188
// We are over limit so we'll add it to the queue.
@@ -305,9 +306,10 @@ Agent.prototype.removeSocket = function removeSocket(s, options){
305306

306307
if(this.requests[name]&&this.requests[name].length){
307308
debug('removeSocket, have a request, make a socket');
308-
varreq=this.requests[name][0];
309+
constreq=this.requests[name][0];
309310
// If we have pending requests and a socket gets closed make a new one
310-
this.createSocket(req,options,handleSocketCreation(req,false));
311+
constsocketCreationHandler=handleSocketCreation(this,req,false);
312+
this.createSocket(req,options,socketCreationHandler);
311313
}
312314
};
313315

@@ -337,19 +339,36 @@ Agent.prototype.destroy = function destroy(){
337339
}
338340
};
339341

340-
functionhandleSocketCreation(request,informRequest){
342+
functionhandleSocketCreation(agent,request,informRequest){
341343
returnfunctionhandleSocketCreation_Inner(err,socket){
342344
if(err){
343345
process.nextTick(emitErrorNT,request,err);
344346
return;
345347
}
346348
if(informRequest)
347-
request.onSocket(socket);
349+
setRequestSocket(agent,request,socket);
348350
else
349351
socket.emit('free');
350352
};
351353
}
352354

355+
functionsetRequestSocket(agent,req,socket){
356+
req.onSocket(socket);
357+
constagentTimeout=agent.options.timeout||0;
358+
if(req.timeout===undefined||req.timeout===agentTimeout){
359+
return;
360+
}
361+
socket.setTimeout(req.timeout);
362+
// reset timeout after response end
363+
req.once('response',(res)=>{
364+
res.once('end',()=>{
365+
if(socket.timeout!==agentTimeout){
366+
socket.setTimeout(agentTimeout);
367+
}
368+
});
369+
});
370+
}
371+
353372
functionemitErrorNT(emitter,err){
354373
emitter.emit('error',err);
355374
}

‎lib/_http_client.js‎

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -639,18 +639,35 @@ function tickOnSocket(req, socket){
639639
socket.on('end',socketOnEnd);
640640
socket.on('close',socketCloseListener);
641641

642-
if(req.timeout){
643-
constemitRequestTimeout=()=>req.emit('timeout');
644-
socket.once('timeout',emitRequestTimeout);
645-
req.once('response',(res)=>{
646-
res.once('end',()=>{
647-
socket.removeListener('timeout',emitRequestTimeout);
648-
});
649-
});
642+
if(req.timeout!==undefined){
643+
listenSocketTimeout(req);
650644
}
651645
req.emit('socket',socket);
652646
}
653647

648+
functionlistenSocketTimeout(req){
649+
if(req.timeoutCb){
650+
return;
651+
}
652+
constemitRequestTimeout=()=>req.emit('timeout');
653+
// Set timeoutCb so it will get cleaned up on request end.
654+
req.timeoutCb=emitRequestTimeout;
655+
// Delegate socket timeout event.
656+
if(req.socket){
657+
req.socket.once('timeout',emitRequestTimeout);
658+
}else{
659+
req.on('socket',(socket)=>{
660+
socket.once('timeout',emitRequestTimeout);
661+
});
662+
}
663+
// Remove socket timeout listener after response end.
664+
req.once('response',(res)=>{
665+
res.once('end',()=>{
666+
req.socket.removeListener('timeout',emitRequestTimeout);
667+
});
668+
});
669+
}
670+
654671
ClientRequest.prototype.onSocket=functiononSocket(socket){
655672
process.nextTick(onSocketNT,this,socket);
656673
};
@@ -700,42 +717,29 @@ function _deferToConnect(method, arguments_, cb){
700717
}
701718

702719
ClientRequest.prototype.setTimeout=functionsetTimeout(msecs,callback){
720+
listenSocketTimeout(this);
703721
msecs=validateTimerDuration(msecs);
704722
if(callback)this.once('timeout',callback);
705723

706-
constemitTimeout=()=>this.emit('timeout');
707-
708-
if(this.socket&&this.socket.writable){
709-
if(this.timeoutCb)
710-
this.socket.setTimeout(0,this.timeoutCb);
711-
this.timeoutCb=emitTimeout;
712-
this.socket.setTimeout(msecs,emitTimeout);
713-
returnthis;
714-
}
715-
716-
// Set timeoutCb so that it'll get cleaned up on request end
717-
this.timeoutCb=emitTimeout;
718724
if(this.socket){
719-
varsock=this.socket;
720-
this.socket.once('connect',function(){
721-
sock.setTimeout(msecs,emitTimeout);
722-
});
723-
returnthis;
725+
setSocketTimeout(this.socket,msecs);
726+
}else{
727+
this.once('socket',(sock)=>setSocketTimeout(sock,msecs));
724728
}
725729

726-
this.once('socket',function(sock){
727-
if(sock.connecting){
728-
sock.once('connect',function(){
729-
sock.setTimeout(msecs,emitTimeout);
730-
});
731-
}else{
732-
sock.setTimeout(msecs,emitTimeout);
733-
}
734-
});
735-
736730
returnthis;
737731
};
738732

733+
functionsetSocketTimeout(sock,msecs){
734+
if(sock.connecting){
735+
sock.once('connect',function(){
736+
sock.setTimeout(msecs);
737+
});
738+
}else{
739+
sock.setTimeout(msecs);
740+
}
741+
}
742+
739743
ClientRequest.prototype.setNoDelay=functionsetNoDelay(noDelay){
740744
this._deferToConnect('setNoDelay',[noDelay]);
741745
};

‎lib/net.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ function writeAfterFIN(chunk, encoding, cb){
408408
}
409409

410410
Socket.prototype.setTimeout=function(msecs,callback){
411+
this.timeout=msecs;
411412
// Type checking identical to timers.enroll()
412413
msecs=validateTimerDuration(msecs);
413414

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
4+
// Test that `req.setTimeout` will fired exactly once.
5+
6+
constassert=require('assert');
7+
consthttp=require('http');
8+
9+
constHTTP_CLIENT_TIMEOUT=2000;
10+
11+
constoptions={
12+
method: 'GET',
13+
port: undefined,
14+
host: '127.0.0.1',
15+
path: '/',
16+
timeout: HTTP_CLIENT_TIMEOUT,
17+
};
18+
19+
constserver=http.createServer(()=>{
20+
// Never respond.
21+
});
22+
23+
server.listen(0,options.host,function(){
24+
doRequest();
25+
});
26+
27+
functiondoRequest(){
28+
options.port=server.address().port;
29+
constreq=http.request(options);
30+
req.setTimeout(HTTP_CLIENT_TIMEOUT/2);
31+
req.on('error',()=>{
32+
// This space is intentionally left blank.
33+
});
34+
req.on('close',common.mustCall(()=>server.close()));
35+
36+
lettimeout_events=0;
37+
req.on('timeout',common.mustCall(()=>{
38+
timeout_events+=1;
39+
}));
40+
req.end();
41+
42+
setTimeout(function(){
43+
req.destroy();
44+
assert.strictEqual(timeout_events,1);
45+
},common.platformTimeout(HTTP_CLIENT_TIMEOUT));
46+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use strict';
2+
constcommon=require('../common');
3+
4+
// Test that when http request uses both timeout and agent,
5+
// timeout will work as expected.
6+
7+
constassert=require('assert');
8+
consthttp=require('http');
9+
10+
constHTTP_AGENT_TIMEOUT=1000;
11+
constHTTP_CLIENT_TIMEOUT=3000;
12+
13+
constagent=newhttp.Agent({timeout: HTTP_AGENT_TIMEOUT});
14+
constoptions={
15+
method: 'GET',
16+
port: undefined,
17+
host: '127.0.0.1',
18+
path: '/',
19+
timeout: HTTP_CLIENT_TIMEOUT,
20+
agent,
21+
};
22+
23+
constserver=http.createServer(()=>{
24+
// Never respond.
25+
});
26+
27+
server.listen(0,options.host,function(){
28+
doRequest();
29+
});
30+
31+
functiondoRequest(){
32+
options.port=server.address().port;
33+
constreq=http.request(options);
34+
conststart=Date.now();
35+
req.on('error',()=>{
36+
// This space is intentionally left blank.
37+
});
38+
req.on('close',common.mustCall(()=>server.close()));
39+
40+
lettimeout_events=0;
41+
req.on('timeout',common.mustCall(()=>{
42+
timeout_events+=1;
43+
constduration=Date.now()-start;
44+
// The timeout event cannot be precisely timed. It will delay
45+
// some number of milliseconds, so test it in second units.
46+
assert.strictEqual(duration/1000|0,HTTP_CLIENT_TIMEOUT/1000);
47+
}));
48+
req.end();
49+
50+
setTimeout(function(){
51+
req.destroy();
52+
assert.strictEqual(timeout_events,1);
53+
// Ensure the `timeout` event fired only once.
54+
},common.platformTimeout(HTTP_CLIENT_TIMEOUT*2));
55+
}

0 commit comments

Comments
(0)