Skip to content

Commit 3c257cb

Browse files
committed
quic: split headers out from src/quic/stream.{h/cc}
As part of the effort to get better separation between the generalized QUIC streams and HTTP/3 streams, separate out header handling from the C++ class. Signed-off-by: James M Snell <jasnell@gmail.com> Assisted-by: Opencode
1 parent 8af7545 commit 3c257cb

11 files changed

Lines changed: 440 additions & 387 deletions

File tree

‎lib/internal/quic/quic.js‎

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ let debug = require('internal/util/debuglog').debuglog('quic', (fn) => {
5050

5151
const {
5252
Endpoint: Endpoint_,
53+
sendHeaders,
5354
setCallbacks,
55+
setHeadersInterest,
5456

5557
// The constants to be exposed to end users for various options.
5658
CC_ALGO_RENO_STR: CC_ALGO_RENO,
@@ -1303,6 +1305,17 @@ function parseHeaderPairs(pairs) {
13031305
return block;
13041306
}
13051307

1308+
function updateHeaderInterest(handle, inner) {
1309+
if (handle === undefined) return;
1310+
setHeadersInterest(
1311+
handle,
1312+
inner.onheaders !== undefined ||
1313+
inner.ontrailers !== undefined ||
1314+
inner.oninfo !== undefined,
1315+
inner.onwanttrailers !== undefined || inner.pendingTrailers !== undefined,
1316+
);
1317+
}
1318+
13061319
/**
13071320
* Applies session and stream callbacks from an options object to a session.
13081321
* @param {QuicSession} session
@@ -1827,13 +1840,12 @@ class QuicStream {
18271840
const inner = this.#inner;
18281841
if (fn === undefined) {
18291842
inner.onheaders = undefined;
1830-
inner.state.wantsHeaders = false;
18311843
} else {
18321844
validateFunction(fn, 'onheaders');
18331845
assertHeadersSupported(inner.session);
18341846
inner.onheaders = FunctionPrototypeBind(fn, this);
1835-
inner.state.wantsHeaders = true;
18361847
}
1848+
updateHeaderInterest(this.#handle, inner);
18371849
}
18381850

18391851
/** @type {Function|undefined} */
@@ -1852,6 +1864,7 @@ class QuicStream {
18521864
assertHeadersSupported(inner.session);
18531865
inner.oninfo = FunctionPrototypeBind(fn, this);
18541866
}
1867+
updateHeaderInterest(this.#handle, inner);
18551868
}
18561869

18571870
/** @type {Function|undefined} */
@@ -1870,6 +1883,7 @@ class QuicStream {
18701883
assertHeadersSupported(inner.session);
18711884
inner.ontrailers = FunctionPrototypeBind(fn, this);
18721885
}
1886+
updateHeaderInterest(this.#handle, inner);
18731887
}
18741888

18751889
/** @type {Function|undefined} */
@@ -1883,13 +1897,12 @@ class QuicStream {
18831897
const inner = this.#inner;
18841898
if (fn === undefined) {
18851899
inner.onwanttrailers = undefined;
1886-
inner.state.wantsTrailers = false;
18871900
} else {
18881901
validateFunction(fn, 'onwanttrailers');
18891902
assertHeadersSupported(inner.session);
18901903
inner.onwanttrailers = FunctionPrototypeBind(fn, this);
1891-
inner.state.wantsTrailers = true;
18921904
}
1905+
updateHeaderInterest(this.#handle, inner);
18931906
}
18941907

18951908
/**
@@ -1918,10 +1931,12 @@ class QuicStream {
19181931
assertHeadersSupported(inner.session);
19191932
if (headers === undefined) {
19201933
inner.pendingTrailers = undefined;
1934+
updateHeaderInterest(this.#handle, inner);
19211935
return;
19221936
}
19231937
validateObject(headers, 'headers');
19241938
inner.pendingTrailers = headers;
1939+
updateHeaderInterest(this.#handle, inner);
19251940
}
19261941

19271942
/**
@@ -2104,7 +2119,8 @@ class QuicStream {
21042119
const headerString = buildNgHeaderString(
21052120
headers, assertValidPseudoHeader, true /* strictSingleValueFields */);
21062121
const flags = terminal ? kHeadersFlagsTerminal : kHeadersFlagsNone;
2107-
return this.#handle.sendHeaders(kHeadersKindInitial, headerString, flags);
2122+
return sendHeaders(
2123+
this.#handle, kHeadersKindInitial, headerString, flags);
21082124
}
21092125

21102126
/**
@@ -2123,8 +2139,8 @@ class QuicStream {
21232139
validateObject(headers, 'headers');
21242140
const headerString = buildNgHeaderString(
21252141
headers, assertValidPseudoHeader, true);
2126-
return this.#handle.sendHeaders(
2127-
kHeadersKindHints, headerString, kHeadersFlagsNone);
2142+
return sendHeaders(
2143+
this.#handle, kHeadersKindHints, headerString, kHeadersFlagsNone);
21282144
}
21292145

21302146
/**
@@ -2143,8 +2159,8 @@ class QuicStream {
21432159
}
21442160
validateObject(headers, 'headers');
21452161
const headerString = buildNgHeaderString(headers);
2146-
return this.#handle.sendHeaders(
2147-
kHeadersKindTrailing, headerString, kHeadersFlagsNone);
2162+
return sendHeaders(
2163+
this.#handle, kHeadersKindTrailing, headerString, kHeadersFlagsNone);
21482164
}
21492165

21502166
/**
@@ -2562,7 +2578,7 @@ class QuicStream {
25622578
assertValidPseudoHeader,
25632579
true, // This could become an option in future
25642580
);
2565-
return this.#handle.sendHeaders(kind, headerString, flags);
2581+
return sendHeaders(this.#handle, kind, headerString, flags);
25662582
}
25672583

25682584
[kFinishClose](error) {
@@ -2670,7 +2686,6 @@ class QuicStream {
26702686

26712687
switch (kindName) {
26722688
case 'initial':
2673-
assert(inner.onheaders, 'Unexpected stream headers event');
26742689
inner.headers ??= block;
26752690
if (onStreamHeadersChannel.hasSubscribers) {
26762691
onStreamHeadersChannel.publish({
@@ -2680,7 +2695,8 @@ class QuicStream {
26802695
headers: block,
26812696
});
26822697
}
2683-
safeCallbackInvoke(inner.onheaders, this, block);
2698+
if (inner.onheaders)
2699+
safeCallbackInvoke(inner.onheaders, this, block);
26842700
break;
26852701
case 'trailing':
26862702
if (onStreamTrailersChannel.hasSubscribers) {
@@ -2716,8 +2732,20 @@ class QuicStream {
27162732
// nghttp3 is asking us to provide trailers to send.
27172733
// Check for pre-set pendingTrailers first, then the callback.
27182734
if (inner.pendingTrailers) {
2719-
this.sendTrailers(inner.pendingTrailers);
2735+
let sent;
2736+
try {
2737+
sent = this.sendTrailers(inner.pendingTrailers);
2738+
} catch (error) {
2739+
this.destroy(error);
2740+
return;
2741+
}
2742+
if (!sent) {
2743+
this.destroy(new ERR_QUIC_STREAM_ABORTED(
2744+
'Failed to submit trailing headers'));
2745+
return;
2746+
}
27202747
inner.pendingTrailers = undefined;
2748+
updateHeaderInterest(this.#handle, inner);
27212749
} else if (typeof inner.onwanttrailers === 'function') {
27222750
safeCallbackInvoke(inner.onwanttrailers, this);
27232751
}

‎lib/internal/quic/state.js‎

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,8 @@ const {
101101
IDX_STATE_STREAM_HAS_OUTBOUND,
102102
IDX_STATE_STREAM_HAS_READER,
103103
IDX_STATE_STREAM_WANTS_BLOCK,
104-
IDX_STATE_STREAM_WANTS_HEADERS,
105104
IDX_STATE_STREAM_WANTS_RESET,
106105
IDX_STATE_STREAM_WANTS_STOP_SENDING,
107-
IDX_STATE_STREAM_WANTS_TRAILERS,
108106
IDX_STATE_STREAM_RECEIVED_EARLY_DATA,
109107
IDX_STATE_STREAM_WRITE_DESIRED_SIZE,
110108
IDX_STATE_STREAM_BUDGET,
@@ -145,10 +143,8 @@ assert(IDX_STATE_STREAM_RESET !== undefined);
145143
assert(IDX_STATE_STREAM_HAS_OUTBOUND !== undefined);
146144
assert(IDX_STATE_STREAM_HAS_READER !== undefined);
147145
assert(IDX_STATE_STREAM_WANTS_BLOCK !== undefined);
148-
assert(IDX_STATE_STREAM_WANTS_HEADERS !== undefined);
149146
assert(IDX_STATE_STREAM_WANTS_RESET !== undefined);
150147
assert(IDX_STATE_STREAM_WANTS_STOP_SENDING !== undefined);
151-
assert(IDX_STATE_STREAM_WANTS_TRAILERS !== undefined);
152148
assert(IDX_STATE_STREAM_WRITE_DESIRED_SIZE !== undefined);
153149
assert(IDX_STATE_STREAM_RESET_CODE !== undefined);
154150

@@ -824,20 +820,6 @@ class QuicStreamState {
824820
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_BLOCK, val ? 1 : 0);
825821
}
826822

827-
/** @type {boolean} */
828-
get wantsHeaders() {
829-
const handle = this.#handle;
830-
if (handle === undefined) return undefined;
831-
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_HEADERS) !== 0;
832-
}
833-
834-
/** @type {boolean} */
835-
set wantsHeaders(val) {
836-
const handle = this.#handle;
837-
if (handle === undefined) return;
838-
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_HEADERS, val ? 1 : 0);
839-
}
840-
841823
/** @type {boolean} */
842824
get wantsReset() {
843825
const handle = this.#handle;
@@ -870,20 +852,6 @@ class QuicStreamState {
870852
val ? 1 : 0);
871853
}
872854

873-
/** @type {boolean} */
874-
get wantsTrailers() {
875-
const handle = this.#handle;
876-
if (handle === undefined) return undefined;
877-
return DataViewPrototypeGetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_TRAILERS) !== 0;
878-
}
879-
880-
/** @type {boolean} */
881-
set wantsTrailers(val) {
882-
const handle = this.#handle;
883-
if (handle === undefined) return;
884-
DataViewPrototypeSetUint8(handle, this.#offset + IDX_STATE_STREAM_WANTS_TRAILERS, val ? 1 : 0);
885-
}
886-
887855
/** @type {boolean} */
888856
get early() {
889857
const handle = this.#handle;
@@ -948,8 +916,6 @@ class QuicStreamState {
948916
wantsBlock,
949917
wantsReset,
950918
wantsStopSending,
951-
wantsHeaders,
952-
wantsTrailers,
953919
early,
954920
resetCode,
955921
writeDesiredSize,
@@ -969,8 +935,6 @@ class QuicStreamState {
969935
wantsBlock,
970936
wantsReset,
971937
wantsStopSending,
972-
wantsHeaders,
973-
wantsTrailers,
974938
early,
975939
resetCode: `${resetCode}`,
976940
writeDesiredSize,
@@ -1007,8 +971,6 @@ class QuicStreamState {
1007971
wantsBlock,
1008972
wantsReset,
1009973
wantsStopSending,
1010-
wantsHeaders,
1011-
wantsTrailers,
1012974
early,
1013975
resetCode,
1014976
writeDesiredSize,
@@ -1028,8 +990,6 @@ class QuicStreamState {
1028990
wantsBlock,
1029991
wantsReset,
1030992
wantsStopSending,
1031-
wantsHeaders,
1032-
wantsTrailers,
1033993
early,
1034994
resetCode,
1035995
writeDesiredSize,

‎src/quic/application.cc‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Maybe<Session::Application_Options> Session::Application_Options::From(
117117

118118
// Ensure the advertised max_field_section_size in SETTINGS is at least
119119
// as large as max_header_length. Otherwise the peer would be told to
120-
// restrict headers to a smaller size than what CanAddHeader accepts.
120+
// restrict headers to a smaller size than what the HTTP/3 stream accepts.
121121
if (options.max_field_section_size < options.max_header_length) {
122122
options.max_field_section_size = options.max_header_length;
123123
}

‎src/quic/application.h‎

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@
1111

1212
namespace node::quic {
1313

14+
enum class HeadersKind : uint8_t {
15+
HINTS,
16+
INITIAL,
17+
TRAILING,
18+
};
19+
20+
enum class HeadersFlags : uint8_t {
21+
NONE,
22+
TERMINAL,
23+
};
24+
1425
// An Application implements the ALPN-protocol specific semantics on behalf
1526
// of a QUIC Session.
1627
class Session::Application : public MemoryRetainer {
@@ -95,13 +106,10 @@ class Session::Application : public MemoryRetainer {
95106
// Application.
96107
virtual bool AcknowledgeStreamData(stream_id id, size_t datalen);
97108

98-
// Called to determine if a Header can be added to this application.
99-
// Applications that do not support headers will always return false.
100-
virtual bool CanAddHeader(size_t current_count,
101-
size_t current_headers_length,
102-
size_t this_header_length) {
103-
return false;
104-
}
109+
// Called when a pending transport stream receives its stream ID. Protocols
110+
// can use this to flush operations that require an opened stream. Returns
111+
// false if deferred application data could not be submitted.
112+
virtual bool StreamOpened(Stream& stream) { return true; }
105113

106114
// Called when ngtcp2 reports NGTCP2_ERR_STREAM_SHUT_WR for a stream.
107115
// Applications that manage their own framing (e.g., HTTP/3) must inform
@@ -173,13 +181,19 @@ class Session::Application : public MemoryRetainer {
173181
// Submits an outbound block of headers for the given stream. Not all
174182
// Application types will support headers, in which case this function
175183
// should return false.
176-
virtual bool SendHeaders(const Stream& stream,
184+
virtual bool SendHeaders(Stream& stream,
177185
HeadersKind kind,
178186
const v8::Local<v8::Array>& headers,
179187
HeadersFlags flags = HeadersFlags::NONE) {
180188
return false;
181189
}
182190

191+
// Updates JavaScript callback interest for an application's stream header
192+
// events. Applications without header semantics ignore this.
193+
virtual void SetHeadersInterest(Stream& stream,
194+
bool wants_headers,
195+
bool wants_trailers) {}
196+
183197
// Returns true if the application protocol supports sending and
184198
// receiving headers on streams (e.g. HTTP/3). Applications that
185199
// do not support headers should return false (the default).

0 commit comments

Comments
 (0)