Umbrella apps on Heroku, channels not connecting

I’m running multiple apps on Heroku, and use a proxy app to reroute the traffic to the appropriate app;
this because Heroku allows only one port to the interwebz.

It’s working perfectly except, the phoenix channels. :frowning:
I tried the following change because the web sockets connect always thows a 404.

defmodule Proxy.Plug do
  require Logger

  def init(options) do
    options
  end

  def call(conn, _opts) do
    cond do
      conn.request_path =~ ~r{^/webapi} ->
        WebApi.Endpoint.call(conn, [])
      conn.scheme == :wss -> #added this line
        WebApi.Endpoint.call(conn, [])
      true ->
        Web.Endpoint.call(conn, [])
    end
  end
end

How do I redirect the wss connections to the channels, are they going trough WebApi.Endpoint?

This might not be very helpful but if your proxy app is just a simple plug/cowboy combo it may be because the proxy isn’t capable of handling websocket communication properly. Websockets with only plug haven’t seemed to materialize yet (I could be wrong about that). Phoenix seems to have put in a lot of effort on top of Plug to make websockets work.

I don’t really know the answer but this is my guess.

You could try switching the proxy from a simple Plug app to Phoenix. I’ve never tried that but it could be worth a shot.

This also popped up with a simple Google search. It’s pretty recent.

found a workaround, moving away from Heroku :slight_smile:

1 Like

Why did you need to move away from Heroku?

For me it was only temporary, the plan was to move to a VPS.
So I didn’t want to put much effort in it to the proxy’s running etc.

Are you replacing the proxy with a load balancer that supports websockets or something?

Yes, running good old nginx now as reversed proxy

I’m having a similar issue (I believe), with accessing channels through an umbrella proxy. For instance, if my umbrella is run from :4000, I can successfully access the channel app directly (eg. :4010). But if I try to connect through :4000, the router is called and returns a 404 (no route found for GET /socket/websocket). I’m still new to Elixir and Phoenix, so I’m wondering if this is simply a mistake in how I’m using Cowboy.child_spec, or maybe my endpoint conditionals. This app is currently being run on Heroku, but without the use of channels. I’d like to stick with Heroku if I can, but that will require sending all calls (http and ws) through one port.

@jos were you able to get this working locally, with all requests proxied?

Hi, @bjunc locally I didn’t get it working either.
i routed local the channels to the specific port instead of going trough the proxy.

I got this working after investigating the dispatch option (thanks @anthonator), which ignores the Plug provided, and allows you conditionally init endpoints based on the route (not to confused with possible conditional logic in the Plug, as that turned out to be too late).

You can piece together how to do this by looking at the source code for Plug.Adapters.Cowboy and Phoenix.Endpoint.CowboyHandler. Specifically:

{"/socket/websocket", Phoenix.Endpoint.CowboyWebSocket,
  {Phoenix.Transports.WebSocket,
    {MyApp.Endpoint, MyApp.UserSocket, :websocket}}}

With that little nugget of logic, I was able to handle all requests through the same port. Non-socket requests go to my proxy plug, and calls the respective endpoints.

I have this currently working in Heroku :slight_smile:

Hey @bjunc and @anthonator thanks for leaving a trail to follow.

For anyone who may find this a little confusing, here is a gist which hopefully makes it a touch more obvious.

2 Likes

That’s basically where I netted out as well. I didn’t break apart the dispatch as you did, but your approach looks a bit cleaner.

I’m still new to Elixir, but I’m under the impression that the Cowboy.child_spec plug isn’t called when dispatch is set, so I set the plug to nil as to not cause confusion.

cowboy = Plug.Adapters.Cowboy.child_spec(:http, nil, [], [port: port, dispatch: dispatch()])

In case it’s helpful to anyone, I created a library to help make this easier. It supports channels/websockets.

5 Likes