Skip to content

Commit 57f2fe0

Browse files
mmomtchevBethGriggs
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 <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Rich Trott <[email protected]> Reviewed-By: Alba Mendez <[email protected]> Reviewed-By: Franziska Hinkelmann <[email protected]> Reviewed-By: Ricky Zhou <[email protected]>
1 parent 2dbaaf9 commit 57f2fe0

File tree

4 files changed

+106
-2
lines changed

4 files changed

+106
-2
lines changed

‎lib/internal/http2/core.js‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1028,7 +1028,7 @@ function finishSessionClose(session, error){
10281028
if(socket&&!socket.destroyed){
10291029
// Always wait for writable side to finish.
10301030
socket.end((err)=>{
1031-
debugSessionObj(session,'finishSessionClose socket end',err);
1031+
debugSessionObj(session,'finishSessionClose socket end',err,error);
10321032
// Due to the way the underlying stream is handled in Http2Session we
10331033
// won't get graceful Readable end from the other side even if it was sent
10341034
// as the stream is already considered closed and will neither be read
@@ -1046,7 +1046,7 @@ function finishSessionClose(session, error){
10461046
}
10471047

10481048
functioncloseSession(session,code,error){
1049-
debugSessionObj(session,'start closing/destroying');
1049+
debugSessionObj(session,'start closing/destroying',error);
10501050

10511051
conststate=session[kState];
10521052
state.flags|=SESSION_FLAGS_DESTROYED;
@@ -3106,6 +3106,17 @@ function connect(authority, options, listener){
31063106

31073107
if(typeoflistener==='function')
31083108
session.once('connect',listener);
3109+
3110+
debug('Http2Session connect',options.createConnection);
3111+
// Socket already has some buffered data - emulate receiving it
3112+
// https://github.com/nodejs/node/issues/35475
3113+
if(typeofoptions.createConnection==='function'){
3114+
letbuf;
3115+
while((buf=socket.read())!==null){
3116+
debug(`Http2Session connect: injecting ${buf.length} already in buffer`);
3117+
session[kHandle].receive(buf);
3118+
}
3119+
}
31093120
returnsession;
31103121
}
31113122

‎src/node_http2.cc‎

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

1825+
// Allow injecting of data from JS
1826+
// This is used when the socket has already some data received
1827+
// before our listener was attached
1828+
// https://github.com/nodejs/node/issues/35475
1829+
voidHttp2Session::Receive(const FunctionCallbackInfo<Value>& args){
1830+
Http2Session* session;
1831+
ASSIGN_OR_RETURN_UNWRAP(&session, args.Holder());
1832+
CHECK(args[0]->IsObject());
1833+
1834+
ArrayBufferViewContents<char> buffer(args[0]);
1835+
constchar* data = buffer.data();
1836+
size_t len = buffer.length();
1837+
Debug(session, "Receiving %zu bytes injected from JS", len);
1838+
1839+
// Copy given buffer
1840+
while (len > 0){
1841+
uv_buf_t buf = session->OnStreamAlloc(len);
1842+
size_t copy = buf.len > len ? len : buf.len;
1843+
memcpy(buf.base, data, copy);
1844+
buf.len = copy;
1845+
session->OnStreamRead(copy, buf);
1846+
1847+
data += copy;
1848+
len -= copy;
1849+
}
1850+
}
1851+
18251852
Http2Stream* Http2Stream::New(Http2Session* session,
18261853
int32_t id,
18271854
nghttp2_headers_category category,
@@ -3047,6 +3074,7 @@ void Initialize(Local<Object> target,
30473074
env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc);
30483075
env->SetProtoMethod(session, "ping", Http2Session::Ping);
30493076
env->SetProtoMethod(session, "consume", Http2Session::Consume);
3077+
env->SetProtoMethod(session, "receive", Http2Session::Receive);
30503078
env->SetProtoMethod(session, "destroy", Http2Session::Destroy);
30513079
env->SetProtoMethod(session, "goaway", Http2Session::Goaway);
30523080
env->SetProtoMethod(session, "settings", Http2Session::Settings);

‎src/node_http2.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ class Http2Session : public AsyncWrap,
695695
// The JavaScript API
696696
staticvoidNew(const v8::FunctionCallbackInfo<v8::Value>& args);
697697
staticvoidConsume(const v8::FunctionCallbackInfo<v8::Value>& args);
698+
staticvoidReceive(const v8::FunctionCallbackInfo<v8::Value>& args);
698699
staticvoidDestroy(const v8::FunctionCallbackInfo<v8::Value>& args);
699700
staticvoidSettings(const v8::FunctionCallbackInfo<v8::Value>& args);
700701
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)