基于负载的 Phoenix 路由

Phoenix routing based on the payload

我正在 Elixir/Phoenix 中实现后端 API。出于安全原因,决定在 URL 上尽可能少地显示,以便将要执行的操作嵌入到 POST 请求的 JSON 有效负载中。例如,我们不会使用类似 REST 的请求 https://my.server.com/api/get_user/42,而是在 https://my.server.com/api/ 上使用以下负载发出 POST:

{
    "action" : "get_user",
    "params" : {
        "id" : 42
    }
}

到目前为止,我的路由器是这样的:

  scope "/api", AlaaarmWeb do
     pipe_through :api

     post "/", ApiController, :process
     match :*, "/*path", ApiController, :error_404
   end

我有一堆 process 函数在 params 地图上进行模式匹配:

  def process(conn, params = %{"action" => "get_user"}) do
    # ... get the user from the DB in resp, and sed back
    json(conn, resp)
  end

它工作正常,但代码不是很清楚:一个大文件,使用相同的控制器,ApiController,而有一个 UserApiController 来管理用户会更清晰, a ProductApiController 用于管理产品等..

所以,我想知道是否有一种方法可以根据有效负载的内容来选择要调用的模块和函数,而不仅仅是 URL。

不,我不认为 Phoenix.Router 可以根据负载进行路由。这必须在控制器中完成。

但是您仍然可以按照您的描述制作单独的控制器 files/modules,并且 use 它们都在“主”ApiController 中。

如果您已将结构定义为通过负载传递的“action”和“params”键值对,并且有许多请求需要过滤并根据路径委托给不同的控制器,那么您应该能够在端点中定义了这样的插头。 ex 插入前 YourApp.Router call

  plug Plug.Session, @session_options
  plug UrlFromPayloadAction, []
  plug WhosebugApiWeb.Router

这个插件会改变路径信息,所以我们修改的 url 会被路由器解析,我们可以定义控制器,比如 post "/get_user/:id", ApiUserController, :process 和其他基于定义的动作。

defmodule UrlFromPayloadAction do
        import Plug.Conn

        def init(default), do: default

        def call(%Plug.Conn{params: %{"action" => action,
                                         "params" => %{ "id" => id }
                                     }} = conn, _default) do
              conn = conn
                     |> assign(:original_path_info, conn.path_info)
                     |> assign(:original_request_path, conn.request_path)
               
              %Plug.Conn{conn | path_info: conn.path_info ++ [ "#{action}", "#{id}" ] , request_path: conn.request_path <> "/#{action}/#{id}" }
        end
        def call(conn, _default), do: conn
end

这更像是一种解决这个问题的可变方法,它可能违背像长生不老药这样的功能框架的一般哲学