fix(api): vary Souin cache key on Accept to fix admin API doc discovery - #682
Merged
Conversation
The admin loads in Hydra mode and calls parseHydraDocumentation(window.origin),
i.e. a GET / with `Accept: application/ld+json`. Souin caches / under the key
`GET-...-/` without including Accept, so the first cached representation (the PWA
HTML homepage) is served for every request, including the admin's ld+json
entrypoint fetch. api-doc-parser receives text/html, gets no JSON-LD body, and
throws "An empty response was received for the entrypoint URL", leaving the admin
stuck on "This page couldn't load".
Add `key { headers Accept }` to the Souin cache block so content-negotiated URLs
(/, /books, ...) are cached per representation. Applied to both the Helm chart
values and compose.prod.yaml for parity.
Refs #681
vincentchalamon
force-pushed
the
fix/souin-cache-accept-key
branch
from
August 7, 2026 07:30
14a2fdb to
951039b
Compare
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.
Problem
https://demo.api-platform.com/adminfails to load after signing in as admin, with the console error:Root cause
The admin runs in Hydra mode and calls
parseHydraDocumentation(window.origin), i.e. aGET /withAccept: application/ld+json./is content-negotiated: browsers (Accept: text/html) get the PWA homepage, API clients (Accept: application/ld+json) get the Hydra entrypoint. The Souin HTTP cache added in #619 keys/asGET-<host>-/without includingAccept, so the first cached representation (the PWA HTML, whoseVarydoes not listAccept) is served for every request, including the admin'sld+jsonentrypoint fetch.api-doc-parser'sfetchJsonLdthen seescontent-type: text/html, returns nobody, andparseHydraDocumentationthrows "An empty response was received for the entrypoint URL". The CORS hint in the message is generic and misleading; CORS is not involved.Observed on production (read-only):
GET /withAccept: application/ld+jsontext/html—cache-status: Souin; hit; key=GET-...-/(noAcceptin key)application/ld+json— the real API entrypointFix
Add
key { headers Accept }to the Souincache {}block so content-negotiated URLs (/,/books, ...) are cached per representation.headersis additive to the default key (method+host+path+query). Applied to both the Helm chart values andcompose.prod.yamlfor parity.Only
Acceptis required. Responses already emitVary: Accept(from content negotiation) plusContent-Type, Authorization, Origin(fromapi_platform.http_cache.vary) andAccept-Encoding(Caddyencode); Souin honors these per RFC 7234, so per-user/-origin variants stay isolated without puttingAuthorizationin the key (which would fragment the shared cache per token). The bug was specifically the cross-backend collision at/(PWA HTML vs API JSON-LD), which onlyAcceptin the key resolves.Verification
helm templateconfirms the renderedcaddy-global-optionsConfigMap value now contains thekey { headers Accept }block.Tradeoff / follow-ups
Acceptfragments cached entries by the rawAcceptvalue (browsers vary it), lowering hit-rate on non-negotiated resources. Acceptable for the demo; a narrowercache_keysscope or a Vary-based approach (PWA emittingVary: Accept) would avoid it but is broader.noevictionpolicy at a 128Mi cap; a wider keyspace mildly raises OOM risk. Consider--maxmemory+allkeys-lruas a separate hardening.Note
Production is deployed via Flux from a separate GitOps repo. If that repo overrides
php.caddyGlobalOptionsinstead of inheriting the chart default, the samekey { headers Accept }one-liner must be added to its Souin block for the fix to reach production.Refs #681