feat(web): HTTP/3 (QUIC) registration surface on the Web server builder [L03.01.01.01.06] - #927
Merged
Merged
Conversation
Commit 6688b71 renamed HttpAuthenticationFeature -> AuthenticationFeature but left HttpContextAuthenticationExtensions (namespace Assimalign.Cohesion.Web.Authentication) without a using for the child .Internal namespace where the concrete type lives, breaking the Web.Authentication build and every project that references it — including the Web.Hosting test project this task needs to verify. Add the missing using. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…al via OnError hook [L03.01.01.19] Implements #881 as a CONSUMER of the shipped #864 OnError chain (PR #893), re-scoped from the issue's IResult design: IResult was withdrawn pre-merge, so the Web area is middleware-first and there is no result-carrier channel. No IResult, no Results.Problem/ExecuteResultAsync, no second handler abstraction. Web.ErrorHandling (new pipeline verbs, homed with AddErrorHandling): - UseErrorHandling() installs the exception-boundary middleware: catches faults escaping downstream, publishes IHttpExceptionFeature (caught exception + path), and on an unstarted response resets it and dispatches through the shipped IErrorHandlingFeature.Handlers chain (registration order, first-true wins) to the ProblemDetails-500 terminal. No-clobber: when IHttpResponseStreamingFeature .HasStarted reports the head is committed, it aborts the one exchange via IHttpContext.CancelAsync instead of half-writing (connection survives). - Developer-detail toggle (off by default) enriches only the boundary's terminal fallback. OnException observation hook + SuppressDiagnosticsCallback are the Cohesion parity for .NET 10 SuppressDiagnosticsCallback (no Microsoft.Extensions logging). Handler faults propagate, never masked (shipped OnError semantics); an observer fault is swallowed (observation must not defeat rendering). - UseStatusCodePages() upgrades a bodyless 4xx/5xx terminal response into problem+json (or a custom responder). Web.Hosting: - The silent Task.CompletedTask pipeline terminal now sets a bodyless 404 for an unhandled request (still 200, no body/content-type/location). It stays payload- free because COHRES002 forbids the runtime module referencing Web.ProblemDetails; UseStatusCodePages upgrades it. A deliberate empty 200 must be terminal (not chain to next) — the TLS integration test's terminal handler is adapted to match. Tests: 22 new Web.ErrorHandling unit tests + 3 end-to-end (UseErrorHandling default, handler-owns, UseStatusCodePages 404 upgrade) + 3 Web.Hosting terminal tests. Docs: Web.ErrorHandling DESIGN/OVERVIEW, Web.Hosting DESIGN, Web README updated; fixed the IHttpErrorHandler/IHttpErrorHandlingFeature naming drift in OVERVIEW. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ature naming drift Review follow-ups on the #881 branch: implement IWebApplicationContext.ContentRootPath on the Web.Routing TestWebApplicationContext (stale since 6688b71) and correct the IHttpErrorHandlingFeature -> IErrorHandlingFeature name drift in the ErrorHandling csproj description, xmldoc, and DESIGN.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…1.02.01.02] Adds server-side content negotiation as a thin layer over the #864 content-serialization registry (#149), reusing the #771 `HttpContentNegotiation` q-value/precedence primitive rather than re-implementing selection. - `HttpContentNegotiationExtensions`: `IHttpContentSerializationFeature.TryNegotiate` (pure, non-throwing seam over the registered writers), `IHttpContext.TryNegotiateContentType` (reads the exchange's Accept), and `IHttpContext.WriteNegotiatedContentAsync<T>` (negotiate -> write via the registry's explicit media-type overload, or compose a 406). - `ContentNegotiator` (internal): collects the writers' concrete media types in server-preference order and delegates exact RFC 9110 §12.5.1 matching to the primitive. - Structured-suffix fallback (the call #864 deferred to #149): a bare base-type Accept range (application/json) is satisfied by a registered structured-suffix writer (application/problem+json) only when exact matching yields nothing; already-suffixed ranges are never widened, q=0 refusals are honored, and `application/*+json` wildcard-suffix ranges stay unsupported (a #771 parser gap, recorded not duplicated). - `Vary: Accept` appended (never clobbering an existing Vary) on negotiated responses and the 406; no acceptable representation is a bodyless 406 outcome the #881 status-code-pages middleware upgrades, while a missing registry stays the existing composition fault. - Media types only; Accept-Charset/Language and the client half are non-goals. DESIGN.md/OVERVIEW.md updated in the same change. 15 new tests (11 unit + 4 pipeline); Web.Serialization 42, Web.ErrorHandling 37, Web.Hosting 49 all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on [L03.01.01.08] New resources/Web feature package housing both directions of HTTP body compression over BCL codecs only (GZipStream/BrotliStream/ZLibStream), AOT-safe, builder-time options, no Microsoft.Extensions.* and no hosting reference. Response side (UseResponseCompression): negotiates gzip/brotli from Accept-Encoding via the shared Http #771 primitives, wrapping IHttpResponse.Body with a first-write-deferred decision stream (there is no header-commit hook). It stamps Content-Encoding, drops the stale Content-Length so the transport re-synthesizes it, and always appends Vary: Accept-Encoding for eligible media types (no-clobber over an existing Accept token). Honors a size threshold without buffering whole responses, never double-compresses already-encoded content, and hands off cleanly to streamed responses. Off by default for HTTPS dynamic content (BREACH, CVE-2013-3587) via an explicit EnableForHttps opt-in. Per-response opt-out via IResponseCompressionFeature. Request side (UseRequestDecompression): transparently inflates gzip/br/deflate bodies by decorating the exchange (IHttpRequest.Body is get-only), enforcing a decompressed- size guard (413) against zip bombs, 415 on unsupported codings, 400 on malformed content, and decoding multi-coding chains (Content-Encoding: gzip, br) in reverse application order. Wiring: App.Web framework manifest, root + Web solutions, resource-web CI matrix, and the Web README project map. Docs: OVERVIEW.md + DESIGN.md. Tests: 27 (in-memory factory E2E for both directions incl. round-trips, threshold, Vary append, 413/415/400, multi-coding; unit tests for the BREACH gate and MIME matcher). Web.ErrorHandling/Serialization/Hosting stack suites remain green. No new Http-core primitive was needed: HttpAcceptParser.ParseAcceptEncoding + HttpContentNegotiation.TrySelectEncoding (#771) already cover content-coding/q-value parsing and identity;q=0 semantics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…port-Security header key [L03.01.01.09] Add resources/Web/Assimalign.Cohesion.Web.HttpsPolicy, one lean feature package pairing both HTTPS-posture concerns as extension(IWebApplicationPipelineBuilder) verbs (no DI/config/logging; refs Web + Http only, never Web.Hosting): - UseHttpsRedirection: insecure request -> bodyless method-preserving redirect (307 default, 308 configurable); Location rebuilt from the request (https scheme, host with its port swapped to the configured HTTPS port — 443 omitted, IPv6 re-bracketed; path verbatim; query reconstructed from the parsed collection). HttpsPort is explicit (Web.Hosting isolation forbids deriving it). - UseHsts: RFC 6797 Strict-Transport-Security composed once at builder time (max-age default 365d, includeSubDomains, preload), emitted on secure responses only (RFC 6797 7.2), loopback (localhost/127.0.0.1/[::1]) excluded by default via the core HttpHostMatcher. Applied post-next (outside the #881 boundary) so it survives the boundary's header reset on a faulted response; skipped on a committed head via IHttpHeaderCollection.IsReadOnly. Connection security is read from the transport-derived typed IHttpRequest.Scheme (#763), not scheme-string sniffing. Also fix HttpHeaderKey.StrictTransportSecurity value from the misspelled "Strict-Transports-Security" to "Strict-Transport-Security" (RFC 6797), with round-trip tests through IHttpHeaderCollection (member name unchanged). Wiring: App.Web manifest, both slnx files, resource-web.yml matrix, README map, OVERVIEW/DESIGN docs. Tests: 32 new (redirection + HSTS) via unit-level context and pipeline-builder doubles; 3 new core Http header-key tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… detection Review follow-up: Request.Scheme is the immediate hop; Web.ForwardedHeaders deliberately never mutates it, so document the plaintext-hop redirect-loop hazard and the IHttpForwardedFeature follow-up rather than leaving it implicit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cies [L03.01.01.12] New resources/Web feature package adapting the BCL System.Threading.RateLimiting engine to the Web pipeline — supplying the middleware, policy model, partition-key surface, and rejection response, never a limiter algorithm (#783). - UseRateLimiting composes a global limiter (applied to every request, acquired up-front with full queueing) plus named policies attached per-endpoint as the sealed RateLimitingMetadata carrier in the routing metadata bag. - Per-endpoint gating mirrors the Web.RequestTimeouts seam: a decorated context reads the matched endpoint's metadata at the router's route-match publication. Since the router matches and dispatches in one middleware, the sync gate uses AttemptAcquire and raises an internal signal the middleware catches to answer before the handler runs. Global + endpoint are additive (both must grant a lease). - Partitioned via PartitionedRateLimiter<IHttpContext> with AOT-safe selectors: RateLimitPartitionKeys.ClientAddress composes the Http.Forwarded effective client (trust-gated; BCP 38 caution documented), plus Header and typed selectors. - Rejection: 429 (configurable) + Retry-After from lease metadata, OnRejected hook (bodyless default composes with #881 status-code pages), OnDecision observation hook in place of a hosting-layer OTel seam (re-scoped, follow-up recorded). - IRateLimitingFeature exposes the decision downstream. Limiters live for the application lifetime; per-request leases released on completion (disposal posture documented). Wiring: App.Web manifest, both slnx, resource-web.yml matrix, README project map, OVERVIEW/DESIGN docs. System.Threading.RateLimiting is not added to the framework manifest — it carries only Cohesion assemblies, matching the existing Resilience.RateLimiting precedent in base App. 20 new tests (15 middleware unit + 5 e2e over WebApplicationTestFactory), all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ception boundary Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
….01.14] Http.Sessions: add the async IHttpSessionStore seam (get/set/renew/remove keyed by session id, opaque byte[] payload, idle-timeout metadata) with the in-memory dictionary moved behind it as the default InMemoryHttpSessionStore. Payload framing is a version-prefixed, length-prefixed binary codec (HttpSessionSerializer) — no reflection — so any store round-trips identical bytes. The concurrency contract is last-commit-wins (documented on the interface + DESIGN.md, pinned by tests); sliding expiration is renew-on-access. An internal store-backed session composes the store with the frame codec. Every existing IHttpSession caller and test is unchanged. Web.Sessions: fill the empty scaffold with the per-request middleware. UseSessions installs a lazy, store-backed session on the exchange (no store I/O or cookie until first access), establishes a hardened session-id cookie (HttpOnly, SameSite=Lax, Secure on HTTPS, session-scoped) via the Http.Cookies model synchronously at id-mint time (before the head commits), and commits after next (persist-if-modified, else slide). Session ids are 128-bit crypto-random base64url; RegenerateSessionIdAsync is the post-auth fixation defense (new id, old id removed, cookie replaced). Distributed backends are deferred to IHttpSessionStore adapters. Wiring: Web.Sessions docs (OVERVIEW/DESIGN), Http.Sessions DESIGN.md scope update, README project-map row, slnx docs entries. Manifest + CI matrix already carried the scaffold; App.Web.Runtime pack validated with both assemblies. Tests: Http.Sessions 47 (23 existing regression + 24 new), Web.Sessions 15 (unit middleware + e2e round-trip over WebApplicationTestFactory). Stack regressions green (ErrorHandling/Serialization/Compression/HttpsPolicy/ RateLimiting/Hosting). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…t be delivered Review follow-ups on the #785 branch: a session first accessed after the response head commits mints an id the client can never present again — skip the store persist instead of littering the store until idle-timeout reaping (externally supplied sessions keep their caller-owned commit lifecycle); pin with a RecordingSessionStore fixture and an existing-cookie-after-head counterpart test; true up the stale Http.Sessions csproj description (Items bag -> typed feature collection). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New resources/Web/Assimalign.Cohesion.Web.Caching feature package: UseOutputCache serves cacheable GET/HEAD responses from an async, tag-aware store without invoking the endpoint, buffering the response body on a miss within per-entry/total size caps. Design: - Async tag-aware IOutputCacheStore seam (get/set with tags, TTL, opaque entries) layered above the sync Caching foundation; default public InMemoryOutputCacheStore adapts Caching.InMemory MemoryCache with SizeLimit + per-entry size accounting and a self-cleaning tag index (precedent: InMemoryHttpSessionStore). - Builder-time policy model: base + named policies + per-endpoint overrides via the sealed OutputCacheMetadata carrier read last-wins at the router's metadata seam. Endpoint discovery uses the router's own side-effect-free Match ahead of UseRouting (async-correct; a hit skips the handler) rather than the sync route-match decorator. - Cache-or-bypass via the #755 typed HttpCacheControl/HttpFreshness primitives: bypass on no-store/private/no-cache, Set-Cookie, Authorization (never cache authenticated by default), non-safe methods, non-200; TTL = policy Duration capped by response freshness. - Vary: the cache key honors the response's own Vary header (RFC 9111 4.1) via a per-primary-key marker + variant entries, so a compressed/negotiated variant is never served to a client that cannot accept it; register before UseResponseCompression. - QUERY (RFC 10008) scoped out (documented follow-up): no non-destructive request-content key seam exists yet; cacheable set is GET/HEAD. - Tag eviction reachable from app code (held store or IOutputCacheFeature). Wiring: App.Web manifest, both slnx files, resource-web.yml matrix, docs (OVERVIEW/DESIGN with the store seam, policy model, bypass matrix, the Vary decision + ordering, QUERY posture, size accounting, non-goals), README row. 28 new tests (store, key builder, middleware bypass matrix, e2e hit/miss/vary/bypass/evict/opt-in). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…acheAuthenticated Review follow-up on the #795 branch: the CacheAuthenticated opt-in shares the response representation, but the Set-Cookie field is per-recipient — a stored entry replaying one client's cookie grant (e.g. a fresh session id) to every hit is a session-leak footgun. Set-Cookie joins the unconditional non-cacheable header set; pinned by a hit-without-Set-Cookie test and documented in DESIGN. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er [L03.01.01.01.06] Add UseHttp3 to WebHostingExtensions as the h3 counterpart of the TCP UseHttp1s/UseHttp2s sugar, resolving the async-materialization mismatch the prior remarks recorded as the reason h3 was omitted. Two overloads: - UseHttp3(Action<QuicConnectionListenerOptions>) — QUIC-native form; the cert flows through ServerAuthenticationOptions (the TlsServerOptions-equivalent). - UseHttp3(Action<QuicConnectionListenerOptions>, TlsServerOptions) — mirrors UseHttp2s ergonomics; the cert flows through the same TlsServerOptions surface. Both register the transport's existing synchronous deferred-factory seam (HttpConnectionListenerOptions.UseHttp3(Func<IMultiplexedConnectionListener>)) and materialize the QUIC listener at server start — never at configuration time — blocking once on QuicConnectionListener.CreateAsync, offloaded to the pool to avoid any SynchronizationContext deadlock. ALPN defaults to h3 and TLS to 1.3 when unset. Members are [SupportedOSPlatform]-annotated; on a platform without QUIC (QuicListener.IsSupported == false) materialization throws PlatformNotSupportedException at start. Coexists with h1/h2 on one listener and feeds Alt-Svc (#754) advertisement with no extra wiring. Tests: registration/ALPN/defer/null-arg unit tests plus platform-guarded materialization, coexistence, and a real-QUIC e2e (server-side Http/3 + https observation load-bearing; client response best-effort because the full h3 response round-trip trips a pre-existing Http.Connections server control-stream defect, H3_CLOSED_CRITICAL_STREAM, reproduced by that library's Http3 example). No new dependencies (Connections.Quic already referenced). Updates DESIGN.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
Author
|
The pre-existing h3 defect surfaced by this PR's e2e is now filed as #928 ([L01.01.11.44], Http area, DiscoveredFeature origin) — including the body-position and bodyless-200 observations to verify against the fix. This PR does NOT close it. |
…ack reconciliation) PRs #920-#925 squash-merged to main leave this stacked branch's copy of those commits content-identical but history-divergent; the only textual conflicts were Web.Caching's wiring lines sitting alphabetically adjacent to Web.Compression's in App.props, the CI matrix, both slnx files, and the README project map - resolved to the union. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… retarget clean after #926 squash-merges) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stack 8/8 of the Web/Http Batch 4b stacked series (base:
feature/L03.01.01.17-output-caching, PR #926).Lane D work in
Web.Hosting(the runtime module legitimately owns listener registration — no new project):UseHttp3besideUseHttp1/UseHttp2inWebHostingExtensions, two overloads: QUIC-native (Action<QuicConnectionListenerOptions>— cert viaServerAuthenticationOptions) and theTlsServerOptionsmirror ofUseHttp2s. Both default ALPN→h3and TLS→1.3 (RFC 9001) while preserving caller choices.QuicConnectionListener.CreateAsyncis bridged once at startup, pool-offloaded so no captured SynchronizationContext can deadlock. Configure callbacks provably deferred (pinned by test).[SupportedOSPlatform(windows/linux/macos)]+PlatformNotSupportedExceptionsurfaced at start; all QUIC tests platform-guarded onQuicListener.IsSupported.Http20|Http30; the e2e proves a real .NET HTTP/3 client completes the QUIC handshake and the pipeline reportsHttpVersion.Http30+HttpScheme.HttpsonIHttpContext.BuildAltSvcHeaderValuereads —AdvertiseAltServicelights up automatically.Pre-existing defect surfaced (not introduced here — being filed separately)
The full h3 response round-trip fails with
H3_CLOSED_CRITICAL_STREAM(0x104) — reproduced with the existingHttp.ConnectionsHttp3 example, independent of this surface. The defect lives in the Http-area h3 server control-stream path; the e2e asserts the server-side dispatch observation and treats the client response as best-effort (documented in test + DESIGN).Tests
Web.Hosting 58 (49 pre-existing + 9 new; QUIC e2e RAN on this machine, no skip) — all passing; stack regressions green (ErrorHandling 37, Compression 27, Caching 29, Sessions 16).
Closes #767
🤖 Generated with Claude Code