diff --git a/config/config.exs b/config/config.exs index fa61688..a0641b5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,3 +7,6 @@ config :git_ops, manage_mix_version?: true, version_tag_prefix: "v", manage_readme_version: "README.md" + +config :wayfarer, + start_listeners?: config_env() != :test diff --git a/lib/wayfarer.ex b/lib/wayfarer.ex index 669b5b0..8ebb5ba 100644 --- a/lib/wayfarer.ex +++ b/lib/wayfarer.ex @@ -2,17 +2,4 @@ defmodule Wayfarer do @moduledoc """ Documentation for `Wayfarer`. """ - - @doc """ - Hello world. - - ## Examples - - iex> Wayfarer.hello() - :world - - """ - def hello do - :world - end end diff --git a/lib/wayfarer/application.ex b/lib/wayfarer/application.ex index 3824f6d..626ba8b 100644 --- a/lib/wayfarer/application.ex +++ b/lib/wayfarer/application.ex @@ -7,6 +7,15 @@ defmodule Wayfarer.Application do @spec start(any, any) :: {:error, any} | {:ok, pid} def start(_type, _args) do [] + |> start_listeners?() |> Supervisor.start_link(strategy: :one_for_one, name: Wayfarer.Supervisor) end + + defp start_listeners?(children) do + if Application.get_env(:wayfarer, :start_listeners?, true) do + Enum.concat(children, [Wayfarer.Listener.Supervisor]) + else + children + end + end end diff --git a/lib/wayfarer/listener.ex b/lib/wayfarer/listener.ex new file mode 100644 index 0000000..59f184e --- /dev/null +++ b/lib/wayfarer/listener.ex @@ -0,0 +1,23 @@ +defmodule Wayfarer.Listener do + @moduledoc """ + Manage HTTP listeners. + """ + + alias Wayfarer.Listener.DynamicSupervisor, as: ListenerSupervisor + alias Wayfarer.Listener.Registry, as: ListenerRegistry + alias Wayfarer.Listener.Server + + @doc """ + Start listener. + """ + @spec start_listener(Server.options()) :: Supervisor.on_start_child() + def start_listener(options), + do: DynamicSupervisor.start_child(ListenerSupervisor, {Server, options}) + + @doc """ + Stop listener + """ + @spec stop_listener(:inet.socket_address(), :inet.port_number()) :: :ok + def stop_listener(ip, port), + do: GenServer.stop({:via, Registry, {ListenerRegistry, {ip, port}}}, :normal) +end diff --git a/lib/wayfarer/listener/plug.ex b/lib/wayfarer/listener/plug.ex new file mode 100644 index 0000000..ea263b2 --- /dev/null +++ b/lib/wayfarer/listener/plug.ex @@ -0,0 +1,21 @@ +defmodule Wayfarer.Listener.Plug do + @moduledoc """ + Plug pipeline to handle inbound HTTP connections. + """ + import Plug.Conn + @behaviour Plug + + @doc false + @impl true + def init(options) do + options + end + + @doc false + @impl true + def call(conn, _opts) do + conn + |> put_resp_content_type("text/plain") + |> send_resp(502, "Bad Gateway") + end +end diff --git a/lib/wayfarer/listener/server.ex b/lib/wayfarer/listener/server.ex new file mode 100644 index 0000000..f032659 --- /dev/null +++ b/lib/wayfarer/listener/server.ex @@ -0,0 +1,86 @@ +defmodule Wayfarer.Listener.Server do + @moduledoc """ + A GenServer which manages the state of each listener. + """ + + alias Wayfarer.Listener.Plug + alias Wayfarer.Listener.Registry, as: ListenerRegistry + + use GenServer, restart: :transient + require Logger + + # How long to wait for connections to drain when shutting down a listener. + @drain_timeout :timer.seconds(60) + + @type options :: [ + scheme: :http | :https, + port: :inet.port_number(), + ip: :inet.socket_address(), + keyfile: binary(), + certfile: binary(), + otp_app: binary() | atom(), + cipher_suite: :strong | :compatible, + display_plug: module(), + startup_log: Logger.level() | false, + thousand_island_options: ThousandIsland.options(), + http_1_options: Bandit.http_1_options(), + http_2_options: Bandit.http_2_options(), + websocket_options: Bandit.websocket_options() + ] + + @doc false + @spec start_link(options) :: GenServer.on_start() + def start_link(options), do: GenServer.start_link(__MODULE__, options) + + @doc false + @impl true + def init(options) do + options = + options + |> Keyword.put(:plug, Plug) + |> Keyword.put(:startup_log, false) + |> Keyword.update( + :thousand_island_options, + [shutdown_timeout: @drain_timeout], + &Keyword.put_new(&1, :shutdown_timeout, @drain_timeout) + ) + + with {:ok, scheme} <- fetch_required_option(options, :scheme), + {:ok, pid} <- Bandit.start_link(options), + {:ok, %{address: addr, port: port}} <- ThousandIsland.listener_info(pid), + {:ok, _pid} <- Registry.register(ListenerRegistry, {addr, port}, pid) do + listen_url = listen_url(scheme, addr, port) + version = Application.spec(:wayfarer)[:vsn] + Logger.info("Started Wayfarer v#{version} listener on #{listen_url}") + + {:ok, %{server: pid, options: options, addr: addr}} + end + end + + @doc false + @impl true + def terminate(:normal, %{server: server}) do + GenServer.stop(server, :normal) + end + + defp fetch_required_option(options, option) do + case Keyword.fetch(options, option) do + {:ok, value} -> {:ok, value} + :error -> {:error, {:required_option, option}} + end + end + + defp listen_url(scheme, {:local, socket_path}, _), do: "#{scheme}:#{socket_path}" + + defp listen_url(scheme, address, port) when tuple_size(address) == 4 do + "#{scheme}://#{:inet.ntoa(address)}:#{port}" + |> URI.new!() + |> to_string() + end + + defp listen_url(scheme, address, port) when tuple_size(address) == 8 do + "#{scheme}://[#{:inet.ntoa(address)}]:#{port}" + |> URI.new!() + |> to_string() + end +end diff --git a/lib/wayfarer/listener/supervisor.ex b/lib/wayfarer/listener/supervisor.ex new file mode 100644 index 0000000..2f8fd0f --- /dev/null +++ b/lib/wayfarer/listener/supervisor.ex @@ -0,0 +1,21 @@ +defmodule Wayfarer.Listener.Supervisor do + @moduledoc """ + Supervisor for HTTP listeners. + """ + + use Supervisor + + @doc false + @spec start_link(any) :: Supervisor.on_start() + def start_link(arg), do: Supervisor.start_link(__MODULE__, arg) + + @doc false + @impl true + def init(_arg) do + [ + {Registry, keys: :unique, name: Wayfarer.Listener.Registry}, + {DynamicSupervisor, name: Wayfarer.Listener.DynamicSupervisor} + ] + |> Supervisor.init(strategy: :one_for_one) + end +end diff --git a/mix.exs b/mix.exs index 6fd5b8a..19587d7 100644 --- a/mix.exs +++ b/mix.exs @@ -55,6 +55,12 @@ defmodule Wayfarer.MixProject do opts = [only: ~w[dev test]a, runtime: false] [ + {:bandit, "~> 0.7"}, + {:mint, "~> 1.5"}, + {:nimble_options, "~> 1.0"}, + {:plug, "~> 1.15"}, + {:websock, "~> 0.5"}, + # Dev/test {:credo, "~> 1.7", opts}, {:dialyxir, "~> 1.3", opts}, @@ -63,6 +69,7 @@ defmodule Wayfarer.MixProject do {:ex_check, "~> 0.15", opts}, {:ex_doc, ">= 0.0.0", opts}, {:faker, "~> 0.17", opts}, + {:finch, "~> 0.16", opts}, {:git_ops, "~> 2.6", opts}, {:mix_audit, "~> 2.1", opts} ] diff --git a/mix.lock b/mix.lock index 3030b63..46e624e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,5 +1,7 @@ %{ + "bandit": {:hex, :bandit, "0.7.7", "48456d09022607a312cf723a91992236aeaffe4af50615e6e2d2e383fb6bef10", [:mix], [{:hpax, "~> 0.1.1", [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, "~> 0.6.7", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "772f0a32632c2ce41026d85e24b13a469151bb8cea1891e597fb38fde103640a"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "castore": {:hex, :castore, "1.0.4", "ff4d0fb2e6411c0479b1d965a814ea6d00e51eb2f58697446e9c41a97d940b28", [:mix], [], "hexpm", "9418c1b8144e11656f0be99943db4caf04612e3eaecefb5dae9a2a87565584f8"}, "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.1", "a22ed1e7bd3a3e3f197b68d806ef66acb61ee8f57b3ac85fc5d57354c5482a93", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "84b795d6d7796297cca5a3118444b80c7d94f7ce247d49886e7c291e1ae49801"}, @@ -11,14 +13,25 @@ "ex_doc": {:hex, :ex_doc, "0.30.6", "5f8b54854b240a2b55c9734c4b1d0dd7bdd41f71a095d42a70445c03cf05a281", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "bd48f2ddacf4e482c727f9293d9498e0881597eae6ddc3d9562bd7923375109f"}, "faker": {:hex, :faker, "0.17.0", "671019d0652f63aefd8723b72167ecdb284baf7d47ad3a82a15e9b8a6df5d1fa", [:mix], [], "hexpm", "a7d4ad84a93fd25c5f5303510753789fc2433ff241bf3b4144d3f6f291658a6a"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, "git_cli": {:hex, :git_cli, "0.3.0", "a5422f9b95c99483385b976f5d43f7e8233283a47cda13533d7c16131cb14df5", [:mix], [], "hexpm", "78cb952f4c86a41f4d3511f1d3ecb28edb268e3a7df278de2faa1bd4672eaf9b"}, "git_ops": {:hex, :git_ops, "2.6.0", "e0791ee1cf5db03f2c61b7ebd70e2e95cba2bb9b9793011f26609f22c0900087", [:mix], [{:git_cli, "~> 0.2", [hex: :git_cli, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "b98fca849b18aaf490f4ac7d1dd8c6c469b0cc3e6632562d366cab095e666ffe"}, + "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [: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", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, + "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "mix_audit": {:hex, :mix_audit, "2.1.1", "653aa6d8f291fc4b017aa82bdb79a4017903902ebba57960ef199cbbc8c008a1", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.9", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "541990c3ab3a7bb8c4aaa2ce2732a4ae160ad6237e5dcd5ad1564f4f85354db1"}, + "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, + "plug": {:hex, :plug, "1.15.1", "b7efd81c1a1286f13efb3f769de343236bd8b7d23b4a9f40d3002fc39ad8f74c", [: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", "459497bd94d041d98d948054ec6c0b76feacd28eec38b219ca04c0de13c79d30"}, + "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, + "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "thousand_island": {:hex, :thousand_island, "0.6.7", "3a91a7e362ca407036c6691e8a4f6e01ac8e901db3598875863a149279ac8571", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "541a5cb26b88adf8d8180b6b96a90f09566b4aad7a6b3608dcac969648cf6765"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, "yamerl": {:hex, :yamerl, "0.10.0", "4ff81fee2f1f6a46f1700c0d880b24d193ddb74bd14ef42cb0bcf46e81ef2f8e", [:rebar3], [], "hexpm", "346adb2963f1051dc837a2364e4acf6eb7d80097c0f53cbdc3046ec8ec4b4e6e"}, "yaml_elixir": {:hex, :yaml_elixir, "2.9.0", "9a256da867b37b8d2c1ffd5d9de373a4fda77a32a45b452f1708508ba7bbcb53", [:mix], [{:yamerl, "~> 0.10", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "0cb0e7d4c56f5e99a6253ed1a670ed0e39c13fc45a6da054033928607ac08dfc"}, } diff --git a/test/support/test.cert b/test/support/test.cert new file mode 100644 index 0000000..98f33f0 --- /dev/null +++ b/test/support/test.cert @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFOzCCAyOgAwIBAgIUdOavKT2HoubHQ61WxIFEIXO+/fIwDQYJKoZIhvcNAQEL +BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjMxMDEzMjMxMTI4WhcNMzMx +MDEwMjMxMTI4WjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAKWgA8rt/3qP7L4JqCKj/oRdNm9iUSOP2TfzTH+4 +V2ho9XgE3MZJXqcnKvtIPmAHv2bFIR+jiYmv58cvB+vjRHvl4g88WX0Kbj2VGAFI +VYFJ2lAsuIsxucjoD/6nSoW3uZcWAvipBQsEe7D70IcyXjh6ZwWxabywL3tp9Kjb +ZvB0IwKrBWrqESYRSZtfrh/g//qp1uZJnJf+GaAJ9Mgn0rV8t1IbKOZjDRG8wNm6 +8fuywdu3HYj9x9zHPO0NyxQzOY1tDlcIVXKAvlJbaKP5vYXu/fdRS3t4Xh///CHh +EjoRwymHly9g1hDmQXKe1ZAqudGjqhKG+jTidROuYj25yLpIsi8Lx1ZEQj1vMJMK +pWv+k/Rb2m4a0lvkGPlLAGd7aTakNqAwyEbNKo9T2E/tpggZCQ/72ltW+vFfpMue +gLiJG6CdWH+6iCi99flKApD/I+vBuFRjWYtls8XptmnPHh657WO8J1dGzvhW/W2a +SyWdrKmFsXuDmMlXdoqyNlVnyA3Zq07FlMlHZcqhxFXF9kDAhixbIWUXk8O8Cjqk +7dkTtY/bWBe4jsTigOClPseeTY9h/1zvluJmTycJzH1weWtYrGah1RTX7QcRt5H6 +CiD3hYBGj+0EW9z6R3JqgmfodRUpQRLiWPxDlNly7H0D8c+2pB0mKymF6DRk+S9P +ODCZAgMBAAGjgYAwfjAdBgNVHQ4EFgQU+YZ+OGNddicX5fWAYGfr+iLNV8IwHwYD +VR0jBBgwFoAU+YZ+OGNddicX5fWAYGfr+iLNV8IwDwYDVR0TAQH/BAUwAwEB/zAr +BgNVHREEJDAiggtleGFtcGxlLmNvbYINKi5leGFtcGxlLmNvbYcEfwAAATANBgkq +hkiG9w0BAQsFAAOCAgEALoEobHweztlJaYElHlMKK6ZaWYqXE6BZN4zOuItubqzt +rYVYkpM+Lamu8RCqw+Hizj77OzE5TiLNRFZX/Y27uXFRVw7PT8+IkdVfgfEjIwvR +CCWFmFdTA0qD7mNnZjRUMH8WGllbXHdrZ4WPu3pNWV2op1/nGjSF7+QKez4f7BYK +vcA3N7Vw/w+NE0PLI707RpKdwnbAtqFI/8Rus5K6HEU8lGh4vErQSkYLpxfWfqgH +BPzuA75fpzzx3DPQ6Xo4BN2KwyZFHX8vdZ4aQxFq4aH4ITUuirdJIVLMnM0ghI3O +ZaRdZ6FhV9tSDBrX4pNZSgAFHtDiEGsYZF4gS81G9NUNxGN5zXK1s5KCtvCACKAm +OC0+9zWFNQSK/0KEwVnv7TDsApTa3Fu60xnRLwmcuK1erx8Pa76ae72CVbG4iKgK +msvWCuzUnM3wZqm7rUD8GJMeOJg8aSMJn8qLz/+vr23o8DUxUdPyZPnb/0wkZ88C +IwWPPXz3XgVnUZpLK6KtxPbGyDJlDZ9EzY/avtepMbBYiWB0UfibqiW/ZwwFx0rd +BWamBzZeWjUc7ayJ+4rYpZs4IToPl9sJEiAJnrLlDe5ezJ2tHYHtxBLQZ5XjF4vR +MP5QDhLCMUXqnq9EG5m7bNhm/paHw/OdvhyYwE+HkyzJXJidPnf44Vts+DDGv6o= +-----END CERTIFICATE----- diff --git a/test/support/test.key b/test/support/test.key new file mode 100644 index 0000000..c089363 --- /dev/null +++ b/test/support/test.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +HvfyQy8D6gQkufWaRINIMmNukIEFlg== +-----END PRIVATE KEY----- diff --git a/test/wayfarer/listener_test.exs b/test/wayfarer/listener_test.exs new file mode 100644 index 0000000..ca53858 --- /dev/null +++ b/test/wayfarer/listener_test.exs @@ -0,0 +1,139 @@ +defmodule Wayfarer.ListenerTest do + @moduledoc false + use ExUnit.Case, async: false + alias Wayfarer.Listener + import ExUnit.CaptureLog + + setup do + start_supervised!(Wayfarer.Listener.Supervisor) + + start_supervised!( + {Finch, + name: :test_client, + pools: %{ + default: [ + conn_opts: [ + transport_opts: [ + verify: :verify_none + ] + ] + ] + }} + ) + + :ok + end + + describe "start_listener/1" do + test "it returns an error when the scheme option is missing" do + assert {:error, {:required_option, :scheme}} = Listener.start_listener([]) + end + + test "it returns an error when an option is incorrect" do + assert {:error, _} = Listener.start_listener(scheme: "Marty McFly", port: random_port()) + end + + test "it can start an HTTP listener" do + port = random_port() + + assert {:ok, _pid} = + Listener.start_listener( + scheme: :http, + ip: {127, 0, 0, 1}, + port: port + ) + + assert {:ok, %{status: 502}} = + :get + |> Finch.build("http://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + end + + test "it can start an HTTPS listener" do + port = random_port() + + certfile = Path.join(__DIR__, "../support/test.cert") + keyfile = Path.join(__DIR__, "../support/test.key") + + assert {:ok, _pid} = + Listener.start_listener( + scheme: :https, + ip: {127, 0, 0, 1}, + port: port, + certfile: certfile, + keyfile: keyfile + ) + + assert {:ok, %{status: 502}} = + :get + |> Finch.build("https://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + end + + test "it restarts listeners when they crash" do + port = random_port() + + assert {:ok, _pid} = + Listener.start_listener( + scheme: :http, + ip: {127, 0, 0, 1}, + port: port + ) + + # It's up + assert {:ok, %{status: 502}} = + :get + |> Finch.build("http://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + + # Crash it + capture_log(fn -> + [{_, pid}] = Registry.lookup(Listener.Registry, {{127, 0, 0, 1}, port}) + Process.exit(pid, :kill) + end) + + # It's up again + assert {:ok, %{status: 502}} = + :get + |> Finch.build("http://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + end + end + + describe "stop_listener/2" do + test "it can shut down a listener" do + port = random_port() + + assert {:ok, pid} = + Listener.start_listener( + scheme: :http, + ip: {127, 0, 0, 1}, + port: port + ) + + assert {:ok, %{status: 502}} = + :get + |> Finch.build("http://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + + Listener.stop_listener({127, 0, 0, 1}, port) + + wait_until_dead(pid) + + assert {:error, _} = + :get + |> Finch.build("http://127.0.0.1:#{port}/") + |> Finch.request(:test_client) + end + end + + defp random_port, do: :rand.uniform(0xFFFF - 1000) + 1000 + + defp wait_until_dead(pid) do + if Process.alive?(pid) do + wait_until_dead(pid) + else + :ok + end + end +end diff --git a/test/wayfarer_test.exs b/test/wayfarer_test.exs index 16fbf22..a055656 100644 --- a/test/wayfarer_test.exs +++ b/test/wayfarer_test.exs @@ -1,8 +1,5 @@ defmodule WayfarerTest do + @moduledoc false use ExUnit.Case doctest Wayfarer - - test "greets the world" do - assert Wayfarer.hello() == :world - end end