Is it possible to modify Router matches based on cookies like in Varnish?

Is it possible to modify Router matches based on cookies like in Varnish?

I know how to do it in controllers but what about the Router?

sub vcl_recv {
  if ( req.http.cookie ~ "cookie_name") {
    error 751 "http://site" + req.url;
  }
}
sub vcl_error {
  if (obj.status == 751) {
    /* Get new URL from the response */
    set obj.http.Location = obj.response;
    /* Set HTTP 301 for permanent redirect */
    set obj.status = 301;
    return(deliver);
  }
}

I think you could accomplish this with a custom plug.

An example is worth 1000 words, so here’s a plug I wrote that removes the trailing slash from URLs (the functionality is not comparable but it’s the first thing that comes to mind to provide an example):

Then you can add it into your router:

You can add custom plugs in a pipeline to cover all routes of a given type (e.g. all browser routes), or you could add them in a scope to only cover certain URLs (e.g. URLs for your users context).

Instead of the slash stuff, you would just check for your cookie or whatever. A function plug either returns the unmodified or modified conn based on whether or not the request meets the given criteria. And the cookies are available as part of that conn struct.

That is my understanding of how you would go about implementing the type of functionality you are looking for.

3 Likes

Yes, a Plug would do it just fine, you just check the conn cookies when it runs.

1 Like

The phoenix router compiles to plain function heads, which match on hostname and path only. You can use plugs to customize those values ahead of hitting the router, but you don‘t have alternative values to affect match finding by.

You could consider alternative router implementations, but e.g. LiveView kinda depends on you using a phoenix router, or at least some information providing subsets of it.

Am I missing something? Plug.Conn has cookies and req_cookies fields. And the fetch_cookies function.

One example (from memory) from an older project is:

defmodule OurAppWeb.Plugs.NightMode do
  import Plug.Conn

  def init(_opts), do: []

  def call(conn, _) do
    case conn.cookies["_our_app_night_mode"] do
      "true" -> assign(conn, :night_mode, true)
      _ -> conn
    end
  end
end

Sure, that works fine. But you cannot „route“ based on that information in the (phoenix) router.

Ah, you are right, just thought that maybe doing redirects et. al. in a Plug is also viable.