Skip to content

Commit 4840926

Browse files
committed
quic: split quic connection & session, rename autoWrap to autoStart
Signed-off-by: Tim Perry <pimterry@gmail.com>
1 parent 8a3b183 commit 4840926

62 files changed

Lines changed: 1883 additions & 2086 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎benchmark/quic/handshake.js‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const { createPrivateKey } = require('crypto');
1111

1212
const bench = common.createBenchmark(main, {
1313
// 'raw' negotiates a non-HTTP ALPN and does no application work.
14-
// 'h3' installs HTTP/3 on every session, so each peer also builds an
15-
// nghttp3 connection and its control/QPACK streams.
14+
// 'h3' negotiates HTTP/3, so the server also builds an nghttp3 connection
15+
// and its control/QPACK streams for every session.
1616
protocol: ['raw', 'h3'],
1717
concurrency: [1, 10],
1818
n: [1000],
@@ -23,8 +23,7 @@ async function main({ protocol, concurrency, n }) {
2323

2424
const key = createPrivateKey(fixtures.readKey('agent1-key.pem'));
2525
const cert = fixtures.readKey('agent1-cert.pem');
26-
const http3 = protocol === 'h3';
27-
const alpn = http3 ? 'h3' : 'quic-bench';
26+
const alpn = protocol === 'h3' ? 'h3' : 'quic-bench';
2827

2928
const endpoint = await listen((session) => {
3029
// A benchmark peer never reads these; swallow so a torn-down session

‎doc/api/quic.md‎

Lines changed: 518 additions & 412 deletions
Large diffs are not rendered by default.

‎lib/internal/quic/http3.js‎

Lines changed: 30 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
const {
44
BigInt,
5-
FunctionPrototypeBind,
65
NumberIsInteger,
7-
SymbolAsyncDispose,
6+
PromiseReject,
87
} = primordials;
98

109
const {
@@ -17,29 +16,25 @@ if (!process.features.quic || !getOptionValue('--experimental-quic')) {
1716

1817
// Internal, experimental HTTP/3 layer over node:quic.
1918
//
20-
// A QuicSession created without an application is a raw QUIC session.
21-
// Attaching an Http3Session makes it an HTTP/3 one, and from then on its
22-
// streams are HTTP/3 request streams.
19+
// An Http3Session is started on a QuicConnection that has no session yet,
20+
// and from then on the connection's streams are HTTP/3 request streams.
2321

2422
const {
23+
QuicSessionBase,
2524
createApplicationStream,
26-
getQuicSessionHandle,
27-
getQuicSessionState,
28-
isQuicSession,
25+
getApplicationCallback,
26+
isServerConnection,
2927
setApplicationCallback,
3028
} = require('internal/quic/quic');
3129

3230
const {
33-
kInspect,
3431
kPrivateConstructor,
3532
} = require('internal/quic/symbols');
3633
const { kEmptyObject } = require('internal/util');
37-
const { inspect } = require('internal/util/inspect');
3834

3935
const {
4036
QUIC_APPLICATION_HTTP3,
4137
STREAM_DIRECTION_BIDIRECTIONAL: kStreamDirectionBidirectional,
42-
kHttp3Settings,
4338
} = internalBinding('quic');
4439

4540
const {
@@ -50,7 +45,6 @@ const {
5045

5146
const {
5247
codes: {
53-
ERR_ILLEGAL_CONSTRUCTOR,
5448
ERR_INVALID_ARG_TYPE,
5549
ERR_INVALID_STATE,
5650
ERR_OUT_OF_RANGE,
@@ -117,196 +111,75 @@ function prepareH3Settings(settings) {
117111
};
118112
}
119113

120-
function checkAttachable(session, state) {
121-
if (session.destroyed) {
122-
throw new ERR_INVALID_STATE(
123-
'An application cannot be attached to a destroyed QUIC session');
124-
}
125-
if (state.applicationType !== 0) {
126-
throw new ERR_INVALID_STATE(
127-
'The QUIC session already has an application attached');
128-
}
129-
}
130-
131-
class Http3Session {
132-
#session;
133-
#onstream;
134-
#ongoaway;
135-
#onorigin;
136-
#onsettings;
137-
#onerror;
138-
114+
class Http3Session extends QuicSessionBase {
139115
/**
140-
* Attaches HTTP/3 to a QuicSession that has not yet become active.
141-
* @param {QuicSession} session the QUIC session to attach to
116+
* Starts HTTP/3 on a QuicConnection that has no session yet.
117+
* @param {QuicConnection} connection
142118
* @param {object} [options]
143119
* @param {ApplicationOptions} [options.settings]
144120
* @param {Function} [options.ongoaway]
145121
* @param {Function} [options.onorigin]
146122
* @param {Function} [options.onsettings]
147123
* @returns {Http3Session}
148124
*/
149-
static from(session, options) {
150-
return new Http3Session(kPrivateConstructor, session, options);
125+
static start(connection, options) {
126+
return new Http3Session(kPrivateConstructor, connection, options);
151127
}
152128

153-
constructor(privateSymbol, session, options = kEmptyObject) {
154-
if (privateSymbol !== kPrivateConstructor) {
155-
throw new ERR_ILLEGAL_CONSTRUCTOR();
156-
}
157-
if (!isQuicSession(session)) {
158-
throw new ERR_INVALID_ARG_TYPE('session', 'QuicSession', session);
159-
}
129+
constructor(privateSymbol, connection, options = kEmptyObject) {
160130
validateObject(options, 'options');
161131
const { ongoaway, onorigin, onsettings, settings } = options;
162132
if (ongoaway !== undefined) validateFunction(ongoaway, 'options.ongoaway');
163133
if (onorigin !== undefined) validateFunction(onorigin, 'options.onorigin');
164134
if (onsettings !== undefined) {
165135
validateFunction(onsettings, 'options.onsettings');
166136
}
167-
const state = getQuicSessionState(session);
168-
169137
let preparedSettings;
170138
if (settings !== undefined) {
171139
validateObject(settings, 'options.settings');
172140
preparedSettings = prepareH3Settings(settings);
173141
}
174142

175-
// Reading settings could call getters and go into JS, so do this after:
176-
checkAttachable(session, state);
143+
// Reading settings could call getters and go into JS, so start after:
144+
super(privateSymbol, connection, QUIC_APPLICATION_HTTP3, preparedSettings);
177145

178-
const handle = getQuicSessionHandle(session);
179-
if (preparedSettings !== undefined) handle[kHttp3Settings] = preparedSettings;
180-
state.applicationType = QUIC_APPLICATION_HTTP3;
181-
this.#session = session;
182-
if (ongoaway !== undefined) {
183-
this.#ongoaway = ongoaway;
184-
setApplicationCallback(session, 'ongoaway', this.#bind(ongoaway));
185-
}
186-
if (onorigin !== undefined) {
187-
this.#onorigin = onorigin;
188-
setApplicationCallback(session, 'onorigin', this.#bind(onorigin));
189-
}
190-
if (onsettings !== undefined) {
191-
this.#onsettings = onsettings;
192-
setApplicationCallback(session, 'onapplication', this.#bind(onsettings));
193-
}
146+
if (ongoaway !== undefined) this.ongoaway = ongoaway;
147+
if (onorigin !== undefined) this.onorigin = onorigin;
148+
if (onsettings !== undefined) this.onsettings = onsettings;
194149
}
195150

196-
/**
197-
* The QUIC session carrying this HTTP/3 session
198-
* @type {QuicSession}
199-
*/
200-
get quicSession() { return this.#session; }
201-
202151
/**
203152
* The settings in effect, including any update from the peer's SETTINGS
204153
* frame, which may arrive after the session opens. Null once destroyed.
205154
* @type {ApplicationOptions|null}
206155
*/
207-
get settings() { return this.#session.applicationOptions; }
208-
209-
/** @type {quic.QuicSession.Stats} */
210-
get stats() { return this.#session.stats; }
211-
212-
// Ensure that 'this' in callbacks registered here is the Http3Session
213-
#bind(fn) {
214-
return fn === undefined ? undefined : FunctionPrototypeBind(fn, this);
215-
}
216-
217-
/**
218-
* Called with each request stream the client opens. HTTP/3 has no
219-
* server-initiated requests, so this never fires on a client session.
220-
* @type {Function|undefined}
221-
*/
222-
get onstream() { return this.#onstream; }
223-
set onstream(fn) {
224-
if (fn !== undefined) validateFunction(fn, 'onstream');
225-
setApplicationCallback(this.#session, 'onstream', this.#bind(fn));
226-
this.#onstream = fn;
227-
}
156+
get settings() { return this.connection.applicationOptions; }
228157

229158
/** @type {Function|undefined} */
230-
get ongoaway() { return this.#ongoaway; }
231-
set ongoaway(fn) {
232-
if (fn !== undefined) validateFunction(fn, 'ongoaway');
233-
setApplicationCallback(this.#session, 'ongoaway', this.#bind(fn));
234-
this.#ongoaway = fn;
235-
}
159+
get ongoaway() { return getApplicationCallback(this.connection, 'ongoaway'); }
160+
set ongoaway(fn) { setApplicationCallback(this.connection, 'ongoaway', fn, this); }
236161

237162
/** @type {Function|undefined} */
238-
get onorigin() { return this.#onorigin; }
239-
set onorigin(fn) {
240-
if (fn !== undefined) validateFunction(fn, 'onorigin');
241-
setApplicationCallback(this.#session, 'onorigin', this.#bind(fn));
242-
this.#onorigin = fn;
243-
}
163+
get onorigin() { return getApplicationCallback(this.connection, 'onorigin'); }
164+
set onorigin(fn) { setApplicationCallback(this.connection, 'onorigin', fn, this); }
244165

245166
/** @type {Function|undefined} */
246-
get onsettings() { return this.#onsettings; }
167+
get onsettings() { return getApplicationCallback(this.connection, 'onapplication'); }
247168
set onsettings(fn) {
248-
if (fn !== undefined) validateFunction(fn, 'onsettings');
249-
setApplicationCallback(this.#session, 'onapplication', this.#bind(fn));
250-
this.#onsettings = fn;
169+
setApplicationCallback(this.connection, 'onapplication', fn, this, 'onsettings');
251170
}
252171

253-
/**
254-
* Called with a session error after the QUIC session's own onerror, which
255-
* stays the transport-level handler. Either may be set independently.
256-
* @type {Function|undefined}
257-
*/
258-
get onerror() { return this.#onerror; }
259-
set onerror(fn) {
260-
if (fn !== undefined) validateFunction(fn, 'onerror');
261-
setApplicationCallback(this.#session, 'onapperror', this.#bind(fn));
262-
this.#onerror = fn;
263-
}
264-
265-
/** @type {Promise<object>} */
266-
get opened() { return this.#session.opened; }
267-
268-
/** @type {Promise<void>} */
269-
get closed() { return this.#session.closed; }
270-
271-
/** @type {boolean} */
272-
get closing() { return this.#session.closing; }
273-
274-
/** @type {boolean} */
275-
get destroyed() { return this.#session.destroyed; }
276-
277172
/**
278173
* Opens a request stream.
279174
* @returns {Promise<QuicStream>}
280175
*/
281-
async createBidirectionalStream(options) {
282-
if (getQuicSessionState(this.#session).isServer) {
283-
throw new ERR_INVALID_STATE(
284-
'Server sessions cannot open HTTP/3 request streams');
285-
}
286-
return await createApplicationStream(
287-
this.#session, kStreamDirectionBidirectional, options);
288-
}
289-
290-
close(options) { return this.#session.close(options); }
291-
292-
destroy(error, options) { return this.#session.destroy(error, options); }
293-
294-
async [SymbolAsyncDispose]() { await this.close(); }
295-
296-
[kInspect](depth, options) {
297-
if (depth < 0) {
298-
return 'Http3Session { }';
176+
createBidirectionalStream(options) {
177+
if (isServerConnection(this.connection)) {
178+
return PromiseReject(new ERR_INVALID_STATE(
179+
'Server sessions cannot open HTTP/3 request streams'));
299180
}
300-
const opts = {
301-
__proto__: null,
302-
...options,
303-
depth: options.depth == null ? null : options.depth - 1,
304-
};
305-
return `Http3Session ${inspect({
306-
quicSession: this.#session,
307-
settings: this.settings,
308-
destroyed: this.destroyed,
309-
}, opts)}`;
181+
return createApplicationStream(
182+
this.connection, kStreamDirectionBidirectional, options);
310183
}
311184
}
312185

0 commit comments

Comments
 (0)