Skip to content

Commit 64f7d1e

Browse files
committed
Add Http3
1 parent a15ae0a commit 64f7d1e

217 files changed

Lines changed: 9078 additions & 7776 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.
Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ HTTP/3's `Http3Options` is an empty placeholder class, while HTTP/2 has 5 config
3030
**Parallel:** yes — can run alongside nothing (foundation task)
3131

3232
**Acceptance Criteria:**
33-
- [ ] `Http3Options` has these properties with XML doc comments:
33+
- [x] `Http3Options` has these properties with XML doc comments:
3434
- `MaxConnectionsPerServer` (int, default 4) — fewer than H2's 6 since QUIC multiplexes better
3535
- `QpackMaxTableCapacity` (int, default 4096) — QPACK dynamic table size
3636
- `QpackBlockedStreams` (int, default 100) — max streams blocked on encoder instructions
3737
- `MaxFieldSectionSize` (int, default 65536) — HTTP/3 equivalent of header size limits
3838
- `IdleTimeout` (TimeSpan, default 30s) — QUIC idle timeout
3939
- `MaxReconnectAttempts` (int, default 3) — reconnect limit for QUIC connection drops
40-
- [ ] Property style matches `Http2Options` (public getters/setters, summary comments referencing relevant RFCs)
41-
- [ ] `TurboClientOptions.Http3` property already exists and returns `Http3Options` — verify it works with new properties
42-
- [ ] Unit test validates defaults match documented values
43-
- [ ] Typecheck passes: `dotnet build --configuration Release ./src/TurboHTTP.sln`
40+
- [x] Property style matches `Http2Options` (public getters/setters, summary comments referencing relevant RFCs)
41+
- [x] `TurboClientOptions.Http3` property already exists and returns `Http3Options` — verify it works with new properties
42+
- [x] Unit test validates defaults match documented values
43+
- [x] Typecheck passes: `dotnet build --configuration Release ./src/TurboHTTP.sln`
4444

4545
**Files to modify:**
4646
- `src/TurboHTTP/Http3Options.cs`
@@ -59,13 +59,13 @@ HTTP/3's `Http3Options` is an empty placeholder class, while HTTP/2 has 5 config
5959
**Parallel:** yes — can run alongside TASK-002-003
6060

6161
**Acceptance Criteria:**
62-
- [ ] `ProtocolCoreBuilder.Build()` extracts `clientOptions.Http3.QpackMaxTableCapacity` and `clientOptions.Http3.IdleTimeout`
63-
- [ ] `Http30Engine` constructor signature updated: `Http30Engine(int maxTableCapacity, TimeSpan idleTimeout)`
64-
- [ ] `Http30Engine` passes `idleTimeout` to `Http30ConnectionStage` constructor
65-
- [ ] `ProtocolCoreBuilder` line `new Http30Engine()` changed to `new Http30Engine(clientOptions.Http3.QpackMaxTableCapacity, clientOptions.Http3.IdleTimeout)`
66-
- [ ] Default parameterless `Http30Engine()` constructor preserved for backward compatibility (delegates to new constructor with defaults)
67-
- [ ] All existing tests still pass unchanged
68-
- [ ] Typecheck passes
62+
- [x] `ProtocolCoreBuilder.Build()` extracts `clientOptions.Http3.QpackMaxTableCapacity` and `clientOptions.Http3.IdleTimeout`
63+
- [x] `Http30Engine` constructor signature updated: `Http30Engine(int maxTableCapacity, TimeSpan idleTimeout)`
64+
- [x] `Http30Engine` passes `idleTimeout` to `Http30ConnectionStage` constructor
65+
- [x] `ProtocolCoreBuilder` line `new Http30Engine()` changed to `new Http30Engine(clientOptions.Http3.QpackMaxTableCapacity, clientOptions.Http3.IdleTimeout)`
66+
- [x] Default parameterless `Http30Engine()` constructor preserved for backward compatibility (delegates to new constructor with defaults)
67+
- [x] All existing tests still pass unchanged
68+
- [x] Typecheck passes
6969

7070
**Files to modify:**
7171
- `src/TurboHTTP/Streams/ProtocolCoreBuilder.cs` (line 63)
@@ -85,13 +85,13 @@ HTTP/3's `Http3Options` is an empty placeholder class, while HTTP/2 has 5 config
8585
**Parallel:** yes — can run alongside TASK-002-002
8686

8787
**Acceptance Criteria:**
88-
- [ ] `ProtocolCoreBuilder.Build()` extracts `maxConnsH3` from `clientOptions.Http3.MaxConnectionsPerServer`
89-
- [ ] `MaxSubstreamsPerKey()` has separate branch: `endpoint.Version.Major == 3 ? maxConnsH3`
90-
- [ ] `MaxConcurrencyPerSlot()` has separate branch: `endpoint.Version.Major == 3 ? int.MaxValue` (QUIC handles stream limits at transport level)
91-
- [ ] Existing `Major >= 2` changed to `Major == 2` for HTTP/2-only path
92-
- [ ] Unit test: H2 endpoint uses H2 limits, H3 endpoint uses H3 limits
93-
- [ ] All existing tests still pass
94-
- [ ] Typecheck passes
88+
- [x] `ProtocolCoreBuilder.Build()` extracts `maxConnsH3` from `clientOptions.Http3.MaxConnectionsPerServer`
89+
- [x] `MaxSubstreamsPerKey()` has separate branch: `endpoint.Version.Major == 3 ? maxConnsH3`
90+
- [x] `MaxConcurrencyPerSlot()` has separate branch: `endpoint.Version.Major == 3 ? int.MaxValue` (QUIC handles stream limits at transport level)
91+
- [x] Existing `Major >= 2` changed to `Major == 2` for HTTP/2-only path
92+
- [x] Unit test: H2 endpoint uses H2 limits, H3 endpoint uses H3 limits
93+
- [x] All existing tests still pass
94+
- [x] Typecheck passes
9595

9696
**Files to modify:**
9797
- `src/TurboHTTP/Streams/ProtocolCoreBuilder.cs` (lines 28-50)
@@ -108,9 +108,9 @@ HTTP/3's `Http3Options` is an empty placeholder class, while HTTP/2 has 5 config
108108
**Model:** haiku
109109

110110
**Acceptance Criteria:**
111-
- [ ] ARCHITECTURE.md "Implementation Status" table: HTTP/3 score updated (was 75/100)
112-
- [ ] "Open gaps" list: remove "QUIC transport" if now wired, update QPACK status
113-
- [ ] `TurboClientOptions` description mentions `Http3Options` alongside `Http2Options`
111+
- [x] ARCHITECTURE.md "Implementation Status" table: HTTP/3 score updated (was 75/100)
112+
- [x] "Open gaps" list: remove "QUIC transport" if now wired, update QPACK status
113+
- [x] `TurboClientOptions` description mentions `Http3Options` alongside `Http2Options`
114114

115115
**Files to modify:**
116116
- `ARCHITECTURE.md` (lines 318-331)
Lines changed: 70 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
3636
**Parallel:** yes — can run alongside TASK-003-002
3737

3838
**Acceptance Criteria:**
39-
- [ ] `Http3ConnectionConfig` record created in `src/TurboHTTP/Protocol/Http3/`:
39+
- [x] `Http3ConnectionConfig` record created in `src/TurboHTTP/Protocol/Http3/`:
4040
```csharp
4141
public sealed record Http3ConnectionConfig(
4242
int MaxFieldSectionSize = 65536,
@@ -46,7 +46,7 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
4646
int MaxReconnectAttempts = 3,
4747
bool AllowServerPush = false);
4848
```
49-
- [ ] `IHttp3StageOperations` interface created in `src/TurboHTTP/Protocol/Http3/`:
49+
- [x] `IHttp3StageOperations` interface created in `src/TurboHTTP/Protocol/Http3/`:
5050
```csharp
5151
public interface IHttp3StageOperations
5252
{
@@ -56,8 +56,8 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
5656
void OnReconnectFailed();
5757
}
5858
```
59-
- [ ] Follows `IHttp2StageOperations` naming and callback semantics exactly
60-
- [ ] Typecheck passes
59+
- [x] Follows `IHttp2StageOperations` naming and callback semantics exactly
60+
- [x] Typecheck passes
6161

6262
**Files to create:**
6363
- `src/TurboHTTP/Protocol/Http3/Http3ConnectionConfig.cs`
@@ -77,16 +77,16 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
7777
**Parallel:** yes — can run alongside TASK-003-001
7878

7979
**Acceptance Criteria:**
80-
- [ ] `Http3StreamTracker` class created in `src/TurboHTTP/Protocol/Http3/`
81-
- [ ] Tracks client-initiated bidirectional stream IDs (0, 4, 8, 12, ... — incremented by 4 per RFC 9114 §6.1)
82-
- [ ] `AllocateStreamId()` returns next ID and advances counter by 4
83-
- [ ] `CanOpenStream()` checks against concurrency limit
84-
- [ ] `OnStreamOpened(long streamId)` / `OnStreamClosed(long streamId)` manage active set
85-
- [ ] `Reset()` clears state for reconnection
86-
- [ ] `ActiveStreamCount`, `MaxConcurrentStreams`, `NextStreamId` properties
87-
- [ ] Uses `long` for stream IDs (QUIC uses 62-bit variable-length integers, unlike HTTP/2's 31-bit)
88-
- [ ] Unit tests in `src/TurboHTTP.Tests/Http3/Connection/Http3StreamTrackerSpec.cs`
89-
- [ ] Typecheck passes
80+
- [x] `Http3StreamTracker` class created in `src/TurboHTTP/Protocol/Http3/`
81+
- [x] Tracks client-initiated bidirectional stream IDs (0, 4, 8, 12, ... — incremented by 4 per RFC 9114 §6.1)
82+
- [x] `AllocateStreamId()` returns next ID and advances counter by 4
83+
- [x] `CanOpenStream()` checks against concurrency limit
84+
- [x] `OnStreamOpened(long streamId)` / `OnStreamClosed(long streamId)` manage active set
85+
- [x] `Reset()` clears state for reconnection
86+
- [x] `ActiveStreamCount`, `MaxConcurrentStreams`, `NextStreamId` properties
87+
- [x] Uses `long` for stream IDs (QUIC uses 62-bit variable-length integers, unlike HTTP/2's 31-bit)
88+
- [x] Unit tests in `src/TurboHTTP.Tests/Http3/Connection/Http3StreamTrackerSpec.cs`
89+
- [x] Typecheck passes
9090

9191
**Files to create:**
9292
- `src/TurboHTTP/Protocol/Http3/Http3StreamTracker.cs`
@@ -107,28 +107,28 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
107107
**Model:** opus
108108

109109
**Acceptance Criteria:**
110-
- [ ] `Http3StateMachine` class created in `src/TurboHTTP/Protocol/Http3/StateMachine.cs`
111-
- [ ] Constructor takes `Http3ConnectionConfig` + `IHttp3StageOperations`
112-
- [ ] Owns `Http3StreamTracker`, `ConnectionState` (moved from Http30ConnectionStage inner class)
113-
- [ ] `ConnectionState` class moved from `Http30ConnectionStage` to `StateMachine` (or extracted to own file)
114-
- [ ] Key methods:
110+
- [x] `Http3StateMachine` class created in `src/TurboHTTP/Protocol/Http3/StateMachine.cs`
111+
- [x] Constructor takes `Http3ConnectionConfig` + `IHttp3StageOperations`
112+
- [x] Owns `Http3StreamTracker`, `ConnectionState` (moved from Http30ConnectionStage inner class)
113+
- [x] `ConnectionState` class moved from `Http30ConnectionStage` to `StateMachine` (or extracted to own file)
114+
- [x] Key methods:
115115
- `ProcessFrame(Http3Frame frame)` — handles SETTINGS, GOAWAY, PUSH_PROMISE, CANCEL_PUSH, MAX_PUSH_ID, forwards DATA/HEADERS via callback
116116
- `SendRequest(Http3Frame frame)` — validates GOAWAY not received, tracks stream open, enqueues outbound
117117
- `bool CanAcceptRequest` — no GOAWAY + not reconnecting + concurrency budget
118118
- `bool IsReconnecting` / `int ReconnectBufferCount`
119119
- `OnConnectionLost()` — enters reconnect state, buffers in-flight requests
120120
- `OnConnectionRestored()` — replays buffered requests via callback
121121
- `OnReconnectFailed()` — signals callback after max attempts exhausted
122-
- [ ] All existing `Http30ConnectionStage.Logic` protocol logic migrated to StateMachine
123-
- [ ] Unit tests in `src/TurboHTTP.Tests/Http3/Connection/Http3StateMachineSpec.cs` covering:
122+
- [x] All existing `Http30ConnectionStage.Logic` protocol logic migrated to StateMachine
123+
- [x] Unit tests in `src/TurboHTTP.Tests/Http3/Connection/Http3StateMachineSpec.cs` covering:
124124
- SETTINGS processing
125125
- GOAWAY handling (single, decreasing stream IDs, invalid IDs)
126126
- Push promise rejection (AllowServerPush = false)
127127
- Push promise acceptance (AllowServerPush = true)
128128
- Stream lifecycle (open/close tracking)
129129
- Idle timeout expiry
130130
- Reconnection enter/replay/fail
131-
- [ ] Typecheck passes
131+
- [x] Typecheck passes
132132

133133
**Files to create:**
134134
- `src/TurboHTTP/Protocol/Http3/StateMachine.cs`
@@ -150,20 +150,20 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
150150
**Model:** opus
151151

152152
**Acceptance Criteria:**
153-
- [ ] `Http30ConnectionStage` constructor takes `Http3ConnectionConfig` (not just `TimeSpan idleTimeout`)
154-
- [ ] `Logic` class implements `IHttp3StageOperations`
155-
- [ ] `Logic` creates `Http3StateMachine` in constructor
156-
- [ ] All frame handling delegated: `HandleServerFrame``_sm.ProcessFrame(frame)`, outbound → `_sm.SendRequest(frame)`
157-
- [ ] `ConnectionState` inner class removed (now lives in StateMachine)
158-
- [ ] `IHttp3StageOperations` callbacks translate to Akka operations:
153+
- [x] `Http30ConnectionStage` constructor takes `Http3ConnectionConfig` (not just `TimeSpan idleTimeout`)
154+
- [x] `Logic` class implements `IHttp3StageOperations`
155+
- [x] `Logic` creates `Http3StateMachine` in constructor
156+
- [x] All frame handling delegated: `HandleServerFrame``_sm.ProcessFrame(frame)`, outbound → `_sm.SendRequest(frame)`
157+
- [x] `ConnectionState` inner class removed (now lives in StateMachine)
158+
- [x] `IHttp3StageOperations` callbacks translate to Akka operations:
159159
- `OnResponse``Push(_outApp, frame)`
160160
- `OnOutbound``EnqueueOutbound(frame)`
161161
- `OnWarning``Log.Warning(...)`
162162
- `OnReconnectFailed``FailStage(...)`
163-
- [ ] `Http30Engine` updated to pass `Http3ConnectionConfig` to stage
164-
- [ ] All existing stream tests in `src/TurboHTTP.StreamTests/Http3/` pass unchanged
165-
- [ ] All existing stream tests in `src/TurboHTTP.StreamTests/Http3/Connection/` pass unchanged
166-
- [ ] Typecheck passes
163+
- [x] `Http30Engine` updated to pass `Http3ConnectionConfig` to stage
164+
- [x] All existing stream tests in `src/TurboHTTP.StreamTests/Http3/` pass unchanged
165+
- [x] All existing stream tests in `src/TurboHTTP.StreamTests/Http3/Connection/` pass unchanged
166+
- [x] Typecheck passes
167167

168168
**Files to modify:**
169169
- `src/TurboHTTP/Streams/Stages/Decoding/Http30ConnectionStage.cs` (major refactor)
@@ -183,19 +183,19 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
183183
**Parallel:** no
184184

185185
**Acceptance Criteria:**
186-
- [ ] `Http3StateMachine.OnConnectionLost()` enters reconnect state:
186+
- [x] `Http3StateMachine.OnConnectionLost()` enters reconnect state:
187187
- Sets `_reconnecting = true`
188188
- Buffers pending outbound frames in `_reconnectBuffer`
189189
- Increments `_reconnectAttempts`
190-
- [ ] `Http3StateMachine.OnConnectionRestored()` replays buffer:
190+
- [x] `Http3StateMachine.OnConnectionRestored()` replays buffer:
191191
- Resets stream tracker (`_tracker.Reset()`)
192192
- Replays buffered frames via `_ops.OnOutbound()`
193193
- Clears `_reconnecting` and `_reconnectBuffer`
194-
- [ ] After `MaxReconnectAttempts` exhausted → `_ops.OnReconnectFailed()`
195-
- [ ] `Http30ConnectionStage.Logic` handles upstream finish during reconnect (fails stage)
196-
- [ ] Unit tests in StateMachine spec: reconnect enter, replay, max attempts
197-
- [ ] Stream test: `Http30ConnectionStageReconnectSpec.cs` — simulates transport failure and recovery
198-
- [ ] Typecheck passes
194+
- [x] After `MaxReconnectAttempts` exhausted → `_ops.OnReconnectFailed()`
195+
- [x] `Http30ConnectionStage.Logic` handles upstream finish during reconnect (fails stage)
196+
- [x] Unit tests in StateMachine spec: reconnect enter, replay, max attempts
197+
- [x] Stream test: `Http30ConnectionStageReconnectSpec.cs` — simulates transport failure and recovery
198+
- [x] Typecheck passes
199199

200200
**Files to modify:**
201201
- `src/TurboHTTP/Protocol/Http3/StateMachine.cs` (add reconnect fields + methods)
@@ -219,13 +219,13 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
219219
**Parallel:** yes — can run alongside TASK-003-007, TASK-003-008, TASK-003-009
220220

221221
**Acceptance Criteria:**
222-
- [ ] `Http3ConnectionConfig.AllowServerPush` (already added in TASK-003-001) flows through to StateMachine
223-
- [ ] When `AllowServerPush = false`: PUSH_PROMISE triggers CANCEL_PUSH frame via `_ops.OnOutbound()`
224-
- [ ] When `AllowServerPush = true`: PUSH_PROMISE is forwarded to app layer
225-
- [ ] `MAX_PUSH_ID` frame sent with appropriate value based on config
226-
- [ ] Unit test: push rejected when disabled, accepted when enabled
227-
- [ ] Existing `Http30PushRejectionSpec` stream test still passes
228-
- [ ] Typecheck passes
222+
- [x] `Http3ConnectionConfig.AllowServerPush` (already added in TASK-003-001) flows through to StateMachine
223+
- [x] When `AllowServerPush = false`: PUSH_PROMISE triggers CANCEL_PUSH frame via `_ops.OnOutbound()`
224+
- [x] When `AllowServerPush = true`: PUSH_PROMISE is forwarded to app layer
225+
- [x] `MAX_PUSH_ID` frame sent with appropriate value based on config
226+
- [x] Unit test: push rejected when disabled, accepted when enabled
227+
- [x] Existing `Http30PushRejectionSpec` stream test still passes
228+
- [x] Typecheck passes
229229

230230
**Files to modify:**
231231
- `src/TurboHTTP/Protocol/Http3/StateMachine.cs` (ProcessFrame PUSH_PROMISE branch)
@@ -241,15 +241,15 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
241241
**Parallel:** yes — can run alongside TASK-003-006, TASK-003-008, TASK-003-009
242242

243243
**Acceptance Criteria:**
244-
- [ ] `Http3Options.AllowEarlyData` property added (bool, default false)
245-
- [ ] `Http3ConnectionConfig` includes `AllowEarlyData`
246-
- [ ] `Http30Request2FrameStage` checks if request method is idempotent (GET, HEAD, OPTIONS, TRACE, DELETE)
247-
- [ ] Idempotent requests tagged with `EarlyData = true` when `AllowEarlyData` is enabled
248-
- [ ] `QuicTransportStateMachine` / `QuicClientProvider` uses `QuicStream.CanWrite` for early data
249-
- [ ] On 0-RTT rejection: request re-sent after full handshake
250-
- [ ] Unit test: idempotent request marked for early data
251-
- [ ] Unit test: non-idempotent request blocked from early data
252-
- [ ] Typecheck passes
244+
- [x] `Http3Options.AllowEarlyData` property added (bool, default false)
245+
- [x] `Http3ConnectionConfig` includes `AllowEarlyData`
246+
- [x] `Http30Request2FrameStage` checks if request method is idempotent (GET, HEAD, OPTIONS, TRACE, DELETE)
247+
- [x] Idempotent requests tagged with `EarlyData = true` when `AllowEarlyData` is enabled
248+
- [x] `QuicTransportStateMachine` / `QuicClientProvider` uses `QuicStream.CanWrite` for early data
249+
- [x] On 0-RTT rejection: request re-sent after full handshake
250+
- [x] Unit test: idempotent request marked for early data
251+
- [x] Unit test: non-idempotent request blocked from early data
252+
- [x] Typecheck passes
253253

254254
**Files to modify:**
255255
- `src/TurboHTTP/Http3Options.cs`
@@ -268,13 +268,13 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
268268
**Parallel:** yes — can run alongside TASK-003-006, TASK-003-007, TASK-003-009
269269

270270
**Acceptance Criteria:**
271-
- [ ] `Http3Options.AllowConnectionMigration` property added (bool, default true)
272-
- [ ] `QuicTransportStateMachine` detects address changes on the QUIC connection
273-
- [ ] When migration detected and allowed: connection continues transparently
274-
- [ ] When migration detected and disallowed: connection closed, new connection established (leverages reconnect from TASK-003-005)
275-
- [ ] Unit test: migration allowed continues seamlessly
276-
- [ ] Unit test: migration disallowed triggers reconnect
277-
- [ ] Typecheck passes
271+
- [x] `Http3Options.AllowConnectionMigration` property added (bool, default true)
272+
- [x] `QuicTransportStateMachine` detects address changes on the QUIC connection
273+
- [x] When migration detected and allowed: connection continues transparently
274+
- [x] When migration detected and disallowed: connection closed, new connection established (leverages reconnect from TASK-003-005)
275+
- [x] Unit test: migration allowed continues seamlessly
276+
- [x] Unit test: migration disallowed triggers reconnect
277+
- [x] Typecheck passes
278278

279279
**Files to modify:**
280280
- `src/TurboHTTP/Http3Options.cs`
@@ -293,14 +293,14 @@ HTTP/2's connection handling follows a clean architecture: `StateMachine` (all p
293293
**Model:** opus
294294

295295
**Acceptance Criteria:**
296-
- [ ] `AltSvcCache` class created: per-host cache of Alt-Svc directives with TTL
297-
- [ ] Alt-Svc header parsed from HTTP/1.1 and HTTP/2 responses (RFC 7838)
298-
- [ ] `AltSvcEntry` record: protocol, host, port, maxAge, persist flag
299-
- [ ] Integration point in `ProtocolCoreBuilder` or `RequestEnricher`: check cache before version selection
300-
- [ ] If HTTP/3 advertised and QUIC available: upgrade endpoint version to 3.0
301-
- [ ] `Http3Options.EnableAltSvcDiscovery` property (bool, default false — opt-in)
302-
- [ ] Unit tests: Alt-Svc header parsing, cache TTL expiry, upgrade decision
303-
- [ ] Typecheck passes
296+
- [x] `AltSvcCache` class created: per-host cache of Alt-Svc directives with TTL
297+
- [x] Alt-Svc header parsed from HTTP/1.1 and HTTP/2 responses (RFC 7838)
298+
- [x] `AltSvcEntry` record: protocol, host, port, maxAge, persist flag
299+
- [x] Integration point in `ProtocolCoreBuilder` or `RequestEnricher`: check cache before version selection
300+
- [x] If HTTP/3 advertised and QUIC available: upgrade endpoint version to 3.0
301+
- [x] `Http3Options.EnableAltSvcDiscovery` property (bool, default false — opt-in)
302+
- [x] Unit tests: Alt-Svc header parsing, cache TTL expiry, upgrade decision
303+
- [x] Typecheck passes
304304

305305
**Files to create:**
306306
- `src/TurboHTTP/Protocol/AltSvc/AltSvcCache.cs`

0 commit comments

Comments
 (0)