View Source Mint.WebSocket (MintWebSocket v1.0.4)

HTTP/1 and HTTP/2 WebSocket support for the Mint functional HTTP client

Like Mint, Mint.WebSocket provides a functional, process-less interface for operating a WebSocket connection. Prospective Mint.WebSocket users may wish to 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/5.

{:ok, conn} = Mint.HTTP.connect(:http, "localhost", 9_000)
{:ok, conn, ref} = Mint.WebSocket.upgrade(:ws, conn, "/", [])

upgrade/5 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.WebSocket.stream(conn, http_reply_message)

{:ok, conn, websocket} =
  Mint.WebSocket.new(conn, ref, status, resp_headers)

Once the WebSocket connection has been established, use the websocket data structure to encode and decode frames with encode/2 and decode/2, and send and stream messages with stream_request_body/3 and stream/2.

For example, one may send a "hello world" text frame across a connection like so:

{:ok, websocket, data} = Mint.WebSocket.encode(websocket, {:text, "hello world"})
{:ok, conn} = Mint.WebSocket.stream_request_body(conn, ref, data)

Say that the remote is echoing messages. Use stream/2 and decode/2 to decode a received WebSocket frame:

echo_message = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.WebSocket.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/5 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, conn} =
  Mint.HTTP.connect(:http, "websocket.example", 80, protocols: [:http2])
:http2 = Mint.HTTP.protocol(conn)
{:ok, conn, ref} = Mint.WebSocket.upgrade(:ws, 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 pass the :wss scheme to upgrade/5. 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.

Summary

Types

A WebSocket frame

Shorthand notations for control frames

t()

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

Receives data from the socket

A wrapper around Mint.HTTP.stream/2 for streaming HTTP and WebSocket messages

Streams chunks of data on the connection

Requests that a connection be upgraded to the WebSocket protocol

Types

@type frame() ::
  {:text, String.t()}
  | {:binary, binary()}
  | {:ping, binary()}
  | {:pong, binary()}
  | {:close, code :: non_neg_integer() | nil, reason :: binary() | nil}

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.WebSocket.stream_request_body(conn, ref, data)

close_response = receive(do: (message -> message))
{:ok, conn, [{:data, ^ref, data}]} = Mint.WebSocket.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.

@type shorthand_frame() :: :ping | :pong | :close

Shorthand notations for control frames

  • :ping - shorthand for {:ping, ""}
  • :pong - shorthand for {:pong, ""}
  • :close - shorthand for {:close, nil, nil}

These may be passed to encode/2. Frames decoded with decode/2 are always in frame/0 format.

@opaque t()

An immutable data structure representing a WebSocket connection

Functions

@spec decode(t(), data :: binary()) ::
  {:ok, t(), [frame() | {:error, term()}]} | {:error, t(), any()}

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)
Link to this function

encode(websocket, frame)

View Source
@spec encode(t(), shorthand_frame() | frame()) ::
  {:ok, t(), binary()} | {:error, t(), any()}

Encodes a frame into a binary

The resulting binary may be sent with 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.WebSocket.stream_request_body(conn, websocket_ref, data)
Link to this function

new(conn, request_ref, status, response_headers, opts \\ [])

View Source

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.

Options

  • :mode - (default: :active) either :active or :passive. This corresponds to the same option in Mint.HTTP.connect/4.

Examples

http_reply = receive(do: (message -> message))
{:ok, conn, [{:status, ^ref, status}, {:headers, ^ref, headers}, {:done, ^ref}]} =
  Mint.WebSocket.stream(conn, http_reply)

{:ok, conn, websocket} =
  Mint.WebSocket.new(conn, ref, status, resp_headers)
Link to this function

recv(conn, byte_count, timeout)

View Source
@spec recv(Mint.HTTP.t(), non_neg_integer(), timeout()) ::
  {:ok, Mint.HTTP.t(), [Mint.Types.response()]}
  | {:error, t(), Mint.Types.error(), [Mint.Types.response()]}

Receives data from the socket

This function is used instead of stream/2 when the connection is in :passive mode. You must pass the mode: :passive option to new/5 in order to use recv/3.

This function wraps Mint.HTTP.recv/3. See the Mint.HTTP.recv/3 documentation for more information.

Examples

{:ok, conn, [{:data, ^ref, data}]} = Mint.WebSocket.recv(conn, 0, 5_000)
{:ok, websocket, [{:text, "hello world!"}]} =
  Mint.WebSocket.decode(websocket, data)
@spec stream(Mint.HTTP.t(), term()) ::
  {:ok, Mint.HTTP.t(), [Mint.Types.response()]}
  | {:error, Mint.HTTP.t(), Mint.Types.error(), [Mint.Types.response()]}
  | :unknown

A wrapper around Mint.HTTP.stream/2 for streaming HTTP and WebSocket messages

This function does not decode WebSocket frames. Instead, once a WebSocket connection has been established, decode any {:data, request_ref, data} frames with decode/2.

This function is a drop-in replacement for Mint.HTTP.stream/2 which enables streaming WebSocket data after the bootstrapping HTTP/1 connection has concluded. It decodes both WebSocket and regular HTTP messages.

Examples

message = receive(do: (message -> message))
{:ok, conn, [{:data, ^websocket_ref, data}]} =
  Mint.WebSocket.stream(conn, message)
{:ok, websocket, [{:text, "hello world!"}]} =
  Mint.WebSocket.decode(websocket, data)
Link to this function

stream_request_body(conn, request_ref, data)

View Source
@spec stream_request_body(
  Mint.HTTP.t(),
  Mint.Types.request_ref(),
  iodata() | :eof | {:eof, trailing_headers :: Mint.Types.headers()}
) :: {:ok, Mint.HTTP.t()} | {:error, Mint.HTTP.t(), error()}

Streams chunks of data on the connection

stream_request_body/3 should be used to send encoded data on an established WebSocket connection that has already been upgraded with upgrade/5.

This function is a wrapper around Mint.HTTP.stream_request_body/3. It delegates to that function unless the request_ref belongs to an HTTP/1 WebSocket connection. When the request is an HTTP/1 WebSocket, this function allows sending data on a request which Mint considers to be closed, but is actually a valid WebSocket connection.

See the Mint.HTTP.stream_request_body/3 documentation for more information.

Examples

{:ok, websocket, data} = Mint.WebSocket.encode(websocket, {:text, "hello world!"})
{:ok, conn} = Mint.WebSocket.stream_request_body(conn, websocket_ref, data)
Link to this function

upgrade(scheme, conn, path, headers, opts \\ [])

View Source
@spec upgrade(
  scheme :: :ws | :wss,
  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.

The scheme argument should be either :ws or :wss, using :ws for connections established by passing :http to Mint.HTTP.connect/4 and :wss corresponding to :https.

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 a Mint.WebSocket.Extension struct

Examples

{:ok, conn} = Mint.HTTP.connect(:http, "localhost", 9_000)
{:ok, conn, ref} =
  Mint.WebSocket.upgrade(:ws, conn, "/", [], extensions: [Mint.WebSocket.PerMessageDeflate])
# or provide params:
{:ok, conn, ref} =
  Mint.WebSocket.upgrade(
    :ws,
    conn,
    "/",
    [],
    extensions: [{Mint.WebSocket.PerMessageDeflate, [:client_max_window_bits]]}]
  )