Skip to content

WebSocket: replace hand-rolled transport with Apache HttpClient 5.7 (JDK client workaround) #192

Closed

Description

@magnusp

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-beta1 to libsql-client only (pin the exact version).
  • Replace the Transport inner class and the static handshake(...); keep LibsqlClient, dispatch(byte[]), and the pending-futures/stream logic.
  • Provide a blocking adapter over the callback API (the JDBC connection calls LibsqlClient synchronously). Copy CharBuffer/ByteBuffer payloads in onText/onBinary (they are only valid for the duration of the callback) before decoding or completing futures.

Acceptance criteria

  • WebSocketTransportIT (JSON, hrana3) and WebSocketProtobufTransportIT (Protobuf, hrana3-protobuf) pass against the pinned v0.24.33 image.
  • ws:// and wss:// both connect; the TLS/ALPN path is exercised, not just ws://.
  • HTTP transport unchanged: LibsqlHttpClientIT and the JDBC suite stay green.
  • Hand-written framing (sendFrame, readLoop, the RFC 6455 encode/decode, static handshake) 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 assert 1000.
  • Confirm HttpComponents delivers whole messages (its last flag is currently always true); if sqld ever fragments, reassembly must be verified.

Out of scope

  • Upstream sqld / hyper-tungstenite fix; any Hrana protocol or JDBC API change.

Activity

  1. added a commit that references this issue on Oct 24, 2026
    404bba6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    dependenciesPull requests that update a dependency fileenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions