HTTP cache RFC 9111 conformance
The pinned public http-tests/cache-tests
suite (b55b8bda3dbb8c927c04e85bd8d496a8caa3e4ba), behind
cache-tests.fyi, runs against three actual
implementation assets, in default HybridCache and streaming filesystem modes.
The front-end always uses .NET 10, ASP.NET Core, and YARP 2.3.0; it does not use
HttpListener, URL ACL registration, an obsolete ASP.NET server, or a custom HTTP
parser. The handler runs in Shared mode.
| Implementation | Cache execution | Platforms |
|---|---|---|
net10.0 |
Direct handler, contracts, and filesystem .NET 10 assemblies | Windows, Linux |
netstandard2.0 |
Direct .NET Standard 2.0 assemblies in a .NET 10 host | Windows, Linux |
net472 |
Separate .NET Framework console worker using the Framework CLR and all three net472 assemblies |
Windows only |
The Standard host is a separate project with independent bin/obj directories.
Its references explicitly select netstandard2.0; test-only
Microsoft.Bcl.AsyncInterfaces and Microsoft.Bcl.TimeProvider dependencies
supply forwarding assemblies needed by that host. Startup asserts
TargetFrameworkAttribute on the loaded handler, contracts, and filesystem
adapter. The Framework worker also asserts that
RuntimeInformation.FrameworkDescription starts with .NET Framework.
Loading a net472 assembly on modern .NET does not satisfy this harness.
Upstream support warning: HybridCache 10.8.0 reports that net472 is
unsupported and untested upstream. The warning remains visible in build logs;
these runs do not change upstream support. See
framework compatibility.
Run locally
Requires .NET 10 SDK, Node.js 22+, npm, and Git. Framework runs also require a Windows host with .NET Framework 4.7.2 or newer. Reference assemblies are restored from NuGet; the harness does not install machine-wide SDKs or modify machine ACLs.
From the repository root on Windows:
.\hybrid-cache-handler\conformance\run-conformance.ps1 -Framework net10.0
.\hybrid-cache-handler\conformance\run-conformance.ps1 -Framework netstandard2.0 -FileSystem
.\hybrid-cache-handler\conformance\run-conformance.ps1 -Framework net472 -FileSystemOn Linux/macOS, from the repository root:
./hybrid-cache-handler/conformance/run-conformance.sh --framework netstandard2.0 --file-systemOmit the framework to use net10.0; omit filesystem mode to use default
HybridCache. -TestId ID / --test-id ID runs a single diagnostic fixture and
does not gate or produce a full-suite results file. Ports default to
available loopback ports; -OriginPort/-ProxyPort,
--origin-port/--proxy-port, or the Bash wrapper's
ORIGIN_PORT/PROXY_PORT environment variables override them. An occupied
requested port fails rather than reusing an unrelated listener.
Both shell entrypoints delegate to the same Node launcher. It clones the public
suite if missing, verifies the pinned revision and unmodified fixture source,
and installs dependencies only when node_modules is missing. It runs the pinned
npm scripts' Node entrypoints with their npm configuration environment directly,
so process ownership is unambiguous and paths containing spaces do not need shell
escaping. Complete initial clone/install and builds before parallel local runs;
distinct framework/mode cells have separate result/log filenames.
Shared options preserve the direct modern baseline: shared-cache mode,
50 MiB maximum cacheable content, no enabled compression, threshold 1 MiB in
default mode or 1 byte in filesystem mode, no redirects/cookies/system proxy,
and HTTP/1.1 forwarding. The worker uses Framework's HttpClientHandler;
direct hosts retain SocketsHttpHandler. No header-reflection workaround is
used. Legacy public header enumeration can normalize case, spacing, dates, and
value boundaries. Investigate these differences rather than silently changing
the modern baseline.
Framework transport isolates connection pools by the suite's fixture UUID,
while retaining one shared cache handler and HybridCache. Some pinned fixtures
deliberately send a body longer than Content-Length. HttpWebRequest can reuse
that contaminated connection for another fixture, yielding spurious
ResponseStatusLine failures or retries. Each fixture reuses its own
HttpClientHandler; requests and response bytes are not rewritten. At most 1,024
fixture pools are allowed, all owned by the worker. Modern/Standard pooling is
unchanged.
Framework bridge
Each request gets its own randomly named-pipe connection to the persistent
worker-owned cache. A bounded, length-prefixed binary envelope carries method,
absolute URI, HTTP version, separate HTTP/content header arrays, binary body,
status, and reason phrase. Header values, notably Set-Cookie, are not joined.
Explicit Content-Length values are retained, including HEAD responses; buffering
does not invent Content-Length when none was declared. This is an IPC envelope
around framework HTTP messages, not an HTTP parser.
Frames are bounded to 64 MiB, bodies to 60 MiB, individual strings to 1 MiB, header entries to 512, and values per entry to 4,096. Encoding checks frame/body budgets before each write and caps buffer capacity accordingly; string byte lengths are checked before allocating UTF-8 payloads. The worker accepts at most 32 concurrent connections. Invalid lengths, truncated frames, unexpected message kinds, misplaced headers, and trailing data are rejected. This test-only bridge buffers bodies; it is not a production streaming transport.
Cancellation closes the request pipe and cancels the worker's origin request. Worker/protocol errors poison front-end health rather than becoming successful fallback responses. Readiness verifies the launched front-end PID, exact implementation provenance, and worker handshake. Health and process ownership are checked again after the suite, before accepting results. The worker exits if its parent dies; normal cleanup stops only explicitly launched PID trees and removes only that run's uniquely created content directory.
Origin transport failures are a distinct IPC message: deliberate closed-origin fixtures exercise YARP's normal error behavior without misclassifying a healthy worker as failed. Cancelling a response-body copy disposes its content to interrupt blocked legacy reads, as well as cancelling the origin request.
Results and gating
results-<framework>-<default|filesystem>.json, matching *-provenance.json,
and build/origin/proxy/client/comparison *.log files are written in
the conformance directory
and ignored by Git. The checks workflow uploads them even on failure.
Every full run uses the unchanged
expected-results.json:
previously passing fixtures must still pass, and every baseline fixture must
be present, including known failures. New passes and changed known-failure
categories are reported. Single-fixture diagnostics cannot masquerade as a full
run. -Update / --update is an explicit maintenance operation restricted to
full modern runs; review differences before using it.
legacy-expectations.json
is a reviewed, target-scoped exact-outcome overlay, not a rewritten baseline
or modified suite. It accepts only recorded header-formatting messages:
Cache-Control directive ordering/case and Content-Type optional whitespace.
A different outcome on a previously passing fixture, or any missing baseline
fixture, still fails. Raw pass/failure counts are not inflated by these
exceptions. Modern runs cannot use the overlay.
The pinned suite checks most origin-header string equality after request sequences and cache assertions; diagnostics also demonstrate cached responses for these formatting-only cases. Explicit header assertions can stop a fixture earlier, so an accepted formatting outcome does not establish that every later assertion ran.
The compatibility change recorded six local Windows runs of 365 fixtures each: 344 raw passes per modern mode and 324 per legacy mode, with no unaccepted baseline regressions. Framework execution used .NET Framework 4.8, not an actual 4.7.2 runtime. These are recorded results, not a conformance certification; the suite also measures optional optimizations and does not expect every fixture to pass. The CI matrix covers all six Windows cells and four modern/Standard Linux cells. Linux execution was configured in CI but not verified locally as part of that change.
Debug and maintain the harness
From the repository root, debug one Framework fixture:
.\hybrid-cache-handler\conformance\run-conformance.ps1 -Framework net472 -TestId <test-id>After reviewing new passes from a full modern run, ratchet the baseline:
.\hybrid-cache-handler\conformance\run-conformance.ps1 -Framework net10.0 -UpdateOr on Linux/macOS:
./hybrid-cache-handler/conformance/run-conformance.sh --framework net10.0 --updateReview and commit expected-results.json. Run harness tests from the repository
root:
node --test hybrid-cache-handler/conformance/compare-results.test.mjs hybrid-cache-handler/conformance/worker-lifecycle.test.mjs
dotnet run --project hybrid-cache-handler/conformance/ConformanceProtocol.Tests/ConformanceProtocol.Tests.csproj -c ReleaseComparison tests cover missing passing/known-failure fixtures, empty output, regressions, and improvements. Protocol tests cover metadata/body fidelity, cookie boundaries, content separation, HEAD lengths, fragmented reads, invalid lengths, truncation, trailing data, and cancellation. Size tests cover exact frame/string boundaries, combined metadata/header/body budgets, and rejection before buffer growth or later fields.
The Windows-only lifecycle test additionally exercises binary PATCH forwarding,
multiple Set-Cookie fields, origin disconnects, cancellation during a stalled
body, and worker termination. Its diagnostic output is saved in
worker-lifecycle.log.