Skip to content

Instantly share code, notes, and snippets.

@fnicastri
Forked from cblavier/application.ex
Created April 30, 2023 08:33
Show Gist options
  • Save fnicastri/ff2f33085b010d531860c43ec31c57c2 to your computer and use it in GitHub Desktop.
Save fnicastri/ff2f33085b010d531860c43ec31c57c2 to your computer and use it in GitHub Desktop.
Cowboy 2.5 proxy, used to bind a single port (on Heroku) for umbrella Phoenix applications. It supports HTTPS and websockets properly.
defmodule MasterProxy.Application do
alias MyApp.Endpoint, as: MyAppEndpoint
alias MyApp.UserSocket, as: MyAppUserSocket
alias MyOtherApp.Endpoint, as: MyOtherAppEndpoint
alias MyOtherApp.UserSocket, as: MyOtherAppUserSocket
alias Phoenix.LiveReloader.Socket, as: LiveReloadSocket
alias Plug.Cowboy
use Application
require Logger
@port Application.get_env(:master_proxy, :port)
def start(_type, _args) do
{:ok, pid} = in_phoenix?() |> children |> run_application()
Logger.info("successfully started master_proxy on port #{to_port(@port)}")
{:ok, pid}
end
defp run_application(children) do
import Supervisor.Spec, warn: false
Supervisor.start_link(
children,
[strategy: :one_for_one, name: MasterProxy.Supervisor]
)
end
defp children(_start_cowboy = false), do: []
defp children(_start_cowboy = true) do
[
Cowboy.child_spec(
plug: nil, # since we're using manual dispatch, plug is ignored
scheme: :http,
options: [
port: to_port(@port),
dispatch: [{:_, [
websocket_handler("/my_app/live_reload/socket/websocket", MyAppEndpoint, {LiveReloadSocket, :websocket}),
websocket_handler("/my_other_app/live_reload/socket/websocket", MyOtherAppEndpoint, {LiveReloadSocket, :websocket}),
websocket_handler("/my_app/socket/websocket", MyAppEndpoint, {MyAppUserSocket, websocket: true}),
websocket_handler("/my_other_app/socket/websocket", MyOtherAppEndpoint, {MyOtherAppUserSocket, websocket: true}),
{:_, Cowboy.Handler, {MasterProxy.Plug, []}}
]}]
]
)
]
end
defp websocket_handler(path, endpoint, options) do
{path, Phoenix.Endpoint.Cowboy2Handler, {endpoint, options}}
end
# we only want the proxy to start when phoenix is started as well
# (not in iex or tests)
defp in_phoenix? do
Application.get_env(:phoenix, :serve_endpoints)
end
defp to_port(nil) do
Logger.error "Server can't start because :port in config is nil, please use a valid port number"
exit(:shutdown)
end
defp to_port(binary) when is_binary(binary), do: String.to_integer(binary)
defp to_port(integer) when is_integer(integer), do: integer
defp to_port({:system, env_var}), do: to_port(System.get_env(env_var))
end
use Mix.Config
config :master_proxy, port: 5000
import_config "#{Mix.env}.exs"
defmodule MasterProxy.Plug do
def init(options) do
options
end
def call(conn, _opts) do
if conn.request_path =~ ~r{^/other_app} do
MyOtherApp.Endpoint.call(conn, [])
else
MyApp.Endpoint.call(conn, [])
end
end
end
use Mix.Config
config :master_proxy,
port: {:system, "PORT"}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment