View Source Minch behaviour (Minch v0.1.0)

A WebSocket client build around Mint.WebSocket.

Usage

Supervised client

defmodule EchoClient do
  use Minch

  require Logger

  def start_link(init_arg) do
    Minch.start_link(__MODULE__, init_arg, name: __MODULE__)
  end

  @impl true
  def init(_init_arg) do
    {:ok, %{connected?: false}}
  end

  @impl true
  def connect(_state) do
    url = "wss://ws.postman-echo.com/raw"
    headers = [{"authorization", "bearer: example"}]
    # don't do this in production
    options = [transport_opts: [{:verify, :verify_none}]]
    {url, headers, options}
  end

  @impl true
  def handle_connect(state) do
    Logger.info("connected")
    Process.send_after(self(), :produce, 5000)
    {:reply, {:text, "welcome"}, %{state | connected?: true}}
  end

  @impl true
  def handle_disconnect(reason, state) do
    Logger.warning("disconnected: #{inspect(reason)}")
    {:reconnect, 1000, %{state | connected?: false}}
  end

  @impl true
  def handle_info(:produce, state) do
    Process.send_after(self(), :produce, 5000)
    {:reply, {:text, DateTime.utc_now() |> DateTime.to_iso8601()}, state}
  end

  @impl true
  def handle_frame(frame, state) do
    Logger.info(inspect(frame))
    {:ok, state}
  end
end

Simple client

url = "wss://ws.postman-echo.com/raw"
headers = []
# don't do this in production
options = [transport_opts: [{:verify, :verify_none}]]

IO.puts("checking ping to #{url}...")

case Minch.connect(url, headers, options) do
  {:ok, pid, ref} ->
    Minch.send_frame(pid, {:text, to_string(System.monotonic_time())})

    case Minch.receive_frame(ref, 5000) do
      {:text, start} ->
        ping =
          System.convert_time_unit(
            System.monotonic_time() - String.to_integer(start),
            :native,
            :millisecond
          )

        IO.puts("#{ping}ms")

      :timeout ->
        IO.puts("timeout")
    end

    Minch.close(pid)

  {:error, error} ->
    IO.puts("connection error: #{inspect(error)}")
end

Summary

Callbacks

Invoked to retrieve the connection details.

Invoked to handle a successful connection.

Invoked to handle a disconnect from the server or a failed connection attempt.

Invoked to handle an incoming WebSocket frame.

Invoked to handle all other messages.

Invoked when the client process is started.

Invoked when the client process is about to exit.

Functions

Closes a connection opened by connect/3.

Connects to a WebSocket server.

Receives an incoming WebSocket frame.

Sends a WebSocket frame.

Starts a Minch client process linked to the current process.

Types

Callbacks

@callback connect(state :: term()) :: url | {url, headers} | {url, headers, options}
when url: String.t() | URI.t(),
     headers: Mint.Types.headers(),
     options: Keyword.t()

Invoked to retrieve the connection details.

See Minch.Conn.open/3.

@callback handle_connect(state :: term()) ::
  {:ok, new_state} | {:reply, frame :: Mint.WebSocket.frame(), new_state}
when new_state: term()

Invoked to handle a successful connection.

Link to this callback

handle_disconnect(reason, state)

View Source
@callback handle_disconnect(reason :: term(), state :: term()) ::
  {:ok, new_state} | {:reconnect, backoff :: pos_integer(), new_state}
when new_state: term()

Invoked to handle a disconnect from the server or a failed connection attempt.

Returning {:reconnect, backoff, state} will schedule a reconnect after backoff milliseconds.

Returning {:ok, state} will keep the client in the disconnected state. Later you can instruct the client to reconnect by sending it a message and returning {:reconnect, state} from the handle_info/2 callback.

Link to this callback

handle_frame(frame, state)

View Source
@callback handle_frame(frame :: Mint.WebSocket.frame(), state :: term()) ::
  {:ok, new_state} | {:reply, frame :: Mint.WebSocket.frame(), new_state}
when new_state: term()

Invoked to handle an incoming WebSocket frame.

A "close" frame will cause the connection to be closed but you'll still receive the frame.

Other than that, Minch does not intercept nor handle control frames automatically.

Link to this callback

handle_info(msg, state)

View Source (optional)
@callback handle_info(msg :: :timeout | term(), state :: term()) ::
  {:ok, new_state}
  | {:reply, frame :: Mint.WebSocket.frame(), new_state}
  | {:reconnect, new_state}
when new_state: term()

Invoked to handle all other messages.

Link to this callback

init(init_arg)

View Source (optional)
@callback init(init_arg :: term()) :: {:ok, new_state :: term()}

Invoked when the client process is started.

Link to this callback

terminate(reason, state)

View Source (optional)
@callback terminate(reason, state :: term()) :: term()
when reason: :normal | :shutdown | {:shutdown, term()} | term()

Invoked when the client process is about to exit.

Functions

@spec close(pid()) :: :ok

Closes a connection opened by connect/3.

Link to this function

connect(url, headers \\ [], options \\ [])

View Source
@spec connect(String.t() | URI.t(), Mint.Types.headers(), Keyword.t()) ::
  {:ok, pid(), Mint.Types.request_ref()} | {:error, Mint.WebSocket.error()}

Connects to a WebSocket server.

Once connected, the function will return the connection's PID and the request reference.

Use the send_frame/2 function to send WebSocket frames to the server.

Incoming WebSocket frames will be sent to the caller's mailbox as a tuple {:frame, request_ref, frame}. You can use the receive_frame/2 function to receive these frames.

Use Process.monitor/1 to get notified when the connection is closed by the server.

Example

iex> {:ok, pid, ref} = Minch.connect("wss://ws.postman-echo.com/raw", [], transport_opts: [{:verify, :verify_none}])
{:ok, _pid, _ref}
iex> Minch.send_frame(pid, {:text, "hello"})
:ok
iex> Minch.receive_frame(ref, 5000)
{:text, "hello"}
iex> Minch.close(pid)
:ok
Link to this function

receive_frame(ref, timeout \\ 5000)

View Source
@spec receive_frame(Mint.Types.request_ref(), timeout()) ::
  Mint.WebSocket.frame() | :timeout

Receives an incoming WebSocket frame.

See connect/2.

Link to this function

send_frame(client, frame)

View Source
@spec send_frame(client(), Mint.WebSocket.frame() | Mint.WebSocket.shorthand_frame()) ::
  :ok | {:error, Mint.WebSocket.error()}

Sends a WebSocket frame.

Link to this function

start_link(module, init_arg, options \\ [])

View Source
@spec start_link(module(), any(), GenServer.options()) :: GenServer.on_start()

Starts a Minch client process linked to the current process.