Skip to content

Commit 3d4d207

Browse files
committedJun 12, 2017
http: fix timeout reset after keep-alive timeout
Fix the logic of resetting the socket timeout of keep-alive HTTP connections and add two tests: * `test-http-server-keep-alive-timeout-slow-server` is a regression test for nodejsGH-13391. It ensures that the server-side keep-alive timeout will not fire during processing of a request. * `test-http-server-keep-alive-timeout-slow-headers` ensures that the regular socket timeout is restored as soon as a client starts sending a new request, not as soon as the whole message is received, so that the keep-alive timeout will not fire while, e.g., the client is sending large cookies. Refs: nodejs#2534 Fixes: nodejs#13391
1 parent a2ed3a9 commit 3d4d207

3 files changed

+118
-8
lines changed
 

‎lib/_http_server.js

+11-8
Original file line numberDiff line numberDiff line change
@@ -436,21 +436,14 @@ function socketOnData(server, socket, parser, state, d) {
436436
assert(!socket._paused);
437437
debug('SERVER socketOnData %d', d.length);
438438

439-
if (state.keepAliveTimeoutSet) {
440-
socket.setTimeout(0);
441-
if (server.timeout) {
442-
socket.setTimeout(server.timeout);
443-
}
444-
state.keepAliveTimeoutSet = false;
445-
}
446-
447439
var ret = parser.execute(d);
448440
onParserExecuteCommon(server, socket, parser, state, ret, d);
449441
}
450442

451443
function onParserExecute(server, socket, parser, state, ret, d) {
452444
socket._unrefTimer();
453445
debug('SERVER socketOnParserExecute %d', ret);
446+
resetSocketTimeout(server, socket, state);
454447
onParserExecuteCommon(server, socket, parser, state, ret, undefined);
455448
}
456449

@@ -545,6 +538,8 @@ function resOnFinish(req, res, socket, state, server) {
545538
// new message. In this callback we setup the response object and pass it
546539
// to the user.
547540
function parserOnIncoming(server, socket, state, req, keepAlive) {
541+
resetSocketTimeout(server, socket, state);
542+
548543
state.incoming.push(req);
549544

550545
// If the writable end isn't consuming, then stop reading
@@ -606,6 +601,14 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
606601
return false; // Not a HEAD response. (Not even a response!)
607602
}
608603

604+
function resetSocketTimeout(server, socket, state) {
605+
if (!state.keepAliveTimeoutSet)
606+
return;
607+
608+
socket.setTimeout(server.timeout || 0);
609+
state.keepAliveTimeoutSet = false;
610+
}
611+
609612
function onSocketResume() {
610613
// It may seem that the socket is resumed, but this is an enemy's trick to
611614
// deceive us! `resume` is emitted asynchronously, and may be called from
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
const net = require('net');
7+
8+
const server = http.createServer(common.mustCall((req, res) => {
9+
res.end();
10+
}, 2));
11+
12+
server.keepAliveTimeout = 100;
13+
14+
server.listen(0, common.mustCall(() => {
15+
const port = server.address().port;
16+
const socket = net.connect({ port }, common.mustCall(() => {
17+
request(common.mustCall(() => {
18+
// Make a second request on the same socket, after the keep-alive timeout
19+
// has been set on the server side.
20+
request(common.mustCall());
21+
}));
22+
}));
23+
24+
server.on('timeout', common.mustCall(() => {
25+
socket.end();
26+
server.close();
27+
}));
28+
29+
function request(callback) {
30+
socket.setEncoding('utf8');
31+
socket.on('data', onData);
32+
let response = '';
33+
34+
// Simulate a client that sends headers slowly (with a period of inactivity
35+
// that is longer than the keep-alive timeout).
36+
socket.write('GET / HTTP/1.1\r\n' +
37+
`Host: localhost:${port}\r\n`);
38+
setTimeout(() => {
39+
socket.write('Connection: keep-alive\r\n' +
40+
'\r\n');
41+
}, 200);
42+
43+
function onData(chunk) {
44+
response += chunk;
45+
if (chunk.includes('\r\n')) {
46+
socket.removeListener('data', onData);
47+
onHeaders();
48+
}
49+
}
50+
51+
function onHeaders() {
52+
assert.ok(response.includes('HTTP/1.1 200 OK\r\n'));
53+
assert.ok(response.includes('Connection: keep-alive\r\n'));
54+
callback();
55+
}
56+
}
57+
}));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const http = require('http');
6+
7+
const server = http.createServer(common.mustCall((req, res) => {
8+
if (req.url === '/first') {
9+
res.end('ok');
10+
return;
11+
}
12+
setTimeout(() => {
13+
res.end('ok');
14+
}, 200);
15+
}, 2));
16+
17+
server.keepAliveTimeout = 100;
18+
19+
const agent = new http.Agent({
20+
keepAlive: true,
21+
maxSockets: 1
22+
});
23+
24+
function request(path, callback) {
25+
const port = server.address().port;
26+
const req = http.request({ agent, path, port }, common.mustCall((res) => {
27+
assert.strictEqual(res.statusCode, 200);
28+
29+
res.setEncoding('utf8');
30+
31+
let result = '';
32+
res.on('data', (chunk) => {
33+
result += chunk;
34+
});
35+
36+
res.on('end', common.mustCall(() => {
37+
assert.strictEqual(result, 'ok');
38+
callback();
39+
}));
40+
}));
41+
req.end();
42+
}
43+
44+
server.listen(0, common.mustCall(() => {
45+
request('/first', () => {
46+
request('/second', () => {
47+
server.close();
48+
});
49+
});
50+
}));

0 commit comments

Comments
 (0)
Please sign in to comment.