Skip to content

feat(cache): warn once when a credential makes a route bypass the backend - #350

Merged
allen0099 merged 1 commit into
masterfrom
feat/326-bypass-warning
Sep 29, 2026
Merged

allen0099 merged 1 commit into
masterfrom
feat/326-bypass-warning

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Summary

A request with Authorization or a session bypasses the shared backend on a plain @cache route. Until now each bypass was logged only at DEBUG, so a route with no cache hits gave no sign of why. That affects SPAs that send Authorization on every request and, since #338, every request that carries a session.

  • The first bypass per (route, credential kind) is logged once at WARNING on fastapi_cachex.cache. The message names the route template and the credential (an Authorization header / a session token / non-empty session data). It points to public=True for a response that is the same for every user, noting that this also sends Cache-Control: public downstream, and to cache_authorized=True with a per-user key_builder for a per-user response. The per-request DEBUG log is unchanged.
  • State: each decorated function holds its own _BypassWarner. Inside it, a set is keyed by (route template, credential kind), so one handler registered on two routes warns for each, and every test's fresh app starts with no state. The key is the route template (scope["route"].path), never the requested path. That keeps the set bounded, keeps client-chosen paths out of the log, and means /items/1 and /items/2 share one warning. Without a matched route, the handler's qualified name is used.
  • Thread safety: the wrapper runs on an event loop, but one decorated function can serve apps on loops in several threads, and a set's check-then-add is not atomic across threads. A threading.Lock makes "once" exact. The lock is taken only on a bypass whose pair has not been recorded yet: an unlocked membership check returns early once it has.
  • No leaks: the warning contains only the route template (formatted with %r) and the credential kind, never a header value or token.
  • Docs: a new "Requests with credentials" section in docs/HTTP_CACHING.md explains which option to choose and describes the warning. It is mirrored in i18n/zh-TW/docs/HTTP_CACHING.md.
  • Changelog fragment changelog.d/326.added.md.

Tests

tests/session/test_cache_bypass_warning.py covers:

  • Every credential kind (Authorization, session header, session cookie, Starlette request.session data): exactly one WARNING across three requests, three DEBUG lines, and no token in any log line.
  • Two routes each warn, and one handler on two routes warns for each.
  • Different credential kinds on one route each warn.
  • public=True and cache_authorized=True routes, credential-less requests and private=True routes do not warn.
  • A deterministic test that the check under the lock drops a pair another thread recorded.

Mutation checks. Each one fails at least one test:

  • dropping the once-only check;
  • never recording a pair;
  • dropping the re-check under the lock;
  • dropping the credential or the route from the key;
  • keying by the requested path;
  • warning on public routes.

Closes #326

…kend

The Authorization and session bypass was logged only at DEBUG, so a route that never hit the cache gave no sign of why. The first bypass per route and credential kind is now logged at WARNING, naming the route template and the credential kind and pointing to public=True or cache_authorized=True with a per-user key_builder. Documents the choice in HTTP_CACHING.md (en, zh-TW).

Closes #326
@allen0099
allen0099 merged commit 39a7193 into master Sep 29, 2026
12 checks passed
@allen0099
allen0099 deleted the feat/326-bypass-warning branch September 29, 2026 06:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@cache: Authorization requests get a 0% hit rate on public routes and only a DEBUG log says why

1 participant