Mint.WebSocket
(Unofficial) HTTP/1 and HTTP/2 WebSocket support for Mint 🌱
What is Mint?
Mint is a functional HTTP/1 and HTTP/2 client library written in Elixir.
Why does it matter that it's functional? Isn't Elixir functional?
Existing WebSocket implementations like
:gun,
:websocket_client,
or WebSockex work by spawning and
passing messages among processes. This is a very convenient interface in
Elixir and Erlang, but it does not allow the author much control over
the WebSocket connection.
Instead Mint.WebSocket is process-less: the entire HTTP and WebSocket
states are kept in immutable data structures. This enables authors of
WebSocket clients a more fine-grained control over the connections:
Mint.WebSocket does not prescribe a process archicture.
For more information, check out Mint#Usage.
Spec conformance
This library aims to follow RFC6455 and RFC8441 as closely as possible and uses Autobahn|Testsuite to check conformance with every run of tests/CI. The auto-generated report produced by the Autobahn|Testsuite is uploaded on each push to main.
See the report here: https://mint-websocket.nyc3.digitaloceanspaces.com/autobahn/index.html
A Quick Note About HTTP/2
HTTP/2 WebSockets are not a built-in feature of HTTP/2. RFC8441 is an extension to the HTTP/2 protocol and server libraries are not obligated to implement it. In the current landscape, very few server libraries support the HTTP/2 extended CONNECT method which bootstraps WebSockets.
If Mint.WebSocket.upgrade/4 returns
{:error, conn, %Mint.WebSocketError{reason: :extended_connect_disabled}}Then the server does not support HTTP/2 WebSockets or does not have them enabled.
HTTP/2 support for extended CONNECT has been merged into Mint's main branch but is not yet published. If you need HTTP/2 support, use an override:
# mix.exs
def deps do
[
{:mint_web_socket, "~> 0.1"},
{:mint,
git: "https://github.com/elixir-mint/mint.git",
ref: "488a6ba5fd418a52f697a8d5f377c629ea96af92",
override: true},
# ..
]
endUsage
Mint.WebSocket piggybacks much of the existing Mint.HTTP API. For example,
this snippet shows sending and receiving a text frame of "hello world" to a
WebSocket server which echos our frames:
# bootstrap
{:ok, conn} = Mint.HTTP.connect(:http, "echo", 9000)
{:ok, conn, ref} = Mint.WebSocket.upgrade(conn, "/", [])
http_get_message = receive(do: (message -> message))
{:ok, conn, [{:status, ^ref, status}, {:headers, ^ref, resp_headers}, {:done, ^ref}]} =
Mint.HTTP.stream(conn, http_get_message)
{:ok, conn, websocket} = Mint.WebSocket.new(conn, ref, status, resp_headers)
# send the hello world frame
{:ok, websocket, data} = Mint.WebSocket.encode(websocket, {:text, "hello world"})
{:ok, conn} = Mint.HTTP.stream_request_body(conn, ref, data)
# receive the hello world reply frame
hello_world_echo_message = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.HTTP.stream(conn, hello_world_echo_message)
{:ok, websocket, [{:text, "hello world"}]} = Mint.WebSocket.decode(websocket, data)Development workflow
Interested in developing Mint.WebSocket? The docker-compose.yml sets up
an Elixir container, a simple websocket echo server, and the Autobahn|Testsuite
fuzzing server.
(host)$ docker-compose up -d
(host)$ docker-compose exec app /bin/bash
(app)$ mix deps.get
(app)$ mix test
(app)$ iex -S mix