Conformance

Results from the cache-tests suite. 284 of 365 tests pass, and each of the other 81 has a written reason.

"RFC compliant" is easy to write. This page is what is behind it for Weir: the http-tests/cache-tests suite, run against examples/weirproxy with default settings.

Tests in the suite (commit d644cf4)365
Pass284
Fail, each explained below81

The suite's authors say that passing everything means nothing: many tests check optional behavior, and some check behavior a careful shared cache should refuse. Most of Weir's 81 are deliberate. Weir ignores client no-cache by default, refuses to store Set-Cookie responses, does not store 206 responses, and treats a malformed max-age as zero. Each row below gives the reason and the decision or requirement behind it.

A nightly job reruns the suite and fails only when a test that passed in this baseline stops passing. Reproduce it with make cache-tests.

RFCs implemented

RFCSubject
9111HTTP caching (the shared-cache half)
9110HTTP semantics: conditional requests, ranges, Vary
9211the Cache-Status header
9213targeted cache-control (CDN-Cache-Control)
9875cache groups and group invalidation
5861stale-while-revalidate and stale-if-error

The requirement-by-requirement mapping is in section 8 of the technical specification.

The 81 expected failures

Rendered from docs/cache-tests-expected-failures.md. The reasons were read off the requirements and the suite's request logs. They were not each traced through the code.

By design

TestsWhy Weir differsDecision
ccreq-ma0, ccreq-ma1, ccreq-magreaterage, ccreq-max-stale, ccreq-max-stale-age, ccreq-min-fresh, ccreq-min-fresh-age, ccreq-no-cache, ccreq-no-cache-etag, ccreq-no-cache-lmClient request directives that force revalidation are ignored unless Client.HonorRevalidation is setD5, FR-SRV-8
ccreq-no-storeRequest no-store stops the response being stored; it does not bypass a stored entryFR-SRV-7
stale-503, stale-closeStale is served only when the origin permits it (stale-if-error), and only for the error conditions in FR-STL-2 (a 503 or a closed connection with max-age=2 alone is not one)D6, FR-STL-2
stale-warning-become, stale-warning-storedThe setup needs a stale reuse the origin did not permit; Warning is also obsolete in RFC 9111D6, D7
status-299-fresh, status-303-fresh, status-400-fresh, status-499-fresh, status-500-fresh, status-502-fresh, status-503-fresh, status-504-fresh, status-599-fresh, heuristic-599-cachedOnly the statuses in Storable.Statuses are storedFR-STO-2, D39
method-POST, head-200-freshness-update, head-200-retain, head-200-update, head-410-update, head-writethroughOnly a forwarded GET response is stored, so a POST or HEAD response never updates an entryFR-STO-1
headers-store-Set-Cookie, other-set-cookieA response with Set-Cookie is not stored unless Storable.StripSetCookie is setFR-STO-6, D31
conditional-etag-forward, conditional-etag-forward-unquoted, conditional-etag-vary-headers-mismatchClient If-None-Match is not forwarded on a cacheable request; only the keyed, allowed and protocol headers areD4
304-etag-update-response-Content-Encoding, 304-etag-update-response-Content-TypeA 304 does not change Content-Encoding or Content-Type: they describe the stored bodyFR-SRV-3
304-etag-update-response-Set-CookieThe setup response carries Set-Cookie, so it is never storedFR-STO-6, D31
freshness-max-age-100a, freshness-max-age-a100, freshness-max-age-decimal-five, freshness-max-age-decimal-zero, freshness-max-age-two-fresh-stale-sameline, freshness-max-age-two-fresh-stale-sepline, freshness-max-age-two-stale-fresh-sameline, freshness-max-age-two-stale-fresh-seplineA non-integer max-age, or one repeated with different values, makes the lifetime zeroFR-FRS-2
freshness-max-age-dateAge ignores apparent_age, so a skewed origin Date does not turn the cache offFR-FRS-4, T-30
freshness-expires-wrong-case-tzHTTP dates are case-sensitive; gMT is an invalid Expires, a time in the pastFR-FRS-2
heuristic-delta-5, heuristic-delta-10, heuristic-delta-30The heuristic lifetime is 10% of Date minus Last-Modified, so 0.5 to 3 seconds hereFR-FRS-3
other-age-delayAge is added to responses served from a stored entry, not to the response that triggered the fetchFR-FRS-7
conditional-etag-quoted-respond-unquoted, conditional-etag-unquoted-respond-quoted, conditional-etag-unquoted-respond-unquoted, conditional-etag-strong-generate-unquoted, conditional-etag-strong-respond-obs-text, conditional-etag-weak-respond-backslash, conditional-etag-weak-respond-lowercase, conditional-etag-weak-respond-omit-slashEntity tags are compared as RFC 9110 §8.8.3 defines them; unquoted, mis-cased or malformed tags never matchFR-SRV-2, FR-SRV-3
age-parse-numeric-parameter, age-parse-parameterAn Age with parameters (7200;foo=bar) is not a delta-seconds value, so it is ignored (age 0) as RFC 9111 §5.1 says for an invalid AgeFR-FRS-4, RFC 9111 §5.1
headers-omit-headers-listed-in-Cache-Control-no-cache, headers-omit-headers-listed-in-Cache-Control-no-cache-singleA qualified no-cache="a" is read as unqualified (validate before every reuse), the same strict reading as qualified privateFR-STO-4, FR-SRV-1
vary-normalise-spaceA keyed header value is compared exactly; 1,2 and 1, 2 are two variants. An "optimal" test; normalizing would be a key-boundary changeFR-KEY-9, P2
interim-102, interim-103, interim-no-header-reuse, interim-not-cached1xx responses are not relayed to the client (the final response is cached correctly). Relaying is not a Phase 1 goalD43
headers-store-Transfer-EncodingThe suite sends an invalid Transfer-Encoding value and Go's HTTP client rejects the response, so Weir answers 502 for a malformed origin responseFR-FWD-7, ErrOrigin
304-etag-update-response-ETagThe suite reports a retry (↻), which its authors say needs manual interpretation; nothing is assertedno assertion to decide (suite limitation)
conditional-lm-fresh-no-lmThe suite sends If-Modified-Since 3000 s before the response's Date and the response has no Last-Modified. FR-SRV-2 compares the stored Date, which is 3000 s later than the client's If-Modified-Since, so the resource counts as modified and the answer is 200 (RFC 9110 §13.1.3). The test expects 304FR-SRV-2
pragma-response-no-cache-heuristicGo's net/http client rewrites a response Pragma: no-cache without Cache-Control into Cache-Control: no-cache before Weir sees it (fixPragmaCacheControl), so the engine reads a real no-cache and validates. The error is on the safe side, and telling the two apart would mean parsing responses outside the standard libraryFR-FRS-3, RFC 9111 §5.4

Targeted fields and partial content

TestsWhy Weir differsDecision
cdn-fresh-cc-nostoreAn "optimal" test: it expects CDN-Cache-Control: max-age to override Cache-Control: no-store. Weir lets no-store, private and no-cache bind from either field, so a per-user page marked only in Cache-Control is never sharedD12, FR-TCC-3, T-34
cdn-max-age-case-insensitiveCDN-Cache-Control is an RFC 9651 Dictionary, whose keys are lowercase; MaX-aGe is not a valid key, so the field is ignored (RFC 9213 §2.1)D12, FR-TCC-1
partial-store-partial-complete, partial-store-partial-reuse-partial, partial-store-partial-reuse-partial-absent, partial-store-partial-reuse-partial-byterange, partial-store-partial-reuse-partial-suffixStoring a 206 response is a non-goal: Weir serves ranges from complete objects onlyD11, FR-STO-2