feat(websockets): add manual WebSockets.upgrade(f, stream) for mixed HTTP/WS servers#1255
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1255 +/- ##
==========================================
- Coverage 84.50% 84.36% -0.14%
==========================================
Files 28 28
Lines 10648 10689 +41
==========================================
+ Hits 8998 9018 +20
- Misses 1650 1671 +21 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Restore the 1.x ability to upgrade an in-flight HTTP/1.1 server stream to a WebSocket from inside a normal HTTP.listen!/Router stream handler, so a single server can mix ordinary HTTP routes and WebSocket routes. The 2.0 rewrite only shipped the standalone WebSockets.listen! accept loop, dropping this path. - WebSockets.upgrade(f, stream) writes the 101 handshake directly to the connection, then hijacks it from the HTTP/1 server loop via the existing write_closed/read_closed flags + response.close, clears the per-request read deadline so long-lived sockets are not torn down mid-session, and hands any bytes buffered past the handshake to the WebSocket codec. - Refactor _upgrade_response to take subprotocols/check_origin directly so it no longer requires a WebSockets.Server; the Server method is kept as a thin wrapper (no behavior change). - Server Streams now retain their per-connection reader; server body reads go through request_body, so stream.reader is otherwise unused on the server. - Reject upgrades over HTTP/2 with a clear error. - Document upgrade in the 1.x migration guide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a docstring to isupgrade and list HTTP.WebSockets.upgrade / HTTP.WebSockets.isupgrade in the WebSockets API reference so the new manual upgrade entrypoint and its guard are rendered and their @ref links resolve. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Land the WebSocket upgrade feature together with the intermittent-Windows-hang fix it was blocked on: - runtests.jl hang watchdog that dumps task backtraces on suite timeout (how the root cause was captured) - bound _read_until_quiet with the _run_with_timeout watchdog so a Reseau read-deadline strand (JuliaServices/Reseau.jl#107) fails fast instead of hanging; covers direct callers, not just _raw_http_request - guard the write-timeout test's take! so a missed timeout cannot block on an empty channel Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
HTTP.jl 1.x let you upgrade an in-flight server stream to a WebSocket by hand
(
WebSockets.upgrade(f, http::Stream)), which is how a singleHTTP.listen!/HTTP.Routerserver could serve both ordinary HTTP routes and WebSocket routes.The 2.0 rewrite only shipped the standalone
WebSockets.listen!accept loop, sothis manual path went missing. This PR restores it.
How it works
away from the HTTP/1 server loop using the existing
write_closed/read_closedflags plusresponse.close, so the loop's post-handlerteardown becomes a no-op and it stops reading further requests off what is now
a WebSocket.
handler, so a long-lived WebSocket isn't torn down mid-session (covered by a
test that idles longer than
read_timeout).(mirrors the existing client handshake path).
Changes
WebSockets.upgrade(f, stream)— new public API._upgrade_responseparameterized bysubprotocols/check_origin; theServermethod is now a thin wrapper over it (no behavior change — existingserver origin / subprotocol / invalid-key tests still pass).
Streams now retain their per-connection reader soupgradecanrecover buffered bytes. Server body reads go through
request_body, sostream.readeris otherwise unused on the server side.upgrade.Tests
New integration tests: mixed HTTP + WS on one
listen!(text + binary echo),the read-deadline-clearing case, and rejection of non-upgradeable streams.
Existing WebSocket client / server / integration suites and the HTTP/1 server
suite still pass locally.
🤖 Generated with Claude Code