Mint.WebSocket (MintWebSocket v0.1.0) View Source
(Unofficial) WebSocket support for the Mint functional HTTP client
Like Mint, Mint.WebSocket
provides a functional, process-less interface
for operating a WebSocket connection. Mint.WebSocket
is an extension
to Mint: the sending and receiving of messages is done with Mint functions.
Prospective Mint.WebSocket users should first familiarize themselves with
Mint.HTTP
.
Mint.WebSocket is not fully spec-conformant on its own. Runtime behaviors such as responding to pings with pongs must be implemented by the user of Mint.WebSocket.
Usage
A connection formed with Mint.HTTP.connect/4
can be upgraded to a WebSocket
connection with upgrade/4
.
{:ok, conn} = Mint.HTTP.connect(:http, "localhost", 9_000)
{:ok, conn, ref} = Mint.WebSocket.upgrade(conn, "/", [])
upgrade/4
sends an upgrade request to the remote server. The WebSocket
connection is then built by awaiting the HTTP response from the server.
http_reply_message = receive(do: (message -> message))
{:ok, conn, [{:status, ^ref, status}, {:headers, ^ref, resp_headers}, {:done, ^ref}]} =
Mint.HTTP.stream(conn, http_reply_message)
{:ok, conn, websocket} =
Mint.WebSocket.new(conn, ref, status, resp_headers)
Now that the WebSocket connection has been formed, we use the websocket
data structure to encode and decode frames, and the
Mint.HTTP.stream_request_body/3
and Mint.HTTP.stream/2
functions from
Mint to perform sending and receiving of encoded frames.
For example, we'll send a "hello world" text frame across our connection.
{:ok, websocket, data} = Mint.WebSocket.encode(websocket, {:text, "hello world"})
{:ok, conn} = Mint.HTTP.stream_request_body(conn, ref, data)
And let's say that the server is echoing our messages; let's receive our echoed "hello world" text frame.
echo_message = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.HTTP.stream(conn, echo_message)
{:ok, websocket, [{:text, "hello world"}]} = Mint.WebSocket.decode(websocket, data)
HTTP/2 Support
Mint.WebSocket supports WebSockets over HTTP/2 as defined in rfc8441. rfc8441 is an extension to the HTTP/2 specification. At the time of writing, very few HTTP/2 server libraries support or enable HTTP/2 WebSockets by default.
upgrade/4
works on both HTTP/1 and HTTP/2 connections. In order to select
HTTP/2, the :http2
protocol should be explicitly selected in
Mint.HTTP.connect/4
.
{:ok, %Mint.HTTP2{} = conn} =
Mint.HTTP.connect(:http, "websocket.example", 80, protocols: [:http2])
{:ok, conn, ref} = Mint.WebSocket.upgrade(conn, "/", [])
If the server does not support the extended CONNECT method needed to bootstrap
WebSocket connections over HTTP/2, upgrade/4
will return an error tuple
with the :extended_connect_disabled
error reason.
{:error, conn, %Mint.WebSocketError{reason: :extended_connect_disabled}}
Why use HTTP/2 for WebSocket connections in the first place? HTTP/2 can multiplex many requests over the same connection, which can reduce the latency incurred by forming new connections for each request. A WebSocket connection only occupies one stream of a HTTP/2 connection, so even if an HTTP/2 connection has an open WebSocket communication, it can be used to transport more requests.
WebSocket Secure
Encryption of connections is handled by Mint functions. To start a WSS
connection, select :https
as the scheme in Mint.HTTP.connect/4
:
{:ok, conn} = Mint.HTTP.connect(:https, "websocket.example", 443)
And use upgrade/4
to upgrade the connection to WebSocket. See the
Mint documentation on SSL for more information.
Extensions
The WebSocket protocol allows for extensions. Extensions act as a middleware for encoding and decoding frames. For example "permessage-deflate" compresses and decompresses the body of data frames, which minifies the amount of bytes which must be sent over the network.
See Mint.WebSocket.Extension
for more information about extensions and
Mint.WebSocket.PerMessageDeflate
for information about the
"permessage-deflate" extension.
Link to this section Summary
Types
A WebSocket frame
Shorthand notations for control frames
An immutable data structure representing a WebSocket connection
Functions
Decodes a binary into a list of frames
Encodes a frame into a binary
Creates a new WebSocket data structure given the server's reply to the upgrade request
Requests that a connection be upgraded to the WebSocket protocol
Link to this section Types
Specs
error() :: Mint.Types.error() | Mint.WebSocketError.t()
Specs
frame() :: {:text, String.t()} | {:binary, binary()} | {:ping, binary()} | {:pong, binary()} | {:close, code :: non_neg_integer(), reason :: binary()}
A WebSocket frame
{:binary, binary}
- a frame containing binary data. Binary frames can be used to send arbitrary binary data such as a PDF.{:text, text}
- a frame containing string data. Text frames must be valid utf8. Elixir has wonderful support for utf8:String.valid?/1
can detect valid and invalid utf8.{:ping, binary}
- a control frame which the server should respond to with a pong. The binary data must be echoed in the pong response.{:pong, binary}
- a control frame which forms a reply to a ping frame. Pings and pongs may be used to check the a connection is alive or to estimate latency.{:close, code, reason}
- a control frame used to request that a connection be closed or to acknowledgee a close frame send by the server.
These may be passed to encode/2
or returned from decode/2
.
Close frames
In order to close a WebSocket connection gracefully, either the client or
server sends a close frame. Then the other endpoint responds with a
close with code 1_000
and then closes the TCP connection. This can be
accomplished in Mint.WebSocket like so:
{:ok, websocket, data} = Mint.WebSocket.encode(websocket, :close)
{:ok, conn} = Mint.HTTP.stream_request_body(conn, ref, data)
close_response = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.HTTP.stream(conn, close_response)
{:ok, websocket, [{:close, 1_000, ""}]} = Mint.WebSocket.decode(websocket, data)
Mint.HTTP.close(conn)
rfc6455
section 7.4.1
documents codes which may be used in the code
element.
Specs
shorthand_frame() :: :ping | :pong | :close
Shorthand notations for control frames
:ping
- shorthand for{:ping, ""}
:pong
- shorthand for{:pong, ""}
:close
- shorthand for{:close, 1_000, ""}
These may be passed to encode/2
.
Specs
t()
An immutable data structure representing a WebSocket connection
Link to this section Functions
Specs
Decodes a binary into a list of frames
The binary may received from the connection with Mint.HTTP.stream/2
.
This function will invoke the Mint.WebSocket.Extension.decode/2
callback
for any accepted extensions.
Examples
message = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.HTTP.stream(conn, message)
{:ok, websocket, frames} = Mint.WebSocket.decode(websocket, data)
Specs
Encodes a frame into a binary
The resulting binary may be sent with Mint.HTTP.stream_request_body/3
.
This function will invoke the Mint.WebSocket.Extension.encode/2
callback
for any accepted extensions.
Examples
{:ok, websocket, data} = Mint.WebSocket.encode(websocket, {:text, "hello world"})
{:ok, conn} = Mint.HTTP.stream_request_body(conn, ref, data)
Specs
new(Mint.HTTP.t(), reference(), pos_integer(), Mint.Types.headers()) :: {:ok, Mint.HTTP.t(), t(), [Mint.Types.response()]} | {:error, Mint.HTTP.t(), error()}
Creates a new WebSocket data structure given the server's reply to the upgrade request
This function will setup any extensions accepted by the server using
the Mint.WebSocket.Extension.init/2
callback.
Examples
http_reply = receive(do: (message -> message))
{:ok, conn, [{:status, ^ref, status}, {:headers, ^ref, headers}, {:done, ^ref}]} =
Mint.HTTP.stream(conn, http_reply)
{:ok, conn, websocket} =
Mint.WebSocket.new(conn, ref, status, resp_headers)
Specs
upgrade( conn :: Mint.HTTP.t(), path :: String.t(), headers :: Mint.Types.headers(), opts :: Keyword.t() ) :: {:ok, Mint.HTTP.t(), Mint.Types.request_ref()} | {:error, Mint.HTTP.t(), error()}
Requests that a connection be upgraded to the WebSocket protocol
This function wraps Mint.HTTP.request/5
to provide a single interface
for bootstrapping an upgrade for HTTP/1 and HTTP/2 connections.
For HTTP/1 connections, this function performs a GET request with WebSocket-specific headers. For HTTP/2 connections, this function performs an extended CONNECT request which opens a stream to be used for the WebSocket connection.
Options
:extensions
- a list of extensions to negotiate. See the extensions section below.
Extensions
Extensions should be declared by passing the :extensions
option in the
opts
keyword list. Note that in the WebSocket protocol, extensions are
negotiated: the client proposes a list of extensions and the server may
accept any (or none) of them. See Mint.WebSocket.Extension
for more
information about extension negotiation.
Extensions may be passed as a list of Mint.WebSocket.Extension
structs
or with the following shorthand notations:
module
- shorthand for{module, []}
{module, params}
- shorthand for{module, params, []}
{module, params, opts}
- a shorthand which is expanded to aMint.WebSocket.Extension
struct
Examples
{:ok, conn} = Mint.HTTP.connect(:http, "localhost", 9_000)
{:ok, conn, ref} =
Mint.WebSocket.upgrade(conn, "/", [], extensions: [Mint.WebSocket.PerMessageDeflate])
# or provide params:
{:ok, conn, ref} =
Mint.WebSocket.upgrade(
conn,
"/",
[],
extensions: [{Mint.WebSocket.PerMessageDeflate, [:client_max_window_bits]]}]
)