From ece428ea635a1d914c3fddda2290da53428ac972 Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Sat, 4 Jun 2016 00:21:04 -0400 Subject: [PATCH] http: fix no dumping after `maybeReadMore` When `maybeReadMore` kicks in on a first bytes of incoming data, the `req.read(0)` will be invoked and the `req._consuming` will be set to `true`. This seemingly harmless property leads to a dire consequences: the server won't call `req._dump()` and the whole HTTP/1.1 pipeline will hang (single connection). PR-URL: https://github.com/nodejs/node/pull/7211 Reviewed-By: Ben Noordhuis Reviewed-By: Matteo Collina --- lib/_http_incoming.js | 9 ++++ test/parallel/test-http-no-read-no-dump.js | 51 ++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/parallel/test-http-no-read-no-dump.js diff --git a/lib/_http_incoming.js b/lib/_http_incoming.js index 2a042cbee239f2..3f6ae86f07e211 100644 --- a/lib/_http_incoming.js +++ b/lib/_http_incoming.js @@ -20,6 +20,13 @@ exports.readStop = readStop; function IncomingMessage(socket) { Stream.Readable.call(this); + // Set this to `true` so that stream.Readable won't attempt to read more + // data on `IncomingMessage#push` (see `maybeReadMore` in + // `_stream_readable.js`). This is important for proper tracking of + // `IncomingMessage#_consuming` which is used to dump requests that users + // haven't attempted to read. + this._readableState.readingMore = true; + this.socket = socket; this.connection = socket; @@ -67,6 +74,8 @@ IncomingMessage.prototype.setTimeout = function(msecs, callback) { IncomingMessage.prototype.read = function(n) { + if (!this._consuming) + this._readableState.readingMore = false; this._consuming = true; this.read = Stream.Readable.prototype.read; return this.read(n); diff --git a/test/parallel/test-http-no-read-no-dump.js b/test/parallel/test-http-no-read-no-dump.js new file mode 100644 index 00000000000000..c509146c0a29d7 --- /dev/null +++ b/test/parallel/test-http-no-read-no-dump.js @@ -0,0 +1,51 @@ +'use strict'; +const common = require('../common'); +const http = require('http'); + +let onPause = null; + +const server = http.createServer((req, res) => { + if (req.method === 'GET') + return res.end(); + + res.writeHead(200); + res.flushHeaders(); + + req.connection.on('pause', () => { + res.end(); + onPause(); + }); +}).listen(common.PORT, common.mustCall(() => { + const agent = new http.Agent({ + maxSockets: 1, + keepAlive: true + }); + + const post = http.request({ + agent: agent, + method: 'POST', + port: common.PORT, + }, common.mustCall((res) => { + res.resume(); + + post.write(Buffer.alloc(16 * 1024).fill('X')); + onPause = () => { + post.end('something'); + }; + })); + + /* What happens here is that the server `end`s the response before we send + * `something`, and the client thought that this is a green light for sending + * next GET request + */ + post.write('initial'); + + http.request({ + agent: agent, + method: 'GET', + port: common.PORT, + }, common.mustCall((res) => { + server.close(); + res.connection.end(); + })).end(); +}));