Cowboy 2.5 reverse proxy with websockets

Hey folks, I’m deploying a Phoenix 1.4 umbrella app with multiple web endpoints to Heroku, so I need a single endpoint that will reverse proxy my web_apps and their websockets.

The following code is working properly to dispatch plain HTTP traffic between my apps, but Cowboy crashes when loading live_reload websockets.

I wrote this code from cowboy 1.0 examples, and I guess the way I setup dispatch has changed. But I’m struggling at reading cowboy 2.5 documentation … :confused:

My proxy application :

defmodule MyProxy.Application do

  alias Plug.Cowboy

  use Application

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

    port = (System.get_env("PORT") || "5000") |> String.to_integer

    cowboy = Cowboy.child_spec(
      scheme: :http,
      plug: MyProxy.Plug,
      options: [
        port: port,
        dispatch: [{:_, [
          my_app_web_live_reload(),
          my_other_app_live_reload(),
          proxy()
        ]}]
      ]
    )

    children = [cowboy]
    opts = [strategy: :one_for_one, name: MyProxy.Supervisor]
    Supervisor.start_link(children, opts)
  end

  defp my_app_web_live_reload do
    {
      "/my_app/live_reload/socket/websocket",
      Phoenix.Endpoint.CowboyWebSocket,
      {
        Phoenix.Transports.WebSocket,
        {MyApp.Endpoint, Phoenix.LiveReloader.Socket, :websocket}
      }
    }
  end

  defp my_other_app_live_reload do
    {
      "/my_other_app/live_reload/socket/websocket",
      Phoenix.Endpoint.CowboyWebSocket,
      {
        Phoenix.Transports.WebSocket,
        {MyOtherApp.Endpoint, Phoenix.LiveReloader.Socket, :websocket}
      }
    }
  end

  defp proxy do
    {:_, Cowboy.Handler, {MyProxy.Plug, []}}
  end

end

and the proxy plug:

defmodule MyProxy.Plug do

  def init(options) do
    options
  end

  def call(conn, _opts) do
    if conn.request_path =~ ~r{/my_app} do
      MyApp.Endpoint.call(conn, [])
    else
      MyOtherApp.Endpoint.call(conn, [])
    end
  end

end

The cowboy error

[error] Ranch protocol #PID<0.1043.0> of listener MyProxy.Plug.HTTP (connection #PID<0.1042.0>, stre
am id 1) terminated
** (exit) :undef
[error] :gen_event handler :error_logger_lager_h installed in :error_logger terminating
** (CaseClauseError) no case clause matching: [MyProxy.Plug.HTTP, #PID<0.1042.0>, 1, #PID<0.1043.0>,
 :undef, [{Phoenix.Endpoint.CowboyWebSocket, :init, [%{bindings: %{}, body_length: 0, cert: :undefin
ed, has_body: false, headers: %{"accept-encoding" => "gzip, deflate, br", "accept-language" => "fr-F
R,fr;q=0.9,en-US;q=0.8,en;q=0.7", "cache-control" => "no-cache", "connection" => "Upgrade" , "host" 
=> "localhost:5000", "origin" => "http://localhost:5000", "pragma" => "no-cache", "sec-websocket-ext
ensions" => "permessage-deflate; client_max_window_bits", "sec-websocket-key" => "LiihXA7p3GimivqsYH
hjqg==", "sec-websocket-version" => "13", "upgrade" => "websocket", "user-agent" => "Mozilla/5.0 (Ma
cintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/
537.36"}, host: "localhost", host_info: :undefined, method: "GET", path: "/my_app/live_reload/socket
/websocket", path_info: :undefined, peer: {{127, 0, 0, 1}, 54652}, pid: #PID<0.1042.0>, port: 5000, 
qs: "vsn=2.0.0", ref: MyProxy.Plug.HTTP, scheme: "http", sock: {{127, 0, 0, 1}, 5000}, streamid: 1, 
version: :"HTTP/1.1"}, {Phoenix.Transports.WebSocket, {MyApp.Endpoint, Phoenix.LiveReloader.Socket, 
:websocket}}], []}, {:cowboy_handler, :execute, 2, [file: 'my_app/deps/cowboy/src/cowboy_handler.erl
', line: 41]}, {:cowboy_stream_h, :execute, 3, [file: 'my_app/deps/cowboy/src/cowboy_stream_h.erl', 
line: 293]}, {:cowboy_stream_h, :request_process, 3, [file: 'my_app/deps/cowboy/src/cowboy_stream_h.
erl', line: 271]}, {:proc_lib, :init_p_do_apply, 3, [file: 'proc_lib.erl', line: 247]}]]
1 Like

I finally figured it by myself : the Phoenix.Endpoint.CowboyWebSocket I was using is meant to be used with cowboy 1.x. For cowboy 2.x, I had to use Phoenix.Endpoint.Cowboy2Handler like this :

defp my_app_web_live_reload do
  {
    "/my_app/live_reload/socket/websocket",
    Phoenix.Endpoint.Cowboy2Handler,,
    {
      MyApp.Endpoint,
      {Phoenix.LiveReloader.Socket, :websocket}
    }
  }
end
2 Likes