Fix #91: embed upstream response in splash HTML via internal reverse proxy#92
Fix #91: embed upstream response in splash HTML via internal reverse proxy#92ClaydeCode wants to merge 1 commit into
Conversation
…proxy
Add an internal reverse proxy (app_proxy) that sits between Traefik and
app containers. Instead of routing app traffic directly to containers
(where Traefik's error middleware discards the response body), Traefik
now routes to shard_core's /internal/app_proxy/{path} endpoint.
The proxy:
- Forwards requests to the actual app container using httpx
- For 4xx/5xx responses: captures the full response body and renders the
splash HTML with a hidden <script type="application/json"
id="upstream-response"> element containing the original status code,
content-type, and body
- For 2xx/3xx responses: passes through unchanged
- For connection errors: shows the splash without upstream data
Traefik changes:
- Add app-proxy-prefix middleware (addPrefix: /internal/app_proxy)
- App routers now use service=shard_core + middlewares=[auth, app-proxy-prefix]
instead of the old app-specific service + [app-error, auth] setup
The app-error endpoint is kept as a legacy fallback but is no longer used
for normal app traffic. The splash template now conditionally renders the
upstream-response script tag when upstream_json is provided.
Security: </script> sequences in the body are escaped as <\/script> to
prevent injection through the JSON data island.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
max-tet
left a comment
There was a problem hiding this comment.
That seems way too complex. Traefik already sends error request to shard_core, that is how the splash works. No need for a separate endpoint. Just extend the endpoint that returns the splash screen with the upstream json. No more.
|
Thanks for the review — agreed, the full reverse-proxy in this PR is too heavy. Before I rework it I want to flag a blocker I hit, because I don't think extending The problem: Traefik's On Having So something has to sit inline on the response path to capture the body — that's the only place it exists. The PR did that with a proxy; I just overbuilt it. Proposed lean version (drops most of what made this PR heavy):
Is that acceptable, or do you know a Traefik mechanism to hand the upstream body to the error handler that I've missed? I'll wire up whichever you prefer. |
Summary
Closes #91.
When an app returns a 4xx/5xx error, the splash screen previously swallowed the original response body entirely. This PR implements the desired behavior: the splash looks identical to end users, but the raw HTML contains the original status code and response body in a hidden
<script type="application/json" id="upstream-response">element readable via browser dev tools.Approach
Traefik's
errorsmiddleware discards the original response body before calling the error handler — there is no way to forward it without a custom plugin. The fix is therefore architectural: insert shard_core as a reverse proxy in the path between Traefik and app containers.How it works
app-proxy-prefix(addPrefix:/internal/app_proxy) is added.shard_corewith the new middleware (and no moreapp-errormiddleware)./internal/app_proxy/{path:path}endpoint in shard_core:httpxSecurity
</script>sequences in the embedded body are escaped as<\/script>to prevent injection through the JSON data island.Changes
data/splash.html<script type="application/json" id="upstream-response">shard_core/web/internal/app_error.pyupstream_jsonfield toSplashBehaviour,build_upstream_json()helper,make_splash_behaviour_for_proxy(),render_splash_response()shard_core/web/internal/app_proxy.pyshard_core/web/internal/__init__.pyapp_proxyroutershard_core/service/traefik_dynamic_config.pyapp-proxy-prefixmiddleware; change app routers to useshard_coreservicetests/test_app_error.pytests/test_app_proxy.pytests/test_traefik_dyn_spec.pyRecommended reading order
data/splash.html— template change (one line)shard_core/service/traefik_dynamic_config.py— Traefik config change (routing strategy)shard_core/web/internal/app_error.py— shared helpersshard_core/web/internal/app_proxy.py— the proxy itselftests/test_app_proxy.py— tests🤖 Generated with Claude Code