diff --git a/lib/minch.ex b/lib/minch.ex index 6d27a5d..52383fd 100644 --- a/lib/minch.ex +++ b/lib/minch.ex @@ -9,6 +9,7 @@ defmodule Minch do @type response :: %{status: Mint.Types.status(), headers: Mint.Types.headers()} @type frame :: Mint.WebSocket.frame() | Mint.WebSocket.shorthand_frame() @type option :: {:close_timeout, non_neg_integer()} | GenServer.option() + @type error :: Mint.WebSocket.error() | {:invalid_scheme, String.t() | nil} @type callback_result :: {:ok, state()} @@ -46,9 +47,11 @@ defmodule Minch do Invoked to handle a disconnect from the server or a failed connection attempt. The reason is the `{:close, code, reason}` frame whenever a close handshake was started by - either side, and a `t:Mint.WebSocket.error/0` otherwise. + either side, and a `t:error/0` otherwise. Returning `{:reconnect, backoff, state}` will schedule a reconnect after `backoff` milliseconds. + A reason like `{:invalid_scheme, scheme}` will never resolve on a retry, so return + `{:stop, reason, state}` for those. """ @callback handle_disconnect(reason :: term(), attempt :: pos_integer(), state()) :: {:reconnect, backoff :: pos_integer(), state()} @@ -107,7 +110,7 @@ defmodule Minch do :ok """ @spec connect(String.t() | URI.t(), Mint.Types.headers(), Keyword.t()) :: - {:ok, pid(), Mint.Types.request_ref()} | {:error, Mint.WebSocket.error()} + {:ok, pid(), Mint.Types.request_ref()} | {:error, error() | :timeout} def connect(url, headers \\ [], options \\ []) do Minch.SimpleClient.start(url, headers, options) end diff --git a/lib/minch/conn.ex b/lib/minch/conn.ex index dd5d5c6..061cb0d 100644 --- a/lib/minch/conn.ex +++ b/lib/minch/conn.ex @@ -270,23 +270,22 @@ defmodule Minch.Conn do query -> path <> "?" <> query end - {http_scheme, ws_scheme} = - case url.scheme do - "wss" -> {:https, :wss} - "ws" -> {:http, :ws} - end - {upgrade_opts, connect_opts} = options # set protocol to HTTP1 by default since WebSocket over HTTP2 is barely supported |> Keyword.put_new(:protocols, [:http1]) |> Keyword.split([:extensions]) - with {:ok, conn} <- Mint.HTTP.connect(http_scheme, url.host, url.port, connect_opts) do + with {:ok, http_scheme, ws_scheme} <- schemes(url.scheme), + {:ok, conn} <- Mint.HTTP.connect(http_scheme, url.host, url.port, connect_opts) do Mint.WebSocket.upgrade(ws_scheme, conn, path, headers, upgrade_opts) end end + defp schemes("ws"), do: {:ok, :http, :ws} + defp schemes("wss"), do: {:ok, :https, :wss} + defp schemes(scheme), do: {:error, {:invalid_scheme, scheme}} + defp send_close(%State{} = state, frame) do send_frame(state, frame) cancel_timer(state.close_timer) diff --git a/lib/minch/simple_client.ex b/lib/minch/simple_client.ex index c44168e..9927354 100644 --- a/lib/minch/simple_client.ex +++ b/lib/minch/simple_client.ex @@ -7,7 +7,7 @@ defmodule Minch.SimpleClient do defstruct [:url, :headers, :options, :receiver, :receiver_ref, :monitor_ref, :connected?] @spec start(String.t() | URI.t(), Mint.Types.headers(), Keyword.t()) :: - {:ok, pid(), reference()} | {:error, Mint.WebSocket.error() | :timeout} + {:ok, pid(), reference()} | {:error, Minch.error() | :timeout} def start(url, headers \\ [], options \\ []) do # the default timeout is 30_000 # https://hexdocs.pm/mint/Mint.HTTP.html#connect/4-transport-options diff --git a/test/minch/client_test.exs b/test/minch/client_test.exs index 5a60abf..57db0f9 100644 --- a/test/minch/client_test.exs +++ b/test/minch/client_test.exs @@ -103,6 +103,12 @@ defmodule Minch.ClientTest do [%Mint.TransportError{reason: :nxdomain}, 2, _state]} end + test "handle_disconnect/2 is called for a url with an unsupported scheme" do + Client.start_link(%{receiver: self(), url: "http://localhost", reconnect: 50}) + assert_receive {:client, :handle_disconnect, [{:invalid_scheme, "http"}, 1, _state]} + assert_receive {:client, :handle_disconnect, [{:invalid_scheme, "http"}, 2, _state]} + end + test "replies from a callback", ctx do assert_receive {:client, :handle_connect, _} send(ctx.client, {:reply, :ping})