feat: Add ability to start and stop HTTP listeners. (#1)
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: https://code.harton.nz/bivouac/wayfarer/pulls/1
Co-authored-by: James Harton <james@harton.nz>
Co-committed-by: James Harton <james@harton.nz>
This commit is contained in:
James Harton 2023-10-14 14:18:48 +13:00 committed by james
parent cc9e8978ff
commit 20c5ce6f44
13 changed files with 405 additions and 17 deletions

View file

@ -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

View file

@ -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

View file

@ -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

23
lib/wayfarer/listener.ex Normal file
View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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}
]

View file

@ -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"},
}

30
test/support/test.cert Normal file
View file

@ -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-----

52
test/support/test.key Normal file
View file

@ -0,0 +1,52 @@
-----BEGIN PRIVATE KEY-----
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
HvfyQy8D6gQkufWaRINIMmNukIEFlg==
-----END PRIVATE KEY-----

View file

@ -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

View file

@ -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