I have a bit of a bizar production setup, my apps run on a VPS that only has an IPv6 address,
to support IPv4 I am using a HaProxy with the PROXY_PROTOCOL.
Currently I am running NGINX to support this:
- listens on port 80, redirects to https on port 443
- listens on port 443 https reverse proxies to phoenix
- listens on port 1443 https with PROXY_PROTOCOL
This works but is a bother, I would prefer to just run Phoenix (with site_encrypt).
So the real question is, can I have Phoenix start listening on an additional?
This port would then have protocol_options: [ proxy_header: true ]
, because cowboy supports the PROXY_PROTOCOL.
I think the Cowboy2Adapter in Phoenix only allows one configuration for http and one for https per endpoint.
You could create a separate Endpoint for the proxy stuff (e.g. TestWeb.Endpoint
and TestWeb.ProxyEndpoint
) and start both of them with different configurations.
1 Like
As a quick and dirty option, I copied the code from the Cowboy2Adapter and allowed it to return multiple configurations, and that works.
Now I need to think how I can add the options I want to the other ports.
Changing the code to allow for multiple :http
and :https
keys works, but site_encrypt
apparently replaces the :https
key, so my additional ones get removed.
@doc false
def child_specs(endpoint, config) do
otp_app = Keyword.fetch!(config, :otp_app)
refs_and_specs =
for {scheme, port} <- [http: 4000, https: 4040],
opts when opts != false <- :proplists.get_all_values(scheme, config) do
port = :proplists.get_value(:port, opts, port)
unless port do
Logger.error(":port for #{scheme} config is nil, cannot start server")
raise "aborting due to nil port"
end
opts = [port: port_to_integer(port), otp_app: otp_app] ++ :proplists.delete(:port, opts)
child_spec(scheme, endpoint, opts)
end
{refs, child_specs} = Enum.unzip(refs_and_specs)
if drainer = refs != [] && Keyword.get(config, :drainer, []) do
child_specs ++ [{Plug.Cowboy.Drainer, Keyword.put_new(drainer, :refs, refs)}]
else
child_specs
end
end
Replacing the for loop with this works
for {scheme, default_port} <- [http: 4000, https: 4040],
opts = config[scheme],
port <- :proplists.get_all_values(:port, opts) do
{opts, port} =
cond do
is_list(port) ->
port_ = :proplists.get_value(:port, port, default_port)
opts_ = Keyword.merge(opts, port, fn _key, _v1, v2 -> v2 end)
{opts_, port_}
true ->
{opts, port}
end
This requires me to write the config likes this:
...
http: [ip: {127, 0 ,0 1}, port: 4000, port: [ port: 5000, protocol_options: [ proxy_header: true ]]],
https: [port: 4040, port: [ port: 5050, protocol_options: [ proxy_header: true ]]],
The options given in the port
keyword list will be merged in the original, overwriting any existing.