{"id":50381932,"url":"https://github.com/benoitc/erlang_ws","last_synced_at":"2026-05-30T12:30:37.433Z","repository":{"id":352855132,"uuid":"1215072158","full_name":"benoitc/erlang_ws","owner":"benoitc","description":"WebSocket protocol library for Erlang (RFC 6455 / RFC 8441 / RFC 9220 / RFC 7692)","archived":false,"fork":false,"pushed_at":"2026-05-29T07:55:52.000Z","size":136,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T09:26:20.777Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benoitc.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-19T12:50:37.000Z","updated_at":"2026-05-29T07:55:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/benoitc/erlang_ws","commit_stats":null,"previous_names":["benoitc/erlang_ws"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/benoitc/erlang_ws","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang_ws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang_ws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang_ws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang_ws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benoitc","download_url":"https://codeload.github.com/benoitc/erlang_ws/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benoitc%2Ferlang_ws/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33692997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-30T12:30:36.839Z","updated_at":"2026-05-30T12:30:37.422Z","avatar_url":"https://github.com/benoitc.png","language":"Erlang","funding_links":[],"categories":[],"sub_categories":[],"readme":"# erlang_ws\n\n[![CI](https://github.com/benoitc/erlang_ws/actions/workflows/ci.yml/badge.svg)](https://github.com/benoitc/erlang_ws/actions/workflows/ci.yml)\n[![Hex.pm](https://img.shields.io/hexpm/v/erlang_ws.svg)](https://hex.pm/packages/erlang_ws)\n[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)\n\nWebSocket protocol library for Erlang. Pure-Erlang, no runtime\ndependencies.\n\n- **RFC 6455** — WebSocket over HTTP/1.1 (client + server).\n- **RFC 8441** — Bootstrapping WebSockets with HTTP/2 (extended\n  CONNECT) — pseudo-header validation helpers; integration lives in\n  the embedder (`erlang_h2`).\n- **RFC 9220** — Bootstrapping WebSockets with HTTP/3 — same helpers,\n  exposed under `ws_h3_upgrade`.\n- **RFC 7692** — permessage-deflate negotiation + codec.\n\n`erlang_ws` is a protocol library, **not a server**. Embedders\n(`erlang_h1`, `erlang_h2`, `erlang_quic/h3`, Livery, Cowboy, ...) own\nthe HTTP layer and the stream handle, call the upgrade validators,\nand hand the stream over to a session via a transport callback.\n\nThe hex.pm package is `erlang_ws`; the OTP application and module\natoms are `ws`. Call sites write `ws:accept/5`, `ws:connect/2`, etc.\n\n## Quickstart\n\nThree runnable examples live in\n[`examples/`](https://github.com/benoitc/erlang_ws/tree/main/examples):\n\n- `echo_server.erl` — echoes every text / binary frame.\n- `echo_client.erl` — synchronous send-and-wait client.\n- `chat_server.erl` — broadcast chat server (`pg`-backed).\n\nEach is ~50 lines and directly exercised by `ws_examples_SUITE`. The\nexamples use the bundled reference HTTP/1.1 listener,\n`ws_h1_tcp_server` — a small `gen_tcp` acceptor loop that validates\nthe upgrade request and hands the stream to `ws:accept/5`. Embedders\nwith a full HTTP stack replace it with their own upgrade path.\n\n### Echo server (excerpt from `examples/echo_server.erl`)\n\n```erlang\n-module(echo_server).\n-behaviour(ws_handler).\n-export([run/0, init/2, handle_in/2, handle_info/2, terminate/2]).\n\nrun() -\u003e\n    {ok, _} = application:ensure_all_started(ws),\n    {ok, _} = ws_h1_tcp_server:start_link(\n        #{port =\u003e 8080, handler =\u003e ?MODULE, handler_opts =\u003e #{}}).\n\ninit(_Req, State)              -\u003e {ok, State}.\nhandle_in({text, D}, State)    -\u003e {reply, {text, D}, State};\nhandle_in({binary, D}, State)  -\u003e {reply, {binary, D}, State};\nhandle_in(_, State)            -\u003e {ok, State}.\nhandle_info(_, State)          -\u003e {ok, State}.\nterminate(_, _)                -\u003e ok.\n```\n\nRun it:\n\n```shell\nrebar3 as test compile\nerl -pa _build/test/lib/ws/ebin _build/test/lib/ws/examples \\\n    -s echo_server run -noshell\n```\n\n### Client (excerpt from `examples/echo_client.erl`)\n\n```erlang\nsend(Url, Msg) -\u003e\n    {ok, _} = application:ensure_all_started(ws),\n    {ok, Conn} = ws:connect(Url,\n        #{handler      =\u003e ?MODULE,\n          handler_opts =\u003e #{notify =\u003e self()}}),\n    ok = ws:send(Conn, {text, Msg}),\n    receive {echo, Bin} -\u003e ws:close(Conn, 1000, \u003c\u003c\u003e\u003e), {ok, Bin}\n    after 5000         -\u003e {error, timeout}\n    end.\n```\n\nUsage:\n\n```erlang\n1\u003e echo_client:send(\u003c\u003c\"ws://127.0.0.1:8080/\"\u003e\u003e, \u003c\u003c\"hello\"\u003e\u003e).\n{ok, \u003c\u003c\"hello\"\u003e\u003e}\n```\n\n## Module map\n\n| Module | Role |\n|-|-|\n| `ws` | Public API: `accept/5`, `connect/2`, `send/2`, `close/3`. |\n| `ws_frame` | RFC 6455 encode / decode, masking, UTF-8 validation. |\n| `ws_close` | Close-code classification and validation. |\n| `ws_h1_upgrade` | HTTP/1.1 Upgrade request parsing, 101 response build, client key gen. |\n| `ws_h2_upgrade` | RFC 8441 extended CONNECT helpers (server + client). |\n| `ws_h3_upgrade` | RFC 9220 extended CONNECT helpers (delegate to H2 shape). |\n| `ws_handler` | Behaviour for application code. |\n| `ws_transport` | Behaviour for stream I/O. |\n| `ws_transport_gen_tcp` / `ws_transport_ssl` | Reference transports. |\n| `ws_session` | `gen_statem` driving a single WebSocket connection. |\n| `ws_client` | Client connect path (`ws://`, `wss://`). |\n| `ws_h1_tcp_server` | Reference HTTP/1.1 acceptor + upgrade driver. |\n| `ws_deflate` | RFC 7692 permessage-deflate. |\n\n## Tests\n\n- **Unit (EUnit)** — `rebar3 eunit` — 142 tests covering frame codec,\n  close-code validation, handshake helpers (H1/H2/H3),\n  permessage-deflate negotiation + codec.\n- **Property (PropEr)** — `rebar3 proper -m ws_prop_tests` — 4\n  properties: mask involution, client↔server encode/decode\n  round-trip in both directions, chunked delivery equivalence.\n- **End-to-end (Common Test)** — `rebar3 ct` — 34 cases total:\n  - `ws_session_SUITE` (10) — server-side echo, fragmentation,\n    close / ping / bad-UTF-8 / oversize.\n  - `ws_client_SUITE` (3) — client↔server round-trip.\n  - `ws_examples_SUITE` (12) — boots `examples/` modules and drives\n    them: echo round-trip via `echo_client:send/2`, chat broadcast\n    with multiple clients, 20 concurrent echo clients, fragmented\n    text, ping/pong sequence, subprotocol negotiation, subprotocol\n    rejection, TLS `wss://` round-trip, 512 KiB binary echo, close\n    on invalid UTF-8.\n  - `ws_docs_snippets_SUITE` (9) — mechanically exercises every code\n    example in the README and `docs/guide.md` so documentation\n    cannot silently rot.\n- **Compliance (Autobahn)** —\n  `WS_RUN_AUTOBAHN=1 rebar3 ct --suite=test/ws_compliance_SUITE` —\n  runs the [Autobahn testsuite] via docker against an in-process echo\n  server. 300 cases across sections 1–9 (framing, pings, reserved\n  bits, opcodes, fragmentation, UTF-8, close handling, limits) all\n  green.\n\n## Documentation\n\n- [docs/guide.md](docs/guide.md) — tutorial, every snippet tested.\n- [docs/embedding.md](docs/embedding.md) — how to plug into\n  `erlang_h1` / `erlang_h2` / `erlang_quic/h3` / Cowboy / Livery /\n  your own HTTP layer.\n- [docs/errors.md](docs/errors.md) — error taxonomy, close codes,\n  handshake failure modes.\n- [docs/features.md](docs/features.md) — RFC coverage, hardening\n  list, out-of-scope list.\n\nFull API reference is on hexdocs; regenerate locally with\n`rebar3 ex_doc`.\n\n## Embedder integration notes\n\n- **HTTP/2.** The embedder's HTTP/2 stack must advertise\n  `SETTINGS_ENABLE_CONNECT_PROTOCOL = 1` before RFC 8441 extended\n  CONNECT is accepted. `erlang_h2` exposes this as the\n  `enable_connect_protocol =\u003e true` server option.\n- **HTTP/3.** `erlang_quic/h3` already enforces RFC 9220 validation.\n  The `ws_h3_upgrade` helpers are there so embedders validate via the\n  same surface and surface the same typed errors.\n- **Stream handover.** Embedders must supply a `ws_transport`\n  implementation whose `classify/2` maps their stream messages to the\n  canonical `{ws_data | ws_closed | ws_error, Handle, ...}` shape.\n\n## License\n\nApache-2.0. See `LICENSE` for the full text once published.\n\n[Autobahn testsuite]: https://github.com/crossbario/autobahn-testsuite\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenoitc%2Ferlang_ws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenoitc%2Ferlang_ws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenoitc%2Ferlang_ws/lists"}