Release 2.4.1: SSE connections now respect middleware headers#65
Merged
dcrockwell merged 2 commits intomainfrom Mar 11, 2026
Merged
Release 2.4.1: SSE connections now respect middleware headers#65dcrockwell merged 2 commits intomainfrom
dcrockwell merged 2 commits intomainfrom
Conversation
## Why This Change Was Made - `upgrade_to_sse` was calling `mist.server_sent_events` inside the controller, which sends HTTP headers to the TCP socket immediately — before middleware has a chance to modify the response. This meant CORS headers, security headers, and any other middleware-applied headers were silently discarded, causing cross-origin SSE requests to fail with CORS errors. ## What Was Changed - Added `upgrade_key` constant to `internal.gleam` for a new deferred upgrade stash - Changed `sse.gleam` to stash an upgrade thunk (a closure that will perform the Mist SSE upgrade) instead of calling `mist.server_sent_events` directly - Changed `handler.gleam` to check for the stashed upgrade thunk after `execute_route` returns the middleware-modified Dream response; if found and status is 200, it extracts headers and calls the thunk with them - Added `extract_dream_headers` helper to convert Dream headers to tuples - Added CORS, rejection, and security-headers middleware to the SSE example app - Added 4 new integration test scenarios: CORS headers, middleware stacking, middleware rejection (401 blocks upgrade), and streaming continuity - Added "Using Middleware with SSE" section to the SSE guide - Bumped version to 2.4.1 ## Note to Future Engineer - The `upgrade_key` stash is separate from `response_key` — WebSocket still uses `response_key` in the outer handler function. Don't merge them or you'll break one of the two protocols. Yes, having two different stash-and-upgrade mechanisms for two different protocols in the same handler is a bit of an architectural smell, but Mist's `websocket` doesn't accept an `initial_response` parameter so the WebSocket side can't use this deferred pattern without an upstream change. Enjoy that asymmetry. - The status check (`200 -> upgrade, _ -> convert`) handles the case where middleware calls `next` (which stashes the thunk) but then overrides the response with a non-200. Don't remove it thinking "middleware rejection already prevents the controller from running" — that's only true for middleware that short-circuits before calling `next`.
SSE connections now respect middleware headers like CORS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release 2.4.1
Release Date: March 11, 2026
Why
SSE connections were silently dropping headers added by middleware (CORS, security headers, etc.), causing cross-origin
EventSourcerequests to fail. This was a regression from the 2.4.0 SSE feature release — the Mist SSE upgrade happened inside the controller before middleware could modify the response, so any headers added by middleware were already on the wire without them.What's Fixed
upgrade_to_ssenow defers the Mist SSE upgrade until after the middleware chain completes. A new "deferred upgrade" pattern stashes an upgrade thunk in the process dictionary, lets middleware run on the dummy response, then the handler extracts headers from the middleware-modified response and performs the upgrade with them.If middleware returns a non-200 status (e.g., 401 from auth), the SSE upgrade is not performed and the error response is returned normally.
What's Included
sse.gleam,handler.gleam, andinternal.gleamexamples/sse/Test Results
Full Release Notes
See releases/release-2.4.1.md