Skip to content

Commit b0c92ca

Browse files
apapirovskiBethGriggs
authored andcommitted
http2: fix end without read
Adjust http2 behaviour to allow ending a stream even after some data comes in (when the user has no intention of reading that data). Also correctly end a stream when trailers are present. Backport-PR-URL: #22850 PR-URL: #20621 Fixes: #20060 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Trivikram Kamat <[email protected]>
1 parent e5f8b08 commit b0c92ca

File tree

4 files changed

+66
-15
lines changed

4 files changed

+66
-15
lines changed

lib/internal/http2/compat.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -248,8 +248,6 @@ class Http2ServerRequest extends Readable {
248248
stream[kRequest] = this;
249249

250250
// Pause the stream..
251-
stream.pause();
252-
stream.on('data', onStreamData);
253251
stream.on('trailers', onStreamTrailers);
254252
stream.on('end', onStreamEnd);
255253
stream.on('error', onStreamError);
@@ -316,8 +314,12 @@ class Http2ServerRequest extends Readable {
316314
_read(nread) {
317315
const state = this[kState];
318316
if (!state.closed) {
319-
state.didRead = true;
320-
process.nextTick(resumeStream, this[kStream]);
317+
if (!state.didRead) {
318+
state.didRead = true;
319+
this[kStream].on('data', onStreamData);
320+
} else {
321+
process.nextTick(resumeStream, this[kStream]);
322+
}
321323
} else {
322324
this.emit('error', new errors.Error('ERR_HTTP2_INVALID_STREAM'));
323325
}

lib/internal/http2/core.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -300,11 +300,11 @@ function onStreamClose(code) {
300300
// Push a null so the stream can end whenever the client consumes
301301
// it completely.
302302
stream.push(null);
303-
304-
// Same as net.
305-
if (stream._readableState.length === 0) {
306-
stream.read(0);
307-
}
303+
// If the client hasn't tried to consume the stream and there is no
304+
// resume scheduled (which would indicate they would consume in the future),
305+
// then just dump the incoming data so that the stream can be destroyed.
306+
if (!stream[kState].didRead && !stream._readableState.resumeScheduled)
307+
stream.resume();
308308
}
309309
}
310310

@@ -1789,6 +1789,8 @@ class Http2Stream extends Duplex {
17891789
const ret = this[kHandle].trailers(headersList);
17901790
if (ret < 0)
17911791
this.destroy(new NghttpError(ret));
1792+
else
1793+
this[kMaybeDestroy]();
17921794
}
17931795

17941796
get closed() {

test/parallel/test-http2-client-upload-reject.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ fs.readFile(loc, common.mustCall((err, data) => {
2020
const server = http2.createServer();
2121

2222
server.on('stream', common.mustCall((stream) => {
23-
stream.on('close', common.mustCall(() => {
24-
assert.strictEqual(stream.rstCode, 0);
25-
}));
26-
27-
stream.respond({ ':status': 400 });
28-
stream.end();
23+
// Wait for some data to come through.
24+
setImmediate(() => {
25+
stream.on('close', common.mustCall(() => {
26+
assert.strictEqual(stream.rstCode, 0);
27+
}));
28+
29+
stream.respond({ ':status': 400 });
30+
stream.end();
31+
});
2932
}));
3033

3134
server.listen(0, common.mustCall(() => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
'use strict';
2+
3+
// Verifies that uploading data from a client works
4+
5+
const common = require('../common');
6+
if (!common.hasCrypto)
7+
common.skip('missing crypto');
8+
const assert = require('assert');
9+
const http2 = require('http2');
10+
const fs = require('fs');
11+
const fixtures = require('../common/fixtures');
12+
13+
const loc = fixtures.path('person-large.jpg');
14+
15+
assert(fs.existsSync(loc));
16+
17+
fs.readFile(loc, common.mustCall((err, data) => {
18+
assert.ifError(err);
19+
20+
const server = http2.createServer(common.mustCall((req, res) => {
21+
setImmediate(() => {
22+
res.writeHead(400);
23+
res.end();
24+
});
25+
}));
26+
27+
server.listen(0, common.mustCall(() => {
28+
const client = http2.connect(`http://localhost:${server.address().port}`);
29+
30+
const req = client.request({ ':method': 'POST' });
31+
req.on('response', common.mustCall((headers) => {
32+
assert.strictEqual(headers[':status'], 400);
33+
}));
34+
35+
req.resume();
36+
req.on('end', common.mustCall(() => {
37+
server.close();
38+
client.close();
39+
}));
40+
41+
const str = fs.createReadStream(loc);
42+
str.pipe(req);
43+
}));
44+
}));

0 commit comments

Comments
 (0)