Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 41 additions & 20 deletions lib/minch/test_server.ex
Original file line number Diff line number Diff line change
@@ -1,34 +1,55 @@
if Code.ensure_loaded?(:cowboy) do
if Code.ensure_loaded?(Bandit) and Code.ensure_loaded?(WebSockAdapter) do
defmodule Minch.TestServer do
@moduledoc false

defmacro __using__(_) do
quote do
@behaviour :cowboy_websocket
defmodule UpgradePlug do
@moduledoc false
use Plug.Builder, copy_opts_to_assign: :opts

plug(:connect)
plug(:upgrade)

defp connect(%{assigns: %{opts: {handler, state, upgrade_opts}}} = conn, _) do
{conn, state} = handler.connect(conn, state)
assign(conn, :opts, {handler, state, upgrade_opts})
end

defp upgrade(%{assigns: %{opts: {handler, state, upgrade_opts}}} = conn, _) do
conn
|> WebSockAdapter.upgrade(handler, state, upgrade_opts)
|> Plug.Conn.halt()
end
end

def child_spec(opts) when is_list(opts) do
{state, transport_opts} = Keyword.pop(opts, :state)
def start_link(handler, state, upgrade_opts \\ [], bandit_opts \\ []) do
bandit_opts
|> Keyword.put(:plug, {UpgradePlug, {handler, state, upgrade_opts}})
|> Keyword.put_new(:startup_log, false)
|> Bandit.start_link()
end

so_reuse_port =
case :os.type() do
{:unix, :linux} -> [{:raw, 0x1, 0xF, <<1::32-native>>}]
{:unix, :darwin} -> [{:raw, 0xFFFF, 0x0200, <<1::32-native>>}]
_ -> []
end
defmacro __using__(_) do
quote location: :keep do
@behaviour WebSock

transport_opts = transport_opts ++ so_reuse_port
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]}
}
end

:ranch.child_spec(make_ref(), :ranch_tcp, transport_opts, :cowboy_clear, %{
env: %{dispatch: :cowboy_router.compile([{:_, [{:_, __MODULE__, state}]}])}
})
def start_link(opts) do
{state, opts} = Keyword.pop(opts, :state)
{upgrade_opts, bandit_opts} = Keyword.split(opts, [:upgrade_opts])
Minch.TestServer.start_link(__MODULE__, state, upgrade_opts, bandit_opts)
end

@impl true
def init(req, state) do
{:cowboy_websocket, req, state}
def connect(conn, state) do
{conn, state}
end

defoverridable init: 2
defoverridable connect: 2
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ defmodule Minch.MixProject do

defp deps do
[
{:cowboy, "~> 2.9", optional: true},
{:bandit, "~> 1.4", optional: true},
{:dialyxir, "~> 1.4", only: [:dev, :test], runtime: false},
{:ex_doc, "~> 0.27", only: :dev, runtime: false},
{:mint_web_socket, "~> 1.0"}
{:mint_web_socket, "~> 1.0"},
{:websock_adapter, "~> 0.5.4", optional: true}
]
end

Expand Down
11 changes: 8 additions & 3 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
%{
"cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
"cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
"bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"},
"dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
Expand All @@ -9,8 +8,14 @@
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"},
"mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"},
"mint_web_socket": {:hex, :mint_web_socket, "1.0.4", "0b539116dbb3d3f861cdf5e15e269a933cb501c113a14db7001a3157d96ffafd", [:mix], [{:mint, ">= 1.4.1 and < 2.0.0-0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "027d4c5529c45a4ba0ce27a01c0f35f284a5468519c045ca15f43decb360a991"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"plug": {:hex, :plug, "1.17.0", "a0832e7af4ae0f4819e0c08dd2e7482364937aea6a8a997a679f2cbb7e026b2e", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6692046652a69a00a5a21d0b7e11fcf401064839d59d6b8787f23af55b1e6bc"},
"plug_crypto": {:hex, :plug_crypto, "2.1.1", "19bda8184399cb24afa10be734f84a16ea0a2bc65054e23a62bb10f06bc89491", [:mix], [], "hexpm", "6470bce6ffe41c8bd497612ffde1a7e4af67f36a15eea5f921af71cf3e11247c"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"thousand_island": {:hex, :thousand_island, "1.3.13", "d598c609172275f7b1648c9f6eddf900e42312b09bfc2f2020358f926ee00d39", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5a34bdf24ae2f965ddf7ba1a416f3111cfe7df50de8d66f6310e01fc2e80b02a"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
"websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
}
8 changes: 4 additions & 4 deletions test/minch/client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ defmodule Minch.ClientTest do
port = 8881
url = "ws://localhost:#{port}"

server_state = Map.merge(%{receiver: self(), init_result: :ok}, ctx[:server_state] || %{})
server_state = Map.merge(%{receiver: self(), connect_result: :ok}, ctx[:server_state] || %{})
start_link_supervised!({Server, state: server_state, port: port})

client_state = Map.merge(%{receiver: self(), url: url}, ctx[:client_state] || %{})
Expand All @@ -75,7 +75,7 @@ defmodule Minch.ClientTest do
assert_receive {:client, :handle_connect, [%{status: 101, headers: _}, _state]}
end

@tag server_state: %{init_result: :unauthorized}
@tag server_state: %{connect_result: :unauthorized}
test "handle_disconnect/2 is called after upgrading error" do
assert_receive {:client, :handle_disconnect,
[%Mint.WebSocket.UpgradeFailureError{status_code: 401}, 1, _]}
Expand All @@ -94,7 +94,7 @@ defmodule Minch.ClientTest do
test "replies from a callback", ctx do
assert_receive {:client, :handle_connect, _}
send(ctx.client, {:reply, :ping})
assert_receive {:server, :frame, :ping}
assert_receive {:server, :frame, {:ping, ""}}
end

test "sends pong to server ping automatically", ctx do
Expand All @@ -120,7 +120,7 @@ defmodule Minch.ClientTest do

test "handle_disconnect/2 is called when received a :close frame from server", ctx do
assert_receive {:client, :handle_connect, _}
Server.send_frame(ctx.server, :close)
Server.close(ctx.server)
assert_receive {:client, :handle_disconnect, _}
end

Expand Down
11 changes: 6 additions & 5 deletions test/minch/simple_client_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Minch.SimpleClientTest do

setup ctx do
port = 8882
server_state = Map.merge(%{receiver: self(), init_result: :ok}, ctx[:server_state] || %{})
server_state = Map.merge(%{receiver: self(), connect_result: :ok}, ctx[:server_state] || %{})
start_link_supervised!({Server, state: server_state, port: port})
[url: "ws://localhost:#{port}"]
end
Expand All @@ -12,20 +12,21 @@ defmodule Minch.SimpleClientTest do
assert {:error, %Mint.TransportError{reason: :nxdomain}} = Minch.connect("ws://example.test")
end

@tag server_state: %{init_result: :timeout}
@tag server_state: %{connect_result: :timeout}
test "handles the connection timeout", %{url: url} do
{:error, :timeout} = Minch.connect(url, [], transport_opts: [timeout: 100])
end

test "sends headers to the server while connecting", %{url: url} do
{:ok, _pid, _ref} = Minch.connect(url, [{"x-hello", "world"}])
assert_receive {:server, :request, %{headers: %{"x-hello" => "world"}}}
assert_receive {:server, :connect, conn}
assert Plug.Conn.get_req_header(conn, "x-hello") == ["world"]
end

test "sends frames to the server", %{url: url} do
{:ok, pid, _ref} = Minch.connect(url)
:ok = Minch.send_frame(pid, :ping)
assert_receive {:server, :frame, :ping}
assert_receive {:server, :frame, {:ping, ""}}
end

test "sends received frames to the parent process", %{url: url} do
Expand Down Expand Up @@ -57,7 +58,7 @@ defmodule Minch.SimpleClientTest do
{:ok, pid, _ref} = Minch.connect(url)
monitor_ref = Process.monitor(pid)
assert_receive {:server, :init, server}
Server.send_frame(server, :close)
Server.close(server)
assert_receive {:DOWN, ^monitor_ref, :process, _pid, {:shutdown, _}}
end
end
60 changes: 41 additions & 19 deletions test/support/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,62 @@ defmodule Server do
:ok
end

def init(req, state) do
send(state.receiver, {:server, :request, req})
def close(server) do
send(server, :close)
:ok
end

case state.init_result do
:ok ->
{:cowboy_websocket, req, state}
def connect(conn, state) do
send(state.receiver, {:server, :connect, conn})

:unauthorized ->
send(state.receiver, {:server, :init, nil})
req = :cowboy_req.reply(401, %{}, "Unauthorized", req)
{:ok, req, state}
case state.connect_result do
:ok ->
{conn, state}

:timeout ->
send(state.receiver, {:server, :init, nil})
Process.sleep(:infinity)

:unauthorized ->
send(state.receiver, {:server, :init, nil})
{conn |> Plug.Conn.send_resp(401, "Unauthorized") |> Plug.Conn.halt(), state}
end
end

def websocket_init(state) do
def init(state) do
send(state.receiver, {:server, :init, self()})
{[], state}
{:ok, state}
end

def websocket_handle(frame, state) do
send(state.receiver, {:server, :frame, frame})
{[], state}
def terminate(reason, state) do
send(state.receiver, {:server, :terminate, reason})
:ok
end

def websocket_info({:send_frame, frame}, state) do
{List.wrap(frame), state}
def handle_in(message, state) do
send(state.receiver, {:server, :frame, to_frame(message)})
{:ok, state}
end

def terminate(reason, _req, state) do
send(state.receiver, {:server, :terminate, reason})
:ok
def handle_control(message, state) do
send(state.receiver, {:server, :frame, to_frame(message)})
{:ok, state}
end

def handle_info({:send_frame, frame}, state) do
{:push, frame, state}
end

def handle_info(:close, state) do
{:stop, :normal, state}
end

def handle_info(message, state) do
send(state.receiver, {:server, :handle_info, [message, state]})
{:ok, state}
end

defp to_frame({data, [opcode: code]}) do
{code, data}
end
end