Hi!
There is currently no easy way in Phoenix to retrieve the path of the route against which a request was matched. For example, if you define a get "/users/:id"
route and you receive a GET /users/1
request, you have no easy solution to retrieve that the route was /users/:id
. You’d only have access to conn.request_path
which would be something like /users/1
in such case.
Accessing the route can be really useful in the following cases:
-
logging
- logging your requests by adding aroute
metadata in order to filter quite easily by route. -
metrics
- tagging your request metrics with theroute
so that you could for example check the performance of your app per route. At my company, we are currently tagging our metrics by therequest_path
but it means that eachid
will create a different “metric/graph”. Given that our metrics aggregator pricing increases with the cardinality of the metrics, you can easily see how that could create some issues. -
rate_limiting
- creating a route-based rate limiting.
This issue has been brought up a few time on the forum (Get match path in Phoenix, Access matched Route from phoenix Conn) and since a similar functionality has been merged into Plug
(https://github.com/elixir-plug/plug/pull/630), I think it would be great to have such a feature in Phoenix
as well. Please let me know if I’m mistaken here.
I quickly experimented locally and I think the following change in the Router would be enough to solve this:
defp build_match({route, exprs}, known_pipelines) do
- %{pipe_through: pipe_through} = route
+ %{pipe_through: pipe_through, path: route_path} = route
%{
prepare: prepare,
dispatch: dispatch,
verb_match: verb_match,
path: path,
host: host
} = exprs
{pipe_name, pipe_definition, known_pipelines} =
case known_pipelines do
%{^pipe_through => name} ->
{name, :ok, known_pipelines}
%{} ->
name = :"__pipe_through#{map_size(known_pipelines)}__"
{name, build_pipes(name, pipe_through), Map.put(known_pipelines, pipe_through, name)}
end
quoted =
quote line: route.line do
unquote(pipe_definition)
@doc false
def __match_route__(var!(conn), unquote(verb_match), unquote(path), unquote(host)) do
+ var!(conn) = update_in var!(conn).private,
+ &Map.put(&1, :phoenix_route, unquote(route_path))
{unquote(prepare), &unquote(Macro.var(pipe_name, __MODULE__))/1, unquote(dispatch)}
end
end
{quoted, known_pipelines}
end
and then add a function to the Router
:
defmodule Phoenix.Router do
@doc """
Returns the path of the route that the request was matched to.
"""
@spec match_path(Plug.Conn.t) :: String.t | nil
def match_path(%Plug.Conn{} = conn) do
Map.fetch(conn.private, :phoenix_route)
end
end
path = Phoenix.Router.match_path(conn)
I’d be willing to open a PR for that but since I’m not too familiar with Phoenix
source code, I’d love to get feedback on whether you feel that change should go into Phoenix
and whether the change I suggested above makes sense or not.
Thanks!