Skip to content

Commit 28d8623

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 <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 1901610 commit 28d8623

File tree

4 files changed

+106
-2
lines changed

4 files changed

+106
-2
lines changed

lib/internal/http2/core.js

+13-2
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
function closeSession(session, code, error) {
1049-
debugSessionObj(session, 'start closing/destroying');
1049+
debugSessionObj(session, 'start closing/destroying', error);
10501050

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

31073107
if (typeof listener === '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 (typeof options.createConnection === 'function') {
3114+
let buf;
3115+
while ((buf = socket.read()) !== null) {
3116+
debug(`Http2Session connect: injecting ${buf.length} already in buffer`);
3117+
session[kHandle].receive(buf);
3118+
}
3119+
}
31093120
return session;
31103121
}
31113122

src/node_http2.cc

+28
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+
void Http2Session::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+
const char* 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

+1
Original file line numberDiff line numberDiff line change
@@ -695,6 +695,7 @@ class Http2Session : public AsyncWrap,
695695
// The JavaScript API
696696
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
697697
static void Consume(const v8::FunctionCallbackInfo<v8::Value>& args);
698+
static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args);
698699
static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args);
699700
static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args);
700701
static void Request(const v8::FunctionCallbackInfo<v8::Value>& args);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Flags: --expose-internals
2+
'use strict';
3+
const common = require('../common');
4+
if (!common.hasCrypto)
5+
common.skip('missing crypto');
6+
7+
if (!common.hasMultiLocalhost())
8+
common.skip('platform-specific test.');
9+
10+
const http2 = require('http2');
11+
const assert = require('assert');
12+
const tls = require('tls');
13+
const fixtures = require('../common/fixtures');
14+
15+
const serverOptions = {
16+
key: fixtures.readKey('agent1-key.pem'),
17+
cert: fixtures.readKey('agent1-cert.pem')
18+
};
19+
const server = 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+
const options = {
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+
const socket = tls.connect(options, async () => {
43+
44+
console.log('TLS Connected!');
45+
46+
setTimeout(() => {
47+
48+
const client = http2.connect(
49+
'https://localhost:' + server.address().port,
50+
{ ...options, createConnection: () => socket }
51+
);
52+
const req = 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)