http2: add server handshake utility

PR-URL: https://github.com/nodejs/node/pull/51172
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
snek 2024-01-12 08:09:48 -08:00 committed by GitHub
parent d44814ca68
commit c50524a9fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 7 deletions

View File

@ -2890,6 +2890,19 @@ added: v8.4.0
Returns a [HTTP/2 Settings Object][] containing the deserialized settings from Returns a [HTTP/2 Settings Object][] containing the deserialized settings from
the given `Buffer` as generated by `http2.getPackedSettings()`. the given `Buffer` as generated by `http2.getPackedSettings()`.
### `http2.performServerHandshake(socket[, options])`
<!-- YAML
added: REPLACEME
-->
* `socket` {stream.Duplex}
* `options` {Object}
* ...: Any [`http2.createServer()`][] option can be provided.
* Returns: {ServerHttp2Session}
Create an HTTP/2 server session from an existing socket.
### `http2.sensitiveHeaders` ### `http2.sensitiveHeaders`
<!-- YAML <!-- YAML

View File

@ -8,6 +8,7 @@ const {
getDefaultSettings, getDefaultSettings,
getPackedSettings, getPackedSettings,
getUnpackedSettings, getUnpackedSettings,
performServerHandshake,
sensitiveHeaders, sensitiveHeaders,
Http2ServerRequest, Http2ServerRequest,
Http2ServerResponse, Http2ServerResponse,
@ -21,6 +22,7 @@ module.exports = {
getDefaultSettings, getDefaultSettings,
getPackedSettings, getPackedSettings,
getUnpackedSettings, getUnpackedSettings,
performServerHandshake,
sensitiveHeaders, sensitiveHeaders,
Http2ServerRequest, Http2ServerRequest,
Http2ServerResponse, Http2ServerResponse,

View File

@ -1228,12 +1228,6 @@ class Http2Session extends EventEmitter {
constructor(type, options, socket) { constructor(type, options, socket) {
super(); super();
if (!socket._handle || !socket._handle.isStreamBase) {
socket = new JSStreamSocket(socket);
}
socket.on('error', socketOnError);
socket.on('close', socketOnClose);
// No validation is performed on the input parameters because this // No validation is performed on the input parameters because this
// constructor is not exported directly for users. // constructor is not exported directly for users.
@ -1245,6 +1239,12 @@ class Http2Session extends EventEmitter {
socket[kSession] = this; socket[kSession] = this;
if (!socket._handle || !socket._handle.isStreamBase) {
socket = new JSStreamSocket(socket);
}
socket.on('error', socketOnError);
socket.on('close', socketOnClose);
this[kState] = { this[kState] = {
destroyCode: NGHTTP2_NO_ERROR, destroyCode: NGHTTP2_NO_ERROR,
flags: SESSION_FLAGS_PENDING, flags: SESSION_FLAGS_PENDING,
@ -1644,7 +1644,7 @@ class ServerHttp2Session extends Http2Session {
// not be an issue in practice. Additionally, the 'priority' event on // not be an issue in practice. Additionally, the 'priority' event on
// server instances (or any other object) is fully undocumented. // server instances (or any other object) is fully undocumented.
this[kNativeFields][kSessionPriorityListenerCount] = this[kNativeFields][kSessionPriorityListenerCount] =
server.listenerCount('priority'); server ? server.listenerCount('priority') : 0;
} }
get server() { get server() {
@ -3435,6 +3435,11 @@ function getUnpackedSettings(buf, options = kEmptyObject) {
return settings; return settings;
} }
function performServerHandshake(socket, options = {}) {
options = initializeOptions(options);
return new ServerHttp2Session(options, socket, undefined);
}
binding.setCallbackFunctions( binding.setCallbackFunctions(
onSessionInternalError, onSessionInternalError,
onPriority, onPriority,
@ -3458,6 +3463,7 @@ module.exports = {
getDefaultSettings, getDefaultSettings,
getPackedSettings, getPackedSettings,
getUnpackedSettings, getUnpackedSettings,
performServerHandshake,
sensitiveHeaders: kSensitiveHeaders, sensitiveHeaders: kSensitiveHeaders,
Http2Session, Http2Session,
Http2Stream, Http2Stream,

View File

@ -17,6 +17,7 @@ let debug = require('internal/util/debuglog').debuglog(
); );
const { owner_symbol } = require('internal/async_hooks').symbols; const { owner_symbol } = require('internal/async_hooks').symbols;
const { ERR_STREAM_WRAP } = require('internal/errors').codes; const { ERR_STREAM_WRAP } = require('internal/errors').codes;
const { kSession } = require('internal/stream_base_commons');
const kCurrentWriteRequest = Symbol('kCurrentWriteRequest'); const kCurrentWriteRequest = Symbol('kCurrentWriteRequest');
const kCurrentShutdownRequest = Symbol('kCurrentShutdownRequest'); const kCurrentShutdownRequest = Symbol('kCurrentShutdownRequest');
@ -263,6 +264,14 @@ class JSStreamSocket extends Socket {
cb(); cb();
}); });
} }
get [kSession]() {
return this.stream[kSession];
}
set [kSession](session) {
this.stream[kSession] = session;
}
} }
module.exports = JSStreamSocket; module.exports = JSStreamSocket;

View File

@ -0,0 +1,48 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const assert = require('assert');
const http2 = require('http2');
const stream = require('stream');
const makeDuplexPair = require('../common/duplexpair');
// Basic test
{
const { clientSide, serverSide } = makeDuplexPair();
const client = http2.connect('http://example.com', {
createConnection: () => clientSide,
});
const session = http2.performServerHandshake(serverSide);
session.on('stream', common.mustCall((stream, headers) => {
assert.strictEqual(headers[':path'], '/test');
stream.respond({
':status': 200,
});
stream.end('hi!');
}));
const req = client.request({ ':path': '/test' });
req.on('response', common.mustCall());
req.end();
}
// Double bind should fail
{
const socket = new stream.Duplex({
read() {},
write() {},
});
http2.performServerHandshake(socket);
assert.throws(() => {
http2.performServerHandshake(socket);
}, { code: 'ERR_HTTP2_SOCKET_BOUND' });
}