Phoenix umbrella proxy application.

#elixir#phoenixframework#phoenix#development

Hey everyone!

It's been quite a while since I last wrote here. I've been busy developing games with my free-time company balconygames. We've made good progress, though the market remains challenging due to the high costs of user acquisition.

Today, I'd like to share an example of a proxy application for a Phoenix umbrella project. This might be useful for those facing similar issues with code reloading and WebSocket support. The example has four apps inside the apps/ directory: proxy, web (Phoenix), admin (Phoenix), and db (shared Ecto project).

Without further ado, here's the implementation:

defmodule YourApp.Proxy.Application do
  @moduledoc false
  use Application

  alias Phoenix.LiveReloader.Socket, as: LiveReloadSocket

  require Logger

  def start(_type, _args) do
    import Supervisor.Spec, warn: false

    require_code_reloading?(:your_app_web, YourAppWeb.Endpoint)

    opts = [strategy: :one_for_one, name: YourApp.Proxy.Supervisor]
    Supervisor.start_link(children(Mix.env()), opts)
  end

  def children(:test), do: []

  def children(_) do
    proxy_children = [
      {
        Plug.Cowboy,
        scheme: :http,
        plug: YourApp.Proxy.Builder,
        port: port(),
        dispatch: [
          {:_,
           [
             # your_app_web
             web_phoenix_live_reload(),
             web_phoenix_live_view(),
             web_websocket(),
             # your_app_admin
             admin_phoenix_live_reload(),
             admin_phoenix_live_view(),
             admin_websocket(),
             # proxy
             master_proxy()
           ]}
        ]
      }
    ]

    # - proxy endpoints
    #    - /api endpoints
    #    - /dev/admin pages
    # - web watchers for webpacker and code reloading
    # - admin watchers for webpacker and code reloading
    children =
      proxy_children ++
        watcher_children(
          :your_app_web,
          YourAppWeb.Endpoint
        ) ++
        watcher_children(
          :your_app_admin_web,
          YourAppAdminWeb.Endpoint
        )

    children
  end

  defp watcher_children(otp_app, mod) do
    conf = Application.get_env(otp_app, mod)

    unless Mix.env() == :test do
      Enum.map(conf[:watchers], fn {cmd, args} ->
        {Phoenix.Endpoint.Watcher, watcher_args(cmd, args)}
      end)
    else
      []
    end
  end

  defp watcher_args(cmd, cmd_args) do
    {args, opts} = Enum.split_while(cmd_args, &is_binary(&1))
    {cmd, args, opts}
  end

  def port do
    (System.get_env("PORT") || "4000") |> String.to_integer()
  end

  def web_phoenix_live_reload do
    websocket_handler(
      "/phoenix/live_reload/socket/websocket",
      YourAppWeb.Endpoint,
      {LiveReloadSocket, :websocket}
    )
  end

  def web_phoenix_live_view do
    websocket_handler(
      "/live",
      YourAppWeb.Endpoint,
      {Phoenix.LiveView.Socket, :websocket}
    )
  end

  def web_websocket do
    websocket_handler(
      "/cable/websocket",
      YourAppWeb.Endpoint,
      {YourAppWeb.UserSocket, websocket: true}
    )
  end

  def admin_phoenix_live_reload do
    websocket_handler(
      "/dev/admin/phoenix/live_reload/socket/websocket",
      YourAppAdminWeb.Endpoint,
      {LiveReloadSocket, :websocket}
    )
  end

  def admin_phoenix_live_view do
    websocket_handler(
      "/dev/admin/live",
      YourAppAdminWeb.Endpoint,
      {Phoenix.LiveView.Socket, :websocket}
    )
  end

  def admin_websocket do
    websocket_handler(
      "/dev/admin/websocket",
      YourAppAdminWeb.Endpoint,
      {YourAppAdminWeb.UserSocket, websocket: true}
    )
  end

  def master_proxy do
    {:_, Plug.Cowboy.Handler, {YourApp.Proxy.Plug, []}}
  end

  defp websocket_handler(path, endpoint, options) do
    {path, Phoenix.Endpoint.Cowboy2Handler, {endpoint, options}}
  end

  defp require_code_reloading?(otp_app, mod) do
    conf = Application.get_env(otp_app, mod)

    if conf[:code_reloader] do
      Phoenix.CodeReloader.Server.check_symlinks()
    end
  end
end

That's it! This proxy application handles routing for both your web and admin Phoenix applications, while properly supporting WebSockets, LiveView, and code reloading.

← Back to all posts

© Copyright 2023 Bitscorp