Skip to content

Commit

Permalink
http: fix timeout reset after keep-alive timeout
Browse files Browse the repository at this point in the history
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
  • Loading branch information
aqrln committed Jun 12, 2017
1 parent a2ed3a9 commit 3d4d207
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 8 deletions.
19 changes: 11 additions & 8 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,21 +436,14 @@ function socketOnData(server, socket, parser, state, d) {
assert(!socket._paused);
debug('SERVER socketOnData %d', d.length);

if (state.keepAliveTimeoutSet) {
socket.setTimeout(0);
if (server.timeout) {
socket.setTimeout(server.timeout);
}
state.keepAliveTimeoutSet = false;
}

var ret = parser.execute(d);
onParserExecuteCommon(server, socket, parser, state, ret, d);
}

function onParserExecute(server, socket, parser, state, ret, d) {
socket._unrefTimer();
debug('SERVER socketOnParserExecute %d', ret);
resetSocketTimeout(server, socket, state);
onParserExecuteCommon(server, socket, parser, state, ret, undefined);
}

Expand Down Expand Up @@ -545,6 +538,8 @@ function resOnFinish(req, res, socket, state, server) {
// new message. In this callback we setup the response object and pass it
// to the user.
function parserOnIncoming(server, socket, state, req, keepAlive) {
resetSocketTimeout(server, socket, state);

state.incoming.push(req);

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

function resetSocketTimeout(server, socket, state) {
if (!state.keepAliveTimeoutSet)
return;

socket.setTimeout(server.timeout || 0);
state.keepAliveTimeoutSet = false;
}

function onSocketResume() {
// It may seem that the socket is resumed, but this is an enemy's trick to
// deceive us! `resume` is emitted asynchronously, and may be called from
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const http = require('http');
const net = require('net');

const server = http.createServer(common.mustCall((req, res) => {
res.end();
}, 2));

server.keepAliveTimeout = 100;

server.listen(0, common.mustCall(() => {
const port = server.address().port;
const socket = net.connect({ port }, common.mustCall(() => {
request(common.mustCall(() => {
// Make a second request on the same socket, after the keep-alive timeout
// has been set on the server side.
request(common.mustCall());
}));
}));

server.on('timeout', common.mustCall(() => {
socket.end();
server.close();
}));

function request(callback) {
socket.setEncoding('utf8');
socket.on('data', onData);
let response = '';

// Simulate a client that sends headers slowly (with a period of inactivity
// that is longer than the keep-alive timeout).
socket.write('GET / HTTP/1.1\r\n' +
`Host: localhost:${port}\r\n`);
setTimeout(() => {
socket.write('Connection: keep-alive\r\n' +
'\r\n');
}, 200);

function onData(chunk) {
response += chunk;
if (chunk.includes('\r\n')) {
socket.removeListener('data', onData);
onHeaders();
}
}

function onHeaders() {
assert.ok(response.includes('HTTP/1.1 200 OK\r\n'));
assert.ok(response.includes('Connection: keep-alive\r\n'));
callback();
}
}
}));
50 changes: 50 additions & 0 deletions test/parallel/test-http-server-keep-alive-timeout-slow-server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use strict';

const common = require('../common');
const assert = require('assert');
const http = require('http');

const server = http.createServer(common.mustCall((req, res) => {
if (req.url === '/first') {
res.end('ok');
return;
}
setTimeout(() => {
res.end('ok');
}, 200);
}, 2));

server.keepAliveTimeout = 100;

const agent = new http.Agent({
keepAlive: true,
maxSockets: 1
});

function request(path, callback) {
const port = server.address().port;
const req = http.request({ agent, path, port }, common.mustCall((res) => {
assert.strictEqual(res.statusCode, 200);

res.setEncoding('utf8');

let result = '';
res.on('data', (chunk) => {
result += chunk;
});

res.on('end', common.mustCall(() => {
assert.strictEqual(result, 'ok');
callback();
}));
}));
req.end();
}

server.listen(0, common.mustCall(() => {
request('/first', () => {
request('/second', () => {
server.close();
});
});
}));

0 comments on commit 3d4d207

Please sign in to comment.