erlang_ws

View Source

WebSocket protocol library for Erlang. Pure-Erlang, no runtime dependencies.

  • RFC 6455 — WebSocket over HTTP/1.1 (client + server).
  • RFC 8441 — Bootstrapping WebSockets with HTTP/2 (extended CONNECT) — pseudo-header validation helpers; integration lives in the embedder (erlang_h2).
  • RFC 9220 — Bootstrapping WebSockets with HTTP/3 — same helpers, exposed under ws_h3_upgrade.
  • RFC 7692 — permessage-deflate negotiation + codec.

erlang_ws is a protocol library, not a server. Embedders (erlang_h1, erlang_h2, erlang_quic/h3, Livery, Cowboy, ...) own the HTTP layer and the stream handle, call the upgrade validators, and hand the stream over to a session via a transport callback.

The hex.pm package is erlang_ws; the OTP application and module atoms are ws. Call sites write ws:accept/5, ws:connect/2, etc.

Quickstart

Three runnable examples live in examples/:

  • echo_server.erl — echoes every text / binary frame.
  • echo_client.erl — synchronous send-and-wait client.
  • chat_server.erl — broadcast chat server (pg-backed).

Each is ~50 lines and directly exercised by ws_examples_SUITE. The examples use the bundled reference HTTP/1.1 listener, ws_h1_tcp_server — a small gen_tcp acceptor loop that validates the upgrade request and hands the stream to ws:accept/5. Embedders with a full HTTP stack replace it with their own upgrade path.

Echo server (excerpt from examples/echo_server.erl)

-module(echo_server).
-behaviour(ws_handler).
-export([run/0, init/2, handle_in/2, handle_info/2, terminate/2]).

run() ->
    {ok, _} = application:ensure_all_started(ws),
    {ok, _} = ws_h1_tcp_server:start_link(
        #{port => 8080, handler => ?MODULE, handler_opts => #{}}).

init(_Req, State)              -> {ok, State}.
handle_in({text, D}, State)    -> {reply, {text, D}, State};
handle_in({binary, D}, State)  -> {reply, {binary, D}, State};
handle_in(_, State)            -> {ok, State}.
handle_info(_, State)          -> {ok, State}.
terminate(_, _)                -> ok.

Run it:

rebar3 as test compile
erl -pa _build/test/lib/ws/ebin _build/test/lib/ws/examples \
    -s echo_server run -noshell

Client (excerpt from examples/echo_client.erl)

send(Url, Msg) ->
    {ok, _} = application:ensure_all_started(ws),
    {ok, Conn} = ws:connect(Url,
        #{handler      => ?MODULE,
          handler_opts => #{notify => self()}}),
    ok = ws:send(Conn, {text, Msg}),
    receive {echo, Bin} -> ws:close(Conn, 1000, <<>>), {ok, Bin}
    after 5000         -> {error, timeout}
    end.

Usage:

1> echo_client:send(<<"ws://127.0.0.1:8080/">>, <<"hello">>).
{ok, <<"hello">>}

Module map

ModuleRole
wsPublic API: accept/5, connect/2, send/2, close/3.
ws_frameRFC 6455 encode / decode, masking, UTF-8 validation.
ws_closeClose-code classification and validation.
ws_h1_upgradeHTTP/1.1 Upgrade request parsing, 101 response build, client key gen.
ws_h2_upgradeRFC 8441 extended CONNECT helpers (server + client).
ws_h3_upgradeRFC 9220 extended CONNECT helpers (delegate to H2 shape).
ws_handlerBehaviour for application code.
ws_transportBehaviour for stream I/O.
ws_transport_gen_tcp / ws_transport_sslReference transports.
ws_sessiongen_statem driving a single WebSocket connection.
ws_clientClient connect path (ws://, wss://).
ws_h1_tcp_serverReference HTTP/1.1 acceptor + upgrade driver.
ws_deflateRFC 7692 permessage-deflate.

Tests

  • Unit (EUnit)rebar3 eunit — 142 tests covering frame codec, close-code validation, handshake helpers (H1/H2/H3), permessage-deflate negotiation + codec.
  • Property (PropEr)rebar3 proper -m ws_prop_tests — 4 properties: mask involution, client↔server encode/decode round-trip in both directions, chunked delivery equivalence.
  • End-to-end (Common Test)rebar3 ct — 34 cases total:
    • ws_session_SUITE (10) — server-side echo, fragmentation, close / ping / bad-UTF-8 / oversize.
    • ws_client_SUITE (3) — client↔server round-trip.
    • ws_examples_SUITE (12) — boots examples/ modules and drives them: echo round-trip via echo_client:send/2, chat broadcast with multiple clients, 20 concurrent echo clients, fragmented text, ping/pong sequence, subprotocol negotiation, subprotocol rejection, TLS wss:// round-trip, 512 KiB binary echo, close on invalid UTF-8.
    • ws_docs_snippets_SUITE (9) — mechanically exercises every code example in the README and docs/guide.md so documentation cannot silently rot.
  • Compliance (Autobahn)WS_RUN_AUTOBAHN=1 rebar3 ct --suite=test/ws_compliance_SUITE — runs the Autobahn testsuite via docker against an in-process echo server. 300 cases across sections 1–9 (framing, pings, reserved bits, opcodes, fragmentation, UTF-8, close handling, limits) all green.

Documentation

  • docs/guide.md — tutorial, every snippet tested.
  • docs/embedding.md — how to plug into erlang_h1 / erlang_h2 / erlang_quic/h3 / Cowboy / Livery / your own HTTP layer.
  • docs/errors.md — error taxonomy, close codes, handshake failure modes.
  • docs/features.md — RFC coverage, hardening list, out-of-scope list.

Full API reference is on hexdocs; regenerate locally with rebar3 ex_doc.

Embedder integration notes

  • HTTP/2. The embedder's HTTP/2 stack must advertise SETTINGS_ENABLE_CONNECT_PROTOCOL = 1 before RFC 8441 extended CONNECT is accepted. erlang_h2 exposes this as the enable_connect_protocol => true server option.
  • HTTP/3. erlang_quic/h3 already enforces RFC 9220 validation. The ws_h3_upgrade helpers are there so embedders validate via the same surface and surface the same typed errors.
  • Stream handover. Embedders must supply a ws_transport implementation whose classify/2 maps their stream messages to the canonical {ws_data | ws_closed | ws_error, Handle, ...} shape.

License

Apache-2.0. See LICENSE for the full text once published.