Phoenix umbrella proxy application.
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.