lib/canonical_host.ex

defmodule CanonicalHost do
  @behaviour Plug

  @impl true
  def init(opts) do
    [config_key: opts |> Keyword.validate!([:config_key]) |> Keyword.get(:config_key, :default)]
  end

  @impl true

  def call(%Plug.Conn{method: "GET"} = conn, config_key: config_key) do
    case Application.get_env(:canonical_host, config_key) do
      nil -> conn
      opts -> do_call(conn, Keyword.get(opts, :host), Keyword.get(opts, :scheme, "https"))
    end
  end

  def call(conn, _), do: conn

  defp do_call(conn, nil, _scheme), do: conn
  defp do_call(%Plug.Conn{host: host} = conn, host, _scheme), do: conn

  defp do_call(conn, host, scheme) do
    location = "#{scheme}://#{host}#{conn.request_path}#{query_suffix(conn.query_string)}"

    conn
    |> Phoenix.Controller.redirect(external: location)
    |> Plug.Conn.halt()
  end

  defp query_suffix(""), do: ""
  defp query_suffix(query_string), do: "?#{query_string}"
end