rogerweb

rogerweb

Hi,

I’m running a Phoenix application behind a AWS ALB (Application Load Balancer), which routes requests to different applications based on the path in the requested URL.

Requests that start with /myapp are routed to my application.

The problem is that ALB doesn’t support stripping this /myapp prefix from the actual URL that goes to Phoenix (differently from nginx, haproxy, etc) and I wouldn’t like to add this prefix (a deployment configuration) to all my routes as it can only be done in compilation time (at least without having to create a router myself which I think would be overkill; I might be wrong though).

In an attempt to overcome this issue, I’ve created a simple plug that runs before everything and removes the prefix from connection’s path_info. For instance, it turns

["myapp", "assets", "app-978a7188b69b3752fe7c58e50c7e5571.js"]

into

["assets", "app-978a7188b69b3752fe7c58e50c7e5571.js"]

Also, I configure my Endpoint to use this prefix as the path so URLs generated by the application include the prefix back and are properly routed by ALB.

# runtime.exs
config :myapp, MyAppWeb.Endpoint,
  url: [
    host: get_env("PHX_HOST", "localhost"),
    port: get_env("PHX_PORT", 4000, :int),
    path: get_env("PHX_PATH", "/") # get_env is just a helper func
  ]

It works fine for assets and regular routes, but not for sockets and anything that uses sockets like live reloading and the dashboard. Example:

17:39:09.218 [info] GET /myapp/socket/websocket
17:39:09.280 [debug] ** (Phoenix.Router.NoRouteError) no route found for GET /socket/websocket (MyAppWeb.Router)
    (myapp 1.0.0-alpha.19) lib/phoenix/router.ex:405: MyAppWeb.Router.call/2
    (myapp 1.0.0-alpha.19) lib/myapp_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2 

The plug feels like a hack and it didn’t solve my problem.

Have you guys had to deal with a similar deployment? How did you manage?

Cheers!

Showing Posts 1 to 10

Exadra37

Exadra37

If your app deployment target for production is to be behind that aws load balancer why do you want to overcome the issue in your app? Why not keeping it simple and just add the prefix?

Doesn’t the load balancer support to invoke a lambda function?

Or if your domain is with Route53 you can try to use forwarding rules:

rogerweb

rogerweb OP

Thanks @Exadra37 for taking the time to read my post, I really appreciate it.

I don’t :slight_smile: I just don’t know how.

Add the prefix where? To my application’s router scope’s ?

It does. Not sure how it would help specially because my app uses websockets.

It is not but I have no problems moving to it if it solves the issue. I’m not sure though as I think DNS usually doesn’t get involved with URL paths, right?

Exadra37

Exadra37

Yes, and in the sockets configuration on the endpoint module.

Read the link I shared.

rogerweb

rogerweb OP

Because the prefix is a deployment-specific configuration. I should not need to re-compile the application to change it.

Also, it breaks Phoenix’s LiveDashboard (it adds the prefix twice to the URL) and live reload (dev only):

13:21:42.756 [info] GET /myapp/phoenix/live_reload/frame
13:21:42.843 [debug] ** (Phoenix.Router.NoRouteError) no route found for GET /myapp/phoenix/live_reload/frame (MyAppWeb.Router)
    (myapp 1.0.0-alpha.19) lib/phoenix/router.ex:405: MyAppWeb.Router.call/2
    (myapp 1.0.0-alpha.19) lib/myapp_web/endpoint.ex:1: MyAppWeb.Endpoint.plug_builder_call/2

I will. Thanks again.

Exadra37

Exadra37

You didn’t mention that was deployment specific. But you may try to read the prefix from the runtime.exs configuration, thus no need to recompile the app, only needs to setup an env var with the prefix.

Where is it in the logs?

Please add your router file to your first post so we can better understand what you are doing.

rogerweb

rogerweb OP

That’s what I do for all my runtime configuration and that was my first attempt before starting this thread, but I soon realized that the scope in Phoenix’s router is evaluated at compilation time. So I can’t use things like Application.fetch_env!(:myapp, :prefix).

If I do like:

  scope Application.fetch_env!(:myapp, :prefix), MyAppWeb do
    pipe_through :browser
    get "/users", UserController, :index
  end

I correctly get a compilation error:

== Compilation error in file lib/myapp_web/router.ex ==
** (ArgumentError) could not fetch application environment :prefix for 
application :myapp because the application was not loaded nor configured

I couldn’t figure out how to edit my original post so here it goes.

Router with all the routes statically prefixed with the “/myapp”:

defmodule MyAppWeb.Router do
    use MyAppWeb, :router
    import Phoenix.LiveDashboard.Router

    pipeline :browser do
      plug :accepts, ["html"]
      plug :fetch_session
      plug :fetch_live_flash
      plug :put_root_layout, {MyAppWeb.LayoutView, :root}
      plug :protect_from_forgery
      plug :put_secure_browser_headers
    end
  
    pipeline :basic_auth do
      plug :basic_auth_plug
    end
  
    pipeline :api do
      plug :accepts, ["json"]
    end
  
    scope "/myapp", MyAppWeb do
      pipe_through :browser
  
      get "/", PageController, :index
    end
  
    scope "/myapp/dashboard" do
      pipe_through [:browser, :basic_auth]
      live_dashboard "/", metrics: MyAppWeb.Telemetry
    end
  
    defp basic_auth_plug(conn, _opts) do
      (...)
    end
  end
  

You didn’t ask but I guess the Endpoint is also relevant as the sockets are defined there:

Endpoint with all the sockets statically prefixed with the “/myapp”:

defmodule MyAppWeb.Endpoint do
    use Phoenix.Endpoint, otp_app: :myapp
  
    @session_options [
      store: :cookie,
      key: "_myapp_key",
      signing_salt: "*********"
    ]
  
    socket "/myapp/socket", MyAppWeb.UserSocket,
      websocket: true,
      longpoll: false
  
    socket "/myapp/live", Phoenix.LiveView.Socket,
      websocket: [connect_info: [session: @session_options]]
  
    plug Plug.Static,
      at: "/myapp",
      from: :myapp,
      gzip: false,
      only: ~w(assets fonts images favicon.ico robots.txt)
  
    if code_reloading? do
      socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket  # note 1
      plug Phoenix.LiveReloader
      plug Phoenix.CodeReloader
    end
  
    plug Phoenix.LiveDashboard.RequestLogger,
      param_key: "request_logger",
      cookie_key: "request_logger"
  
    plug Plug.RequestId, http_header: "x-correlation-id"
    plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
  
    plug Plug.Parsers,
      parsers: [:urlencoded, :multipart, :json],
      pass: ["*/*"],
      json_decoder: Phoenix.json_library()
  
    plug Plug.MethodOverride
    plug Plug.Head
    plug Plug.Session, @session_options
    plug MyAppWeb.Router
  end

Note 1: I tried with socket "/myapp/phoenix/live_reload/socket" too but it doesn’t work either.

Exadra37

Exadra37

I though that could be the case after I suggested it.

Sorry for the misleading tip but my Elixir experience is limited to toy apps, thus I am not yet very fluent on it yet.

after some time elapses we cannot edit it any-more.

You also need to update assets/js/app.js:

let liveSocket = new LiveSocket("/myapp/live", Socket, {params: {_csrf_token: csrfToken}})
derek-zhou

derek-zhou

Don’t do that. all you need to do are:

  • change the url key of your Endpoint config with a non-root url.
  • change the js for the lv or socket url if you use channel or liveview. I usually add a data attribute in my <body> tag to pass the correct url (derived from above) to the client side
rogerweb

rogerweb OP

Hi Derek, thanks for joining the conversation.

When you say “Don’t do that” what are you referring to?

I understand your suggestion is the standard approach when the load balancer can re-write the URL, which unfortunately is not the case:

So the request reaches the router with the prefix, hence it is not found.

derek-zhou

derek-zhou

With url config in place, every request reaching plug would need to have the prefix to be processed. So, you need to add the prefix for the socket connect, as the standard js from a phx.new don’t have it. This is my second point.

I am not sure about AWS ALB, but this is how multiple phoenix apps work behind a single nginx reverse proxy.

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
matt-savvy
Anyone here using Honeybadger? My Honeybadger account is being overwhelmed with noise from some bots. Seeing a lot of Bandit.HTTPError...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews