improvement(Server): listeners can be dynamically added.
This commit is contained in:
parent
fad27f142e
commit
9e22c1cca8
3 changed files with 98 additions and 14 deletions
|
@ -98,7 +98,12 @@ defmodule Wayfarer.Server do
|
||||||
|> Keyword.merge(opts)
|
|> Keyword.merge(opts)
|
||||||
|> Keyword.put(:module, __MODULE__)
|
|> Keyword.put(:module, __MODULE__)
|
||||||
|
|
||||||
Server.child_spec(opts)
|
default = %{
|
||||||
|
id: __MODULE__,
|
||||||
|
start: {Wayfarer.Server, :start_link, [opts]}
|
||||||
|
}
|
||||||
|
|
||||||
|
Supervisor.child_spec(default, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -116,6 +121,24 @@ defmodule Wayfarer.Server do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@type listener_options :: unquote(Options.option_typespec(Dsl.Listener.schema()))
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Add a listener to an already running server.
|
||||||
|
|
||||||
|
If the listener fails to start for any reason, then this function will return
|
||||||
|
an error, otherwise it will block until the listener is ready to accept
|
||||||
|
requests.
|
||||||
|
|
||||||
|
"""
|
||||||
|
@spec add_listener(module, listener_options) :: :ok | {:error, any}
|
||||||
|
def add_listener(module, options) do
|
||||||
|
with {:ok, options} <- Options.validate(options, Dsl.Listener.schema()) do
|
||||||
|
{:via, Registry, {Wayfarer.Server.Registry, module}}
|
||||||
|
|> GenServer.call({:add_listener, options})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
@spec target_status_change(
|
@spec target_status_change(
|
||||||
{module, :http | :https, IP.Address.t(), :socket.port_number(),
|
{module, :http | :https, IP.Address.t(), :socket.port_number(),
|
||||||
|
@ -177,26 +200,40 @@ defmodule Wayfarer.Server do
|
||||||
{:noreply, state}
|
{:noreply, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@impl true
|
||||||
|
@spec handle_call(any, GenServer.from(), map) :: {:reply, any, map}
|
||||||
|
def handle_call({:add_listener, listener}, _from, state) do
|
||||||
|
{:reply, start_listener(listener, state), state}
|
||||||
|
end
|
||||||
|
|
||||||
defp start_listeners(listeners, state) do
|
defp start_listeners(listeners, state) do
|
||||||
listeners
|
listeners
|
||||||
|> Enum.reduce_while({:ok, state}, fn listener, success ->
|
|> Enum.reduce_while({:ok, state}, fn listener, success ->
|
||||||
listener = Keyword.put(listener, :module, state.module)
|
case start_listener(listener, state) do
|
||||||
|
{:ok, _} -> {:cont, success}
|
||||||
case DynamicSupervisor.start_child(Listener.DynamicSupervisor, {Listener, listener}) do
|
{:error, reason} -> {:halt, {:error, reason}}
|
||||||
{:ok, pid} ->
|
|
||||||
Process.link(pid)
|
|
||||||
{:cont, success}
|
|
||||||
|
|
||||||
{:error, {:already_started, pid}} ->
|
|
||||||
Process.link(pid)
|
|
||||||
{:cont, success}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
{:halt, {:error, reason}}
|
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp start_listener(listener, state) do
|
||||||
|
listener = Keyword.put(listener, :module, state.module)
|
||||||
|
|
||||||
|
case DynamicSupervisor.start_child(Listener.DynamicSupervisor, {Listener, listener}) do
|
||||||
|
{:ok, pid} ->
|
||||||
|
Process.link(pid)
|
||||||
|
{:ok, pid}
|
||||||
|
|
||||||
|
{:error, {:already_started, pid}} ->
|
||||||
|
Process.link(pid)
|
||||||
|
{:ok, pid}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp start_targets(targets, state) do
|
defp start_targets(targets, state) do
|
||||||
targets
|
targets
|
||||||
|> Enum.reduce_while({:ok, state}, fn target, success ->
|
|> Enum.reduce_while({:ok, state}, fn target, success ->
|
||||||
|
|
6
test/support/dynamic.ex
Normal file
6
test/support/dynamic.ex
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
defmodule Support.Dynamic do
|
||||||
|
@moduledoc """
|
||||||
|
An empty server for testing dynamic proxy configuration.
|
||||||
|
"""
|
||||||
|
use Wayfarer.Server, targets: [], listeners: [], routing_table: []
|
||||||
|
end
|
41
test/wayfarer/server/dynamic_test.exs
Normal file
41
test/wayfarer/server/dynamic_test.exs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
defmodule Wayfarer.Server.DynamicTest do
|
||||||
|
use ExUnit.Case, async: true
|
||||||
|
|
||||||
|
alias Wayfarer.{Listener, Server, Target}
|
||||||
|
use Support.PortTracker
|
||||||
|
use Support.HttpRequest
|
||||||
|
import IP.Sigil
|
||||||
|
|
||||||
|
defmodule DynamicServer1 do
|
||||||
|
use Wayfarer.Server
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule DynamicServer2 do
|
||||||
|
use Wayfarer.Server
|
||||||
|
end
|
||||||
|
|
||||||
|
setup do
|
||||||
|
start_supervised!(Server.Supervisor)
|
||||||
|
start_supervised!(Listener.Supervisor)
|
||||||
|
start_supervised!(Target.Supervisor)
|
||||||
|
start_supervised!(DynamicServer1)
|
||||||
|
start_supervised!(DynamicServer2)
|
||||||
|
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "Server.add_listener/2" do
|
||||||
|
test "a listener can be dynamically added to a server" do
|
||||||
|
port = random_port()
|
||||||
|
assert {:ok, _pid} = Server.add_listener(DynamicServer1, scheme: :http, address: ~i"127.0.0.1", port: port)
|
||||||
|
|
||||||
|
assert {:ok, %{status: 502}} = request(:http, ~i"127.0.0.1", port, host: "www.example.com")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the same listener cannot be added to two servers" do
|
||||||
|
port = random_port()
|
||||||
|
assert {:ok, _pid} = Server.add_listener(DynamicServer1, scheme: :http, address: ~i"127.0.0.1", port: port)
|
||||||
|
assert {:error, _} = Server.add_listener(DynamicServer2, scheme: :http, address: ~i"127.0.0.1", port: port)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue