Skip to content

fix(sse): flush response headers before user handler runs#1038

Merged
wolveix merged 1 commit into
danielgtaylor:mainfrom
alswl:fix/sse-flush-headers-on-open
May 31, 2026
Merged

fix(sse): flush response headers before user handler runs#1038
wolveix merged 1 commit into
danielgtaylor:mainfrom
alswl:fix/sse-flush-headers-on-open

Conversation

@alswl

@alswl alswl commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • sse.Register set Content-Type: text/event-stream but never committed the response status or flushed before invoking the user handler.
  • For handlers that wait on a channel before sending the first event, the browser's EventSource.onopen did not fire until the first event arrived — headers stayed buffered.
  • Fix: call ctx.SetStatus(http.StatusOK) and flusher.Flush() right after the flusher is discovered, so headers are pushed to the client immediately.

Fixes #1037

Root cause

  1. huma.Register skips ctx.SetStatus(status) on the outBodyFunc path (huma.go:1166-1168), so WriteHeader(200) is never called for streaming responses.
  2. sse.Register's Body callback only flushed inside send() after writing event data, so until the first event the client saw no response headers.

Why this matters — spec references

WHATWG HTML Living Standard §9.2 Server-sent events binds onopen to the arrival of the response headers, not to the first event. Once the response is verified as 200 with Content-Type: text/event-stream, the user agent must announce the connection:

When a user agent is to announce the connection, the user agent must queue a task which, if the readyState attribute is set to a value other than CLOSED, sets the readyState attribute to OPEN and fires an event named open at the EventSource object.

MDN — EventSource: open event restates the same contract:

The open event is fired when a connection with an event source is opened. This occurs after the initial HTTP connection to the server has been successfully established.

In Go's net/http, response headers are not put on the wire until WriteHeader is called and an http.Flusher.Flush() pushes the buffered bytes. Without both, a handler that blocks waiting for the first event leaves the client stuck in CONNECTING — violating the spec's expected timing.

Test plan

  • go test ./sse/... -count=1 — all existing tests pass
  • New regression test sse flushes headers before first event uses an orderedWriter to assert WriteHeader(200) and Flush both happen before the user handler blocks on a channel
  • Verified the new test fails on main without the fix and passes with it

The SSE Body callback set Content-Type but never committed the status
or flushed the response writer before invoking the user handler. For
handlers that wait on a channel before sending the first event, the
browser's EventSource.onopen did not fire until the first event
arrived because headers stayed buffered.

Call SetStatus(200) and Flush() right after the flusher is discovered
so headers are sent to the client immediately. Adds a regression test
that asserts WriteHeader and Flush happen before the handler blocks.

Fixes danielgtaylor#1037
@codecov

codecov Bot commented May 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.14%. Comparing base (39f388e) to head (8fb2e9e).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1038   +/-   ##
=======================================
  Coverage   93.14%   93.14%           
=======================================
  Files          23       23           
  Lines        4898     4901    +3     
=======================================
+ Hits         4562     4565    +3     
  Misses        272      272           
  Partials       64       64           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@wolveix wolveix merged commit 5e980c7 into danielgtaylor:main May 31, 2026
4 checks passed
@wolveix

wolveix commented May 31, 2026

Copy link
Copy Markdown
Collaborator

Awesome fix, thanks so much! This will be included in our next release.

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.

SSE: response headers not flushed until first event, delaying EventSource.onopen

2 participants