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 <[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 883ed4b commit 5833007

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
@@ -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
function closeSession(session, code, error) {
1058-
debugSessionObj(session, 'start closing/destroying');
1058+
debugSessionObj(session, 'start closing/destroying', error);
10591059

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

31413141
if (typeof listener === '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 (typeof options.createConnection === 'function') {
3148+
let buf;
3149+
while ((buf = socket.read()) !== null) {
3150+
debug(`Http2Session connect: injecting ${buf.length} already in buffer`);
3151+
session[kHandle].receive(buf);
3152+
}
3153+
}
31433154
return session;
31443155
}
31453156

src/node_http2.cc

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

+1
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,7 @@ class Http2Session : public AsyncWrap,
694694
// The JavaScript API
695695
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
696696
static void Consume(const v8::FunctionCallbackInfo<v8::Value>& args);
697+
static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args);
697698
static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args);
698699
static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args);
699700
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)