Repository navigation
WebSocket: replace hand-rolled transport with Apache HttpClient 5.7 (JDK client workaround) #192
Description
Goal
Replace the hand-rolled RFC 6455 transport in LibsqlWebSocketClient (inner Transport class + static handshake) with Apache HttpClient 5.7's httpclient5-websocket module, keeping the existing LibsqlClient seam and the request_id-based request/response matching in the outer class. Long term we return to the JDK-native java.net.http.WebSocket once the upstream 101 response is fixed.
Why now
The JDK client cannot be used today: sqld answers the upgrade with a 101 that carries content-length: 31 and no body. hyper-tungstenite 0.11.1 builds the 101 with .body(Body::from("switching to websocket protocol")) (31 bytes); hyper emits that as Content-Length, and JDK's OpeningHandshake.send() waits for those 31 bytes via BodyHandlers.ofString(), so the connect future never completes. Reproduced on JDK 25; observed on the latest sqld image, and the pinned v0.24.33 uses the same hyper-tungstenite 0.11.1.
HttpComponents treats 101 as an informational response and upgrades without reading a body, so it is unaffected. Probe against a running sqld (ws://…, hrana3) succeeded: hello → hello_ok.
Links: sqld handshake · hyper-tungstenite 0.11.1 · JDK OpeningHandshake · Apache module docs
Scope (decided)
- Add
org.apache.httpcomponents.client5:httpclient5-websocket:5.7-beta1tolibsql-clientonly (pin the exact version). - Replace the
Transportinner class and the statichandshake(...); keepLibsqlClient,dispatch(byte[]), and the pending-futures/stream logic. - Provide a blocking adapter over the callback API (the JDBC connection calls
LibsqlClientsynchronously). CopyCharBuffer/ByteBufferpayloads inonText/onBinary(they are only valid for the duration of the callback) before decoding or completing futures.
Acceptance criteria
WebSocketTransportIT(JSON,hrana3) andWebSocketProtobufTransportIT(Protobuf,hrana3-protobuf) pass against the pinnedv0.24.33image.ws://andwss://both connect; the TLS/ALPN path is exercised, not justws://.- HTTP transport unchanged:
LibsqlHttpClientITand the JDBC suite stay green. - Hand-written framing (
sendFrame,readLoop, the RFC 6455 encode/decode, statichandshake) is removed once the replacement passes ITs — no dormant alternate path.
Open questions
- Fallback: delete the hand-rolled transport outright, or keep it behind a flag until 5.7 GA?
- Beta pin: ship
5.7-beta1, or hold the PR until a GA release? permessage-deflate: the module enables it by default; the hand-rolled client does no compression, and sqld did not negotiate it in the probe. Disable it initially to match current behavior, then revisit?- Protobuf: only JSON over
ws://has been verified against HttpComponents so far; binary frames are assumed, not proven.
Risks / notes
- 5.7 is beta; isolate the adapter so the API can be swapped if it changes before GA.
- Dependency footprint grows:
httpclient5-websocket+httpclient5+httpcore5+httpcore5-h2+httpclient5-cache+slf4j-api. - sqld does not echo the WS close frame, so clients observe
1006 abnormal closure; do not assert1000. - Confirm HttpComponents delivers whole messages (its
lastflag is currently alwaystrue); if sqld ever fragments, reassembly must be verified.
Out of scope
- Upstream sqld / hyper-tungstenite fix; any Hrana protocol or JDBC API change.
Activity
refactor(client): speak WebSocket through HttpClient instead of a han…