I have already published it here (at least once), but I have created something “in the vain of” that:
defmodule MyWebAppWeb.Endpoint do
use Plug.Builder
require Logger
@endpoints [
MyWebAppRest.Endpoint,
MyWebAppUi.Endpoint
]
def child_spec({scheme, options}) do
dispatches =
@endpoints
|> gen_dispatches()
|> check_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 gen_dispatches(endpoints) do
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, MyWebAppWeb.Endpoint.Handler, {endpoint, prefix, endpoint.init([])}}
end
end
defp check_dispatches(dispatches) do
entries =
dispatches
|> Enum.map(&elem(&1, 0))
|> Enum.sort()
:ok = find_duplicate(entries)
dispatches
catch
{:duplicate, prefix} ->
raise "Duplicated prefix #{inspect(prefix)} in\n#{inspect(dispatches)}"
end
defp find_duplicate([a, a | _]) do
throw({:duplicate, a})
end
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(
MyWebAppWeb.Plugs.Health,
applications: [:kokon, :kokon_web, :kokon_ui, :kokon_rest]
)
plug(Plug.Telemetry.ServerTiming)
plug(Plug.Telemetry, event_prefix: [:phoenix, :endpoint])
plug(PlugClacks, names: ["Joe Armstrong"])
plug(MyWebAppWeb.Plugs.Trace)
plug(MyWebAppWeb.Plugs.Measure)
end
This allows to have multiple Phoenix applications that “feel” independent, but are hosted by single Cowboy instance.