Skip to content

Commit

Permalink
http2: add server handshake utility
Browse files Browse the repository at this point in the history
PR-URL: #51172
Reviewed-By: Matteo Collina <[email protected]>
  • Loading branch information
devsnek authored and targos committed Feb 15, 2024
1 parent 119e045 commit d8aa2ba
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 7 deletions.
13 changes: 13 additions & 0 deletions doc/api/http2.md
Original file line number Diff line number Diff line change
Expand Up @@ -2890,6 +2890,19 @@ added: v8.4.0
Returns a [HTTP/2 Settings Object][] containing the deserialized settings from
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`

<!-- YAML
Expand Down
2 changes: 2 additions & 0 deletions lib/http2.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
performServerHandshake,
sensitiveHeaders,
Http2ServerRequest,
Http2ServerResponse,
Expand All @@ -21,6 +22,7 @@ module.exports = {
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
performServerHandshake,
sensitiveHeaders,
Http2ServerRequest,
Http2ServerResponse,
Expand Down
20 changes: 13 additions & 7 deletions lib/internal/http2/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -1228,12 +1228,6 @@ class Http2Session extends EventEmitter {
constructor(type, options, socket) {
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
// constructor is not exported directly for users.

Expand All @@ -1245,6 +1239,12 @@ class Http2Session extends EventEmitter {

socket[kSession] = this;

if (!socket._handle || !socket._handle.isStreamBase) {
socket = new JSStreamSocket(socket);
}
socket.on('error', socketOnError);
socket.on('close', socketOnClose);

this[kState] = {
destroyCode: NGHTTP2_NO_ERROR,
flags: SESSION_FLAGS_PENDING,
Expand Down Expand Up @@ -1644,7 +1644,7 @@ class ServerHttp2Session extends Http2Session {
// not be an issue in practice. Additionally, the 'priority' event on
// server instances (or any other object) is fully undocumented.
this[kNativeFields][kSessionPriorityListenerCount] =
server.listenerCount('priority');
server ? server.listenerCount('priority') : 0;
}

get server() {
Expand Down Expand Up @@ -3435,6 +3435,11 @@ function getUnpackedSettings(buf, options = kEmptyObject) {
return settings;
}

function performServerHandshake(socket, options = {}) {
options = initializeOptions(options);
return new ServerHttp2Session(options, socket, undefined);
}

binding.setCallbackFunctions(
onSessionInternalError,
onPriority,
Expand All @@ -3458,6 +3463,7 @@ module.exports = {
getDefaultSettings,
getPackedSettings,
getUnpackedSettings,
performServerHandshake,
sensitiveHeaders: kSensitiveHeaders,
Http2Session,
Http2Stream,
Expand Down
9 changes: 9 additions & 0 deletions lib/internal/js_stream_socket.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ let debug = require('internal/util/debuglog').debuglog(
);
const { owner_symbol } = require('internal/async_hooks').symbols;
const { ERR_STREAM_WRAP } = require('internal/errors').codes;
const { kSession } = require('internal/stream_base_commons');

const kCurrentWriteRequest = Symbol('kCurrentWriteRequest');
const kCurrentShutdownRequest = Symbol('kCurrentShutdownRequest');
Expand Down Expand Up @@ -263,6 +264,14 @@ class JSStreamSocket extends Socket {
cb();
});
}

get [kSession]() {
return this.stream[kSession];
}

set [kSession](session) {
this.stream[kSession] = session;
}
}

module.exports = JSStreamSocket;
48 changes: 48 additions & 0 deletions test/parallel/test-http2-perform-server-handshake.js
Original file line number Diff line number Diff line change
@@ -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' });
}

0 comments on commit d8aa2ba

Please sign in to comment.