Phoenix Endpoint `child_spec` argument should be merged into configuration

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.

Pull request