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

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
water
I’m aware of 2 such projects, are there others? git: GitHub - team-alembic/realworld: A fullstack Phoenix LiveView application with back...
New
itzmidinesh
Hi all, I’m working on a Phoenix project where I need to make external HTTP requests and I’m trying to choose between Req and HTTPoison....
New
pragdave
How do I tell Hologram not to escape HTML when substituting into templates? {Textile.render(@product.long_description)} escapes the HTM...
New
leoschweizer
Hi, we run different LLM models across a cluster, with each node loading a configurable subset. Model-specific queues let nodes consume ...
New
Stef-Verniers
Hello I’ve been coding in Elixir for almost two years now because of my current job where we implemented Elixir in our application. But ...
New
csmr
Hi all, I’m new here and would like to hear peoples views on my Elixir/Phoenix based game-engine! :smiley: Mutonex gamedesign was finish...
New

Other Trending Topics Top

dominicletz
Hey there, I haven’t seen a list of useful Elixir specific skills for the popular AI tools yet so I’m starting one here with my first. ...
New
raa-ziq
Hi everyone :waving_hand: I wanted to share something I’ve been building — NowBlind, a random chat app designed to help people discover ...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
kip
Localize is the next generation localisation library for Elixir. Think of it as ex_cldr version 3.0. The first version will be released ...
New
OndrejValenta
(I just needed to vent somewhere and LinkedIn is full of hope, or hype, I’m not sure which exactly) AI dream has many faces, but general...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews