Phoenix框架:如何动态配置全局路由上下文路径?
我明白你的需求啦——你想给整个Phoenix应用的API路由加上一个动态的根前缀/context,让原来的/api/test变成/context/api/test,但发现配置Endpoint的:url选项只是用来生成URL,并不会实际改变请求的匹配路径对吧?咱们可以从这两个方向来解决这个问题:
方案1:用动态作用域(Dynamic Scope)配置路由
Phoenix的路由支持直接引用配置值作为作用域前缀,这是最直接的方式。
步骤1:添加配置项
先在你的配置文件(比如config/config.exs或者对应环境的config/prod.exs)里加一个自定义配置,用来存根前缀:
config :your_app, YourAppWeb.Endpoint, root_context: "/context" # 你也可以从环境变量读取,比如 System.get_env("ROOT_CONTEXT") || "/context"
步骤2:修改router.ex的路由结构
在router.ex里,先从配置中读取这个前缀,再把所有路由包在这个动态作用域里:
defmodule YourAppWeb.Router do use YourAppWeb, :router # 编译时读取配置,默认空字符串避免配置缺失报错 @root_context Application.compile_env(:your_app, YourAppWeb.Endpoint)[:root_context] || "" # 外层套上动态根前缀的作用域 scope @root_context do scope "/api" do get "/test", SomeController, :test end # 其他需要加前缀的路由也可以放在这里面 end end
这样启动应用后,你的接口就会响应/context/api/test这个路径了。如果需要修改前缀,改完配置后重新编译应用就行。
方案2:用自定义Plug重写请求路径
如果需要运行时动态调整前缀(不用重启应用就能生效),可以写一个Plug来处理路径重写。
步骤1:创建自定义Plug
新建一个Plug文件lib/your_app_web/plugs/root_context_plug.ex:
defmodule YourAppWeb.Plugs.RootContextPlug do import Plug.Conn def init(opts), do: opts def call(conn, _opts) do # 运行时读取根前缀配置,也可以从其他地方获取 root_context = Application.get_env(:your_app, YourAppWeb.Endpoint)[:root_context] || "" if root_context != "" and String.starts_with?(conn.request_path, root_context) do # 去掉前缀,让后续路由能匹配到原来的/api/test路径 new_path = String.replace_prefix(conn.request_path, root_context, "") # 更新conn的请求路径和路径信息 %{conn | request_path: new_path, path_info: split_path(new_path)} else conn end end # 辅助函数:把路径拆成Phoenix需要的path_info格式 defp split_path(path) do path |> String.trim("/") |> String.split("/") |> Enum.reject(&(&1 == "")) end end
步骤2:把Plug加到Endpoint里
在endpoint.ex中,把这个Plug放在Router之前:
defmodule YourAppWeb.Endpoint do use Phoenix.Endpoint, otp_app: :your_app # ... 其他已有的Plug配置(比如Parsers、Session等) # 加在Router之前,先处理路径重写 plug YourAppWeb.Plugs.RootContextPlug plug YourAppWeb.Router end
这种方式下,你可以随时通过Application.put_env(:your_app, YourAppWeb.Endpoint, root_context: "/new_context")来修改前缀,不需要重启应用就能生效。
额外注意点
不管用哪种方案,如果你需要在代码里生成正确的URL(比如控制器里用Routes.test_path(conn, :test)),记得同步配置Endpoint的:url选项里的:path参数,这样生成的URL才会带上前缀:
config :your_app, YourAppWeb.Endpoint, url: [host: "your-domain.com", path: "/context"], root_context: "/context"
内容的提问来源于stack exchange,提问作者Oliver

