Skip to content

WebSocket permessage-deflate compression (RFC 7692) (#853)#1308

Merged
quinnj merged 1 commit into
masterfrom
jq-ws-pmce
Jun 15, 2026
Merged

WebSocket permessage-deflate compression (RFC 7692) (#853)#1308
quinnj merged 1 commit into
masterfrom
jq-ws-pmce

Conversation

@quinnj

@quinnj quinnj commented Jun 15, 2026

Copy link
Copy Markdown
Member

Closes #853.

Adds opt-in WebSocket per-message compression via the permessage-deflate extension (RFC 7692), negotiated during the handshake. Modeled on Go's gorilla/coder permessage-deflate semantics.

server = HTTP.WebSockets.listen!("127.0.0.1", 0; listenany=true, compress=true) do ws
    for msg in ws; HTTP.WebSockets.send(ws, msg); end
end
HTTP.WebSockets.open("ws://" * HTTP.WebSockets.server_addr(server); compress=true) do ws
    HTTP.WebSockets.send(ws, repeat("compress me ", 1000))  # sent compressed
    HTTP.WebSockets.receive(ws)
end

compress=true is accepted on the client open(...), the server listen!(...), and upgrade(...). It is opt-in on both ends, negotiated per connection, and transparently falls back to uncompressed frames if either side declines.

Implementation

New src/http_websocket_pmce.jl:

  • Raw DEFLATE via thin Zlib_jll ccall bindings (negative windowBits), driving Z_SYNC_FLUSH with the 00 00 ff ff trailer strip/append trick (RFC 7692 §7.2). CodecZlib's transcode API can express neither sync-flush nor context takeover, so we go to zlib directly. Adds Zlib_jll as a dependency (already transitively present via CodecZlib).
  • Per-connection context takeover or no_context_takeover, negotiated window bits, the empty-message 0x00 rule (§7.2.3.6), and a decompression-bomb guard bounded by maxframesize.
  • Full Sec-WebSocket-Extensions negotiation: client offer, server accept (declining unknown params and unsupported 8-bit windows), and client validation of the server's response (failing the connection on a malformed or unoffered extension).

Codec integration (post-#1304 codec): RSV1 is accepted only when negotiated and only on the first frame of a data message; the decoder defers UTF-8 validation for compressed text (validated after the whole message is inflated, since compressed bytes aren't valid UTF-8). Outgoing data messages compress to a single frame; control frames are never compressed.

Validation

  • Autobahn with compression enabled: 271 cases, zero failures — all 24 permessage-deflate cases (12./13.) pass against the reference fuzzing client, and core protocol cases 1-11 (247) are unaffected.
  • 45 new CI-runnable tests (http_websocket_pmce_tests.jl): compress/decompress round-trips (single, context-takeover, no-takeover, empty, unicode, binary), the bomb guard, full negotiation (offer/accept/decline/window-bits/unknown-param), and end-to-end echo with both fallback paths — no docker required.
  • Existing WS suites unchanged: codec 122, client 20, server 35, integration 42.

🤖 Generated with Claude Code

Adds opt-in per-message DEFLATE compression for WebSocket connections,
negotiated during the handshake. Enable with `compress=true` on the client
`open(...)`, the server `listen!(...)`, or `upgrade(...)`; it is negotiated
per connection and transparently falls back to uncompressed frames if either
side declines.

Implementation (src/http_websocket_pmce.jl):
- Raw DEFLATE via thin Zlib_jll bindings (negative windowBits), driving
  Z_SYNC_FLUSH with the 0x00 0x00 0xff 0xff trailer strip/append trick
  (RFC 7692 §7.2). The CodecZlib transcode API cannot express sync-flush or
  context takeover, so we go to zlib directly. Adds Zlib_jll as a dep.
- Per-connection context-takeover or no_context_takeover, negotiated window
  bits, the empty-message 0x00 rule (§7.2.3.6), and a decompression-bomb
  guard bounded by `maxframesize`.
- Full Sec-WebSocket-Extensions negotiation: client offer, server accept
  (declining unknown params / unsupported 8-bit windows), client validation
  of the server response.

Codec integration: RSV1 is accepted only when negotiated and only on the
first frame of a data message; the decoder defers UTF-8 validation for
compressed text (validated after the whole message is inflated). Outgoing
data messages are compressed as one frame; control frames are never
compressed.

Tests: 45 codec/negotiation/end-to-end cases (no docker needed). Autobahn
with compression enabled: 271 cases, zero failures, all 24 permessage-deflate
cases (12.*/13.*) OK and core cases 1-11 unaffected.

Existing WS suites unchanged: codec 122, client 20, server 35, integration 42.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.86957% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.14%. Comparing base (427b525) to head (b4fc551).

Files with missing lines Patch % Lines
src/http_websocket_pmce.jl 92.30% 12 Missing ⚠️
src/http_websockets.jl 86.15% 9 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1308      +/-   ##
==========================================
- Coverage   87.16%   87.14%   -0.03%     
==========================================
  Files          28       29       +1     
  Lines       11042    11273     +231     
==========================================
+ Hits         9625     9824     +199     
- Misses       1417     1449      +32     

☔ 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.

@quinnj quinnj merged commit eeb77b2 into master Jun 15, 2026
7 of 8 checks passed
@quinnj quinnj deleted the jq-ws-pmce branch June 15, 2026 19:38
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.

WebSocket permessage-deflate

1 participant