Skip to content

Commit b2fb1d7

Browse files
apapirovskiMylesBorins
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. PR-URL: #20621Fixes: #20060 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
1 parent 9e432ca commit b2fb1d7

4 files changed

Lines changed: 66 additions & 15 deletions

File tree

‎lib/internal/http2/compat.js‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -260,8 +260,6 @@ class Http2ServerRequest extends Readable {
260260
stream[kRequest]=this;
261261

262262
// Pause the stream..
263-
stream.pause();
264-
stream.on('data',onStreamData);
265263
stream.on('trailers',onStreamTrailers);
266264
stream.on('end',onStreamEnd);
267265
stream.on('error',onStreamError);
@@ -328,8 +326,12 @@ class Http2ServerRequest extends Readable {
328326
_read(nread){
329327
conststate=this[kState];
330328
if(!state.closed){
331-
state.didRead=true;
332-
process.nextTick(resumeStream,this[kStream]);
329+
if(!state.didRead){
330+
state.didRead=true;
331+
this[kStream].on('data',onStreamData);
332+
}else{
333+
process.nextTick(resumeStream,this[kStream]);
334+
}
333335
}else{
334336
this.emit('error',newERR_HTTP2_INVALID_STREAM());
335337
}

‎lib/internal/http2/core.js‎

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,11 @@ function onStreamClose(code) {
349349
// Push a null so the stream can end whenever the client consumes
350350
// it completely.
351351
stream.push(null);
352-
353-
// Same as net.
354-
if(stream.readableLength===0){
355-
stream.read(0);
356-
}
352+
// If the client hasn't tried to consume the stream and there is no
353+
// resume scheduled (which would indicate they would consume in the future),
354+
// then just dump the incoming data so that the stream can be destroyed.
355+
if(!stream[kState].didRead&&!stream._readableState.resumeScheduled)
356+
stream.resume();
357357
}
358358
}
359359

@@ -1795,6 +1795,8 @@ class Http2Stream extends Duplex {
17951795
constret=this[kHandle].trailers(headersList);
17961796
if(ret<0)
17971797
this.destroy(newNghttpError(ret));
1798+
else
1799+
this[kMaybeDestroy]();
17981800
}
17991801

18001802
getclosed(){

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

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ fs.readFile(loc, common.mustCall((err, data) => {
2020
constserver=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(()=>{
Lines changed: 44 additions & 0 deletions
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+
constcommon=require('../common');
6+
if(!common.hasCrypto)
7+
common.skip('missing crypto');
8+
constassert=require('assert');
9+
consthttp2=require('http2');
10+
constfs=require('fs');
11+
constfixtures=require('../common/fixtures');
12+
13+
constloc=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+
constserver=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+
constclient=http2.connect(`http://localhost:${server.address().port}`);
29+
30+
constreq=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+
conststr=fs.createReadStream(loc);
42+
str.pipe(req);
43+
}));
44+
}));

0 commit comments

Comments
 (0)