Skip to content

Add support for passing IO objects to Websockets.open()#1251

Merged
quinnj merged 2 commits into
JuliaWeb:masterfrom
JamesWrigley:websockets
Jun 16, 2026
Merged

Add support for passing IO objects to Websockets.open()#1251
quinnj merged 2 commits into
JuliaWeb:masterfrom
JamesWrigley:websockets

Conversation

@JamesWrigley

Copy link
Copy Markdown
Member

This is handy if the caller already has the required stream/socket open.

Written with help from Claude 🤖

@JamesWrigley JamesWrigley self-assigned this May 17, 2026
@JamesWrigley

Copy link
Copy Markdown
Member Author

One failure on 1.6, and it looks like the 32bit windows CI is hanging.

@JamesWrigley JamesWrigley force-pushed the websockets branch 2 times, most recently from 4d2b83c to 2fef03e Compare June 15, 2026 21:02
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.90909% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.16%. Comparing base (b4b0a5e) to head (a21f95e).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
src/http_websockets.jl 90.00% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1251      +/-   ##
==========================================
- Coverage   87.31%   87.16%   -0.16%     
==========================================
  Files          29       29              
  Lines       11289    11326      +37     
==========================================
+ Hits         9857     9872      +15     
- Misses       1432     1454      +22     

☔ View full report in Codecov by Harness.
📢 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.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This is handy if the caller already has the required stream/socket open.
@JamesWrigley

Copy link
Copy Markdown
Member Author

Now rewritten to work on v2 🤞 I think the design is ok (went through a couple rounds with Claude) though I'm certainly no expert.

Comment thread src/http_websockets.jl Outdated
"""
function open(
io::IO;
suppress_close_error::Bool=false,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this keyword isn't getting passed along to the _open_client_websocket_io call; I think I'd prefer a one-line definition here to simplify

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in a21f95e.

Comment thread src/http_core.jl Outdated
Comment on lines +1059 to +1086
"""
Adapter that presents an arbitrary caller-provided `IO` for `WebSockets.open(io)`,
bypassing the connection pool entirely.

The key difference it papers over is read semantics: the HTTP/1 handshake parser
and the WebSocket read loop use `readbytes!(...; all=false)` and treat a `0`
return as EOF, relying on Reseau's "block until at least one byte or EOF"
behavior. A stdlib `TCPSocket` with `all=false` instead returns `0` whenever no
bytes are buffered *yet*, so we reimplement the blocking contract here on top of
`eof`/`bytesavailable`.
"""
struct _RawIOConn{T<:IO} <: IO
io::T
end

# Block until data is available or EOF, then return whatever is buffered (up to
# `nb`) — never a spurious `0` while the stream is still open.
function Base.readbytes!(c::_RawIOConn, b::AbstractVector{UInt8}, nb::Integer=length(b); all::Bool=false)::Int
eof(c.io) && return 0
n = min(bytesavailable(c.io), Int(nb))
return readbytes!(c.io, b, n)
end

Base.read(c::_RawIOConn, ::Type{UInt8}) = read(c.io, UInt8)
Base.write(c::_RawIOConn, x::UInt8) = write(c.io, x)
Base.unsafe_write(c::_RawIOConn, p::Ptr{UInt8}, n::UInt) = unsafe_write(c.io, p, n)
Base.eof(c::_RawIOConn) = eof(c.io)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think this could all be a bit simpler than having a dedicated struct. I think you only really need 2 methods of a new HTTP-specific function called _blocking_readybtes! or something. For Reseau conn types, you'd pass through to readbytes!(; all=false) and for all other IO, you'd have the readbytes! implementation here.

I don't think I realized this subtle different in how Reseau.Conns work, but it seems simpler to have ~6 lines for _blocking_readybtes! vs. this whole struct + passthrough.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ah yeah, that is much simpler. Refactored in a21f95e.

@quinnj quinnj merged commit 4c50080 into JuliaWeb:master Jun 16, 2026
7 of 8 checks passed
@JamesWrigley JamesWrigley deleted the websockets branch June 17, 2026 06:21
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