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: #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 e5f8b08 commit b0c92ca

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
@@ -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
conststate=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',newerrors.Error('ERR_HTTP2_INVALID_STREAM'));
323325
}

‎lib/internal/http2/core.js‎

Lines changed: 7 additions & 5 deletions
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
constret=this[kHandle].trailers(headersList);
17901790
if(ret<0)
17911791
this.destroy(newNghttpError(ret));
1792+
else
1793+
this[kMaybeDestroy]();
17921794
}
17931795

17941796
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)