Skip to content

fix(appkit): harden SSE response headers in setupHeaders#331

Merged
MarioCadenas merged 3 commits intomainfrom
fix/sse-writer-setup-headers
Apr 30, 2026
Merged

fix(appkit): harden SSE response headers in setupHeaders#331
MarioCadenas merged 3 commits intomainfrom
fix/sse-writer-setup-headers

Conversation

@MarioCadenas
Copy link
Copy Markdown
Collaborator

@MarioCadenas MarioCadenas commented Apr 30, 2026

What

Improves the SSE response headers set by SSEWriter.setupHeaders() to be more correct and proxy-friendly.

Why

The previous header set had two issues that could cause problems in real deployments:

  • Connection: keep-alive is forbidden by the HTTP/2 spec and was being stripped or rejected by HTTP/2 intermediaries anyway. Node.js manages keep-alive internally.
  • Content-Encoding: none is not a valid header value and could trigger 502 Bad Gateway or RST_STREAM errors in strict proxies (nginx, AWS ALB, GCP LB, Cloudflare).

There were also two missing proxy-buffering mitigations:

  • Cache-Control: no-transform tells proxies not to modify the response body (important for streaming).
  • X-Accel-Buffering: no is the de-facto header for disabling response buffering in nginx and nginx-backed CDNs/proxies (Cloudflare, AWS CloudFront, GCP, and most corporate reverse proxies honour it).

Changes

File Change
packages/appkit/src/stream/sse-writer.ts Updated headers; Connection and Content-Encoding removed; X-Accel-Buffering: no added

- Set Content-Type to text/event-stream; charset=utf-8
- Change Cache-Control to no-cache, no-transform (prevents proxies
  from buffering/transforming the stream)
- Add X-Accel-Buffering: no to disable nginx/proxy response buffering
  (respected by Cloudflare, AWS ALB, GCP, and most corporate proxies)
- Remove Connection: keep-alive (forbidden by HTTP/2; Node manages it)
- Remove Content-Encoding: none (invalid header value; causes 502/RST
  in strict intermediaries)
- Write a ": ok\n\n" sentinel comment immediately after flushHeaders so
  any buffering proxy is forced to release the first chunk to the client
  before the upstream generator yields its first real event. Prevents
  "Failed to fetch" on cold-start SQL warehouses.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Writing res.write() synchronously inside setupHeaders fires before the
caller attaches error handlers to the response stream. A failed write
can emit an async error event on the stream that is not caught by the
try/catch, potentially surfacing as a connection reset. The heartbeat
already keeps the stream alive during cold starts, so the sentinel is
redundant and risky.

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
- Update ServingPlugin._handleStream to use the same hardened headers
  as SSEWriter (charset=utf-8, no-transform, X-Accel-Buffering: no,
  drop Connection and Content-Encoding)
- Update all test assertions to reflect the new header values

Signed-off-by: MarioCadenas <MarioCadenas@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@atilafassina atilafassina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice detective work! 🚀

@MarioCadenas MarioCadenas merged commit 8b90a15 into main Apr 30, 2026
8 checks passed
@MarioCadenas MarioCadenas deleted the fix/sse-writer-setup-headers branch April 30, 2026 08:49
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.

2 participants