Skip to content

perf(proxy): raw passthrough on plain routes, shared PUT path, bounded rate limiter - #6

Merged
Sebas1705 merged 1 commit into
mainfrom
perf/phase2-raw-proxy
Nov 12, 2026
Merged

Sebas1705 merged 1 commit into
mainfrom
perf/phase2-raw-proxy

Conversation

@Sebas1705

Copy link
Copy Markdown
Contributor

Depends on

AxiomNode/shared-sdk-client#3 (adds forwardHttpRaw and the @axiomnode/shared-sdk-client/proxy-raw export). This PR cannot build until that PR is merged, because CI checks out shared-sdk-client main. The dependency declaration is unchanged (file:../../shared-sdk-client/typescript, lockfile untouched), exactly as the repo resolves the SDK today.

What changes

  1. Raw passthrough on plain forwarding routes (src/app/routes/proxy.ts). forwardRequest now uses forwardHttpRaw by default: the request body goes upstream as the original bytes with the original content-type, and the upstream body comes back as a Buffer and is sent as-is (status and content-type preserved; as before, no other upstream headers are copied to the client). The upstream timeout now also covers reading the body.
    • application/json bodies reach handlers as the original Buffer via a parser registered in proxyRoutes. A route that reads request.body itself opts back in with config: { parseJsonBody: true } (only PUT /internal/admin/ai-engine/target), using Fastify's default JSON parser, so its 400s are unchanged.
    • Not changed (stay in JSON mode, per the "validate/transform" rule): GET /v1/mobile/games/quiz/random, GET /v1/mobile/games/wordpass/random, GET /v1/backoffice/users/leaderboard. The three ai-engine routes that only strip language from the query still go raw (body untouched). Tell me if you want the random routes raw as well: they are the largest responses, and the query validation does not depend on the response body.
  2. PUT dedupe: the ~45-line hand-written PUT fetch branch is deleted; PUT goes through the same shared helper (SDK ForwardMethod now includes PUT).
  3. Rate limiter eviction (rateLimiter.ts): no more O(n) scan per request when the map is full. Expired buckets are swept at most once per window; if still over RATE_LIMIT_MAX_TRACKED_KEYS, the oldest-inserted buckets are dropped in O(1) (reset keys are re-inserted so the front of the Map is the oldest window). The map is now actually bounded under an IP spray (previously it grew without limit once everything was fresh). For normal traffic limits behave the same.

Client-visible differences (edge cases)

  • Malformed JSON request bodies on raw routes are no longer rejected at the gateway; the upstream BFF (Fastify) rejects them with its own 400.
  • If an upstream returns invalid JSON with a JSON content-type, the bytes are passed through instead of being replaced by {} (the one existing test that pinned this was updated). An empty JSON body is still answered as {}.
  • A text/plain request body is now forwarded as sent with its own content-type (before: JSON-quoted string with a JSON content-type).
  • Unsupported request content types are still rejected with 415 at the gateway.

Overlap with open PRs / not done here

Verification

Node 22, in src/: npm run build OK, npm run lint OK, npx vitest run 130/130 passing (17 files), against the SDK branch of shared-sdk-client#3 linked locally. New tests (proxyRaw.test.ts, real HTTP upstream, no fetch mocks): byte-identical request and response bodies, content-type and status preserved, error statuses, empty/204/text responses, PUT via the shared path, no body when none sent, malformed JSON left to upstream, 415 for unknown types, 504 on a stalled body, JSON-mode routes still validate, admin target still parses JSON. Rate limiter: cap under an IP spray, sweep at most once per window, expired buckets still removed, limits still enforced.

Not verified: CI (blocked until the SDK PR is merged), a run against real BFFs, and any load/CPU benchmark (the expected gain is an estimate from the review, not measured).

…ound the rate limiter

Plain forwarding routes now use the SDK forwardHttpRaw: request and response bodies are forwarded as Buffers without JSON.parse/stringify, and the upstream timeout covers the body. Routes that validate input stay in JSON mode; the admin target route keeps parsing its own body. The hand-written PUT fetch path is gone. The rate limiter sweeps expired buckets at most once per window and otherwise drops the oldest bucket in O(1) instead of scanning the whole map on every request.
@Sebas1705
Sebas1705 force-pushed the perf/phase2-raw-proxy branch from 56ec2ad to 66ec41a Compare November 12, 2026 17:53
@codecov

codecov Bot commented Nov 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@Sebas1705
Sebas1705 merged commit 8eae365 into main Nov 12, 2026
5 of 6 checks passed
@Sebas1705
Sebas1705 deleted the perf/phase2-raw-proxy branch November 12, 2026 17:55
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.

1 participant