Skip to content

Commit 5833007

Browse files
mmomtchevtargos
authored andcommitted
http2: reinject data received before http2 is attached
Reinject the data already received from the TLS socket when the HTTP2 client is attached with a delay Fixes: #35475 PR-URL: #35678 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Alba Mendez <me@alba.sh> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Ricky Zhou <0x19951125@gmail.com>
1 parent 883ed4b commit 5833007

4 files changed

Lines changed: 106 additions & 2 deletions

File tree

‎lib/internal/http2/core.js‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,7 @@ function finishSessionClose(session, error) {
10371037
if(socket&&!socket.destroyed){
10381038
// Always wait for writable side to finish.
10391039
socket.end((err)=>{
1040-
debugSessionObj(session,'finishSessionClose socket end',err);
1040+
debugSessionObj(session,'finishSessionClose socket end',err,error);
10411041
// Due to the way the underlying stream is handled in Http2Session we
10421042
// won't get graceful Readable end from the other side even if it was sent
10431043
// as the stream is already considered closed and will neither be read
@@ -1055,7 +1055,7 @@ function finishSessionClose(session, error) {
10551055
}
10561056

10571057
functioncloseSession(session,code,error){
1058-
debugSessionObj(session,'start closing/destroying');
1058+
debugSessionObj(session,'start closing/destroying',error);
10591059

10601060
conststate=session[kState];
10611061
state.flags|=SESSION_FLAGS_DESTROYED;
@@ -3140,6 +3140,17 @@ function connect(authority, options, listener) {
31403140

31413141
if(typeoflistener==='function')
31423142
session.once('connect',listener);
3143+
3144+
debug('Http2Session connect',options.createConnection);
3145+
// Socket already has some buffered data - emulate receiving it
3146+
// https://github.com/nodejs/node/issues/35475
3147+
if(typeofoptions.createConnection==='function'){
3148+
letbuf;
3149+
while((buf=socket.read())!==null){
3150+
debug(`Http2Session connect: injecting ${buf.length} already in buffer`);
3151+
session[kHandle].receive(buf);
3152+
}
3153+
}
31433154
returnsession;
31443155
}
31453156

‎src/node_http2.cc‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1829,6 +1829,33 @@ void Http2Session::Consume(Local<Object> stream_obj) {
18291829
Debug(this, "i/o stream consumed");
18301830
}
18311831

1832+
// Allow injecting of data from JS
1833+
// This is used when the socket has already some data received
1834+
// before our listener was attached
1835+
// https://github.com/nodejs/node/issues/35475
1836+
voidHttp2Session::Receive(const FunctionCallbackInfo<Value>& args) {
1837+
Http2Session* session;
1838+
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1839+
CHECK(args[0]->IsObject());
1840+
1841+
ArrayBufferViewContents<char> buffer(args[0]);
1842+
constchar* data = buffer.data();
1843+
size_t len = buffer.length();
1844+
Debug(session, "Receiving %zu bytes injected from JS", len);
1845+
1846+
// Copy given buffer
1847+
while (len > 0) {
1848+
uv_buf_t buf = session->OnStreamAlloc(len);
1849+
size_t copy = buf.len > len ? len : buf.len;
1850+
memcpy(buf.base, data, copy);
1851+
buf.len = copy;
1852+
session->OnStreamRead(copy, buf);
1853+
1854+
data += copy;
1855+
len -= copy;
1856+
}
1857+
}
1858+
18321859
Http2Stream* Http2Stream::New(Http2Session* session,
18331860
int32_t id,
18341861
nghttp2_headers_category category,
@@ -3054,6 +3081,7 @@ void Initialize(Local<Object> target,
30543081
env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
30553082
env->SetProtoMethod(session, "ping", Http2Session::Ping);
30563083
env->SetProtoMethod(session, "consume", Http2Session::Consume);
3084+
env->SetProtoMethod(session, "receive", Http2Session::Receive);
30573085
env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
30583086
env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
30593087
env->SetProtoMethod(session, "settings", Http2Session::Settings);

‎src/node_http2.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ class Http2Session : public AsyncWrap,
694694
// The JavaScript API
695695
staticvoidNew(const v8::FunctionCallbackInfo<v8::Value>& args);
696696
staticvoidConsume(const v8::FunctionCallbackInfo<v8::Value>& args);
697+
staticvoidReceive(const v8::FunctionCallbackInfo<v8::Value>& args);
697698
staticvoidDestroy(const v8::FunctionCallbackInfo<v8::Value>& args);
698699
staticvoidSettings(const v8::FunctionCallbackInfo<v8::Value>& args);
699700
staticvoidRequest(const v8::FunctionCallbackInfo<v8::Value>& args);
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
constcommon=require('../common');
4+
if(!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
if(!common.hasMultiLocalhost())
8+
common.skip('platform-specific test.');
9+
10+
consthttp2=require('http2');
11+
constassert=require('assert');
12+
consttls=require('tls');
13+
constfixtures=require('../common/fixtures');
14+
15+
constserverOptions={
16+
key: fixtures.readKey('agent1-key.pem'),
17+
cert: fixtures.readKey('agent1-cert.pem')
18+
};
19+
constserver=http2.createSecureServer(serverOptions,(req,res)=>{
20+
console.log(`Connect from: ${req.connection.remoteAddress}`);
21+
assert.strictEqual(req.connection.remoteAddress,'127.0.0.2');
22+
23+
req.on('end',common.mustCall(()=>{
24+
res.writeHead(200,{'Content-Type': 'text/plain'});
25+
res.end(`You are from: ${req.connection.remoteAddress}`);
26+
}));
27+
req.resume();
28+
});
29+
30+
server.listen(0,'127.0.0.1',common.mustCall(()=>{
31+
constoptions={
32+
ALPNProtocols: ['h2'],
33+
host: '127.0.0.1',
34+
servername: 'localhost',
35+
localAddress: '127.0.0.2',
36+
port: server.address().port,
37+
rejectUnauthorized: false
38+
};
39+
40+
console.log('Server ready',server.address().port);
41+
42+
constsocket=tls.connect(options,async()=>{
43+
44+
console.log('TLS Connected!');
45+
46+
setTimeout(()=>{
47+
48+
constclient=http2.connect(
49+
'https://localhost:'+server.address().port,
50+
{ ...options,createConnection: ()=>socket}
51+
);
52+
constreq=client.request({
53+
':path': '/'
54+
});
55+
req.on('data',()=>req.resume());
56+
req.on('end',common.mustCall(function(){
57+
client.close();
58+
req.close();
59+
server.close();
60+
}));
61+
req.end();
62+
},1000);
63+
});
64+
}));

0 commit comments

Comments
 (0)