Currently, the argument passed to the child_spec/1
of the endpoint is ignored. IMHO it would be more useful to accept that as a configuration parameters as well.
I have encountered such thing when I was working on “endpoint” (from the lack of better description) that would allow me to have only one Cowboy server running while dispatching the requests handling to respective umbrella applications. Regular forward
in the router will not do, as this do not support handling sockets as well. So I have ended with something like:
defmodule MyApp.Endpoint do
use Plug.Builder
@endpoints [
MyAppRest.Endpoint,
MyAppUi.Endpoint
]
def child_spec({scheme, options}) do
dispatches =
for endpoint <- @endpoints do
url = endpoint.config(:url, [path: "/"]) |> Keyword.fetch!(:path)
prefix =
case Path.split(url) do
["/" | rest] -> rest
rest -> rest
end
path =
case url do
"/" -> :_
other -> Path.join(["/", other, "[...]"])
end
{path, MyApp.Endpoint.Handler, {endpoint, prefix, endpoint.init([])}}
end
check_dispatches(dispatches)
options =
options
|> Keyword.put(:cipher_suite, :strong)
|> Keyword.put_new(:dispatch, _: dispatches)
|> Keyword.put_new(:keyfile, System.get_env("SSL_KEY"))
|> Keyword.put_new(:certfile, System.get_env("SSL_CERT"))
|> Keyword.put_new_lazy(:port, fn -> port(scheme) end)
spec =
Plug.Cowboy.child_spec(
scheme: scheme,
plug: __MODULE__,
options: options
)
update_in(spec.start, &{__MODULE__, :start_link, [scheme, &1]})
end
def start_link(scheme, {m, f, [ref | _] = a}) do
case apply(m, f, a) do
{:ok, pid} ->
:logger.info(&info/1, {scheme, __MODULE__, ref})
{:ok, pid}
{:error, {:shutdown, {_, _, {{_, {:error, :eaddrinuse}}, _}}}} = error ->
:logger.error(
info({scheme, __MODULE__, ref}) <> " failed, port already in use"
)
error
{:error, _} = error ->
error
end
end
defp check_dispatches(dispatches) do
entries =
dispatches
|> Enum.map(&elem(&1, 0))
|> Enum.sort()
find_duplicate(entries)
end
defp find_duplicate([a, a | _]), do: raise "Duplicated prefix #{inspect(a)}"
defp find_duplicate([_ | rest]), do: find_duplicate(rest)
defp find_duplicate([]), do: :ok
defp info({scheme, endpoint, ref}) do
server = "cowboy #{Application.spec(:cowboy)[:vsn]}"
"Running #{inspect(endpoint)} with #{server} at #{uri(scheme, ref)}"
end
defp uri(scheme, ref) do
{host, port} = :ranch.get_addr(ref)
%URI{
scheme: to_string(scheme),
host: List.to_string(:inet.ntoa(host)),
port: port
}
end
defp port(scheme), do: String.to_integer(System.get_env("PORT_#{scheme}"))
plug(MyApp.Plugs.Clacks)
plug(
MyApp.Plugs.Health,
applications: [:kokon, :kokon_web, :kokon_ui, :kokon_rest]
)
plug(MyApp.Plugs.Measure)
plug(MyApp.Plugs.Trace)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
end
But the problem is that I need to remember to set proper path in configuration of each of endpoints. Instead I would prefer it to be constant in and specified within supervisor, as this would reduce the amount of needless options present in the sys.config
file.