feat(09-01): arena-proxy survives SSE, client-IP forwarding, upstream-down
CI / check (push) Has been cancelled

- Forward client IP via x-forwarded-for/x-real-ip so the canonical arena's
  per-IP rate limiting isn't collapsed to one bucket per node.
- 30s AbortSignal.timeout on non-stream requests; SSE fight streams
  (/api/fights/:id/stream) are exempt (long-lived by design).
- On upstream fetch failure, log and return 502 {error} instead of a
  buffered hang or a 500 stack trace.
- fights.ts: set X-Accel-Buffering: no on the SSE stream response so an
  nginx-fronted arena (nginx-proxy-manager) doesn't buffer live fight events.
- docker-compose.yml: document ARENA_UPSTREAM_URL / TRUSTED_PROXY (commented,
  no active value set here — the canonical arena gets its own compose file
  in a later plan).

TDD: added the SSE/XFF/502 tests, confirmed the 502 test failed against the
prior implementation, then implemented to green (9/9 arena-proxy tests,
17/17 combined with rate-limit.test.ts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-07-30 21:32:57 -04:00
co-authored by Claude Fable 5
parent 143ca808e8
commit 0511b97cb9
4 changed files with 133 additions and 2 deletions
+6
View File
@@ -420,6 +420,12 @@ fightsRouter.get('/:id/stream', (c) => {
return c.json({ error: 'Too many SSE connections' }, 429)
}
// nginx (e.g. nginx-proxy-manager fronting the canonical arena) buffers
// proxied responses by default, which would hold every SSE frame until the
// stream closes. This is the documented opt-out — harmless when no nginx
// sits in front of this instance.
c.header('X-Accel-Buffering', 'no')
return streamSSE(c, async (stream) => {
// Track connections
activeSSECount++