# erlang_ws
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/`](https://github.com/benoitc/erlang_ws/tree/main/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`)
```erlang
-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:
```shell
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`)
```erlang
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:
```erlang
1> echo_client:send(<<"ws://127.0.0.1:8080/">>, <<"hello">>).
{ok, <<"hello">>}
```
## Module map
| Module | Role |
|-|-|
| `ws` | Public API: `accept/5`, `connect/2`, `send/2`, `close/3`. |
| `ws_frame` | RFC 6455 encode / decode, masking, UTF-8 validation. |
| `ws_close` | Close-code classification and validation. |
| `ws_h1_upgrade` | HTTP/1.1 Upgrade request parsing, 101 response build, client key gen. |
| `ws_h2_upgrade` | RFC 8441 extended CONNECT helpers (server + client). |
| `ws_h3_upgrade` | RFC 9220 extended CONNECT helpers (delegate to H2 shape). |
| `ws_handler` | Behaviour for application code. |
| `ws_transport` | Behaviour for stream I/O. |
| `ws_transport_gen_tcp` / `ws_transport_ssl` | Reference transports. |
| `ws_session` | `gen_statem` driving a single WebSocket connection. |
| `ws_client` | Client connect path (`ws://`, `wss://`). |
| `ws_h1_tcp_server` | Reference HTTP/1.1 acceptor + upgrade driver. |
| `ws_deflate` | RFC 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](docs/guide.md) — tutorial, every snippet tested.
- [docs/embedding.md](docs/embedding.md) — how to plug into
`erlang_h1` / `erlang_h2` / `erlang_quic/h3` / Cowboy / Livery /
your own HTTP layer.
- [docs/errors.md](docs/errors.md) — error taxonomy, close codes,
handshake failure modes.
- [docs/features.md](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.
[Autobahn testsuite]: https://github.com/crossbario/autobahn-testsuite