GeezerVison is showing github.com — http2: reinject data received before http2 is attached · nodejs/node@5833007
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/http2/core.js +13-2Lines changed: 13 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1037,7 +1037,7 @@ function finishSessionClose(session, error) { | 1037 | 1037 | if (socket && !socket.destroyed) { | 1038 | 1038 | // Always wait for writable side to finish. | 1039 | 1039 | socket.end((err) => { | 1040 | | - debugSessionObj(session, 'finishSessionClose socket end', err); | | 1040 | + debugSessionObj(session, 'finishSessionClose socket end', err, error); | 1041 | 1041 | // Due to the way the underlying stream is handled in Http2Session we | 1042 | 1042 | // won't get graceful Readable end from the other side even if it was sent | 1043 | 1043 | // as the stream is already considered closed and will neither be read | @@ -1055,7 +1055,7 @@ function finishSessionClose(session, error) { | 1055 | 1055 | } | 1056 | 1056 |
| 1057 | 1057 | function closeSession(session, code, error) { | 1058 | | - debugSessionObj(session, 'start closing/destroying'); | | 1058 | + debugSessionObj(session, 'start closing/destroying', error); | 1059 | 1059 |
| 1060 | 1060 | const state = session[kState]; | 1061 | 1061 | state.flags |= SESSION_FLAGS_DESTROYED; | @@ -3140,6 +3140,17 @@ function connect(authority, options, listener) { | 3140 | 3140 |
| 3141 | 3141 | if (typeof listener === 'function') | 3142 | 3142 | 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 | + } | 3143 | 3154 | return session; | 3144 | 3155 | } | 3145 | 3156 |
| |
Collapse file Copy file name to clipboardExpand all lines: src/node_http2.cc +28Lines changed: 28 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1829,6 +1829,33 @@ void Http2Session::Consume(Local<Object> stream_obj) { | 1829 | 1829 | Debug(this, "i/o stream consumed"); | 1830 | 1830 | } | 1831 | 1831 |
| | 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 | + | 1832 | 1859 | Http2Stream* Http2Stream::New(Http2Session* session, | 1833 | 1860 | int32_t id, | 1834 | 1861 | nghttp2_headers_category category, | @@ -3054,6 +3081,7 @@ void Initialize(Local<Object> target, | 3054 | 3081 | env->SetProtoMethod(session, "altsvc", Http2Session::AltSvc); | 3055 | 3082 | env->SetProtoMethod(session, "ping", Http2Session::Ping); | 3056 | 3083 | env->SetProtoMethod(session, "consume", Http2Session::Consume); | | 3084 | + env->SetProtoMethod(session, "receive", Http2Session::Receive); | 3057 | 3085 | env->SetProtoMethod(session, "destroy", Http2Session::Destroy); | 3058 | 3086 | env->SetProtoMethod(session, "goaway", Http2Session::Goaway); | 3059 | 3087 | env->SetProtoMethod(session, "settings", Http2Session::Settings); | |
Collapse file Copy file name to clipboardExpand all lines: src/node_http2.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -694,6 +694,7 @@ class Http2Session : public AsyncWrap, | 694 | 694 | // The JavaScript API | 695 | 695 | static void New(const v8::FunctionCallbackInfo<v8::Value>& args); | 696 | 696 | static void Consume(const v8::FunctionCallbackInfo<v8::Value>& args); | | 697 | + static void Receive(const v8::FunctionCallbackInfo<v8::Value>& args); | 697 | 698 | static void Destroy(const v8::FunctionCallbackInfo<v8::Value>& args); | 698 | 699 | static void Settings(const v8::FunctionCallbackInfo<v8::Value>& args); | 699 | 700 | static void Request(const v8::FunctionCallbackInfo<v8::Value>& args); | |
Collapse file Copy file name to clipboard +64Lines changed: 64 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +})); |
|