查看源代码 Phoenix.Router (Phoenix v1.7.14)

定义 Phoenix 路由器。

路由器提供了一组宏,用于生成将请求分发到特定控制器和操作的路由。这些宏以 HTTP 动词命名。例如

defmodule MyAppWeb.Router do
  use Phoenix.Router

  get "/pages/:page", PageController, :show
end

上面的 get/3 宏接收对 /pages/hello 的请求,并将它分发到 PageControllershow 操作,并将 %{"page" => "hello"} 放入 params 中。

Phoenix 的路由器非常高效,因为它依靠 Elixir 模式匹配来匹配路由和服务请求。

路由

get/3post/3put/3 以及其他以 HTTP 动词命名的宏用于创建路由。

路由

get "/pages", PageController, :index

匹配对 /pagesGET 请求,并将它分发到 PageController 中的 index 操作。

get "/pages/:page", PageController, :show

匹配 /pages/hello,并将它分发到 show 操作,并将 %{"page" => "hello"} 放入 params 中。

defmodule PageController do
  def show(conn, params) do
    # %{"page" => "hello"} == params
  end
end

可以匹配部分和多个片段。例如

get "/api/v:version/pages/:id", PageController, :show

匹配 /api/v1/pages/2,并将 %{"version" => "1", "id" => "2"} 放入 params 中。只有片段的尾部可以被捕获。

路由从上到下匹配。这里的第二个路由

get "/pages/:page", PageController, :show
get "/pages/hello", PageController, :hello

永远不会匹配 /pages/hello,因为 /pages/:page 首先匹配它。

路由可以使用类似通配符的模式来匹配尾随片段。

get "/pages/*page", PageController, :show

匹配 /pages/hello/world,并将通配符片段放入 params["page"] 中。

GET /pages/hello/world
%{"page" => ["hello", "world"]} = params

通配符不能有前缀或后缀,但可以与变量混合使用

get "/pages/he:page/*rest", PageController, :show

匹配

GET /pages/hello
%{"page" => "llo", "rest" => []} = params

GET /pages/hey/there/world
%{"page" => "y", "rest" => ["there" "world"]} = params

为什么使用宏?

Phoenix 尽力减少宏的使用。然而,您可能已经注意到,Phoenix.Router 严重依赖于宏。为什么呢?

我们使用 getpostputdelete 来定义您的路由。我们使用宏有两种目的

  • 它们定义了路由引擎,该引擎用于每个请求,以选择将请求分发到哪个控制器。由于使用了宏,Phoenix 将所有路由编译到一个带有模式匹配规则的单个 case 语句中,该语句由 Erlang VM 进行了高度优化

  • 对于您定义的每个路由,我们还定义元数据以实现 Phoenix.VerifiedRoutes。正如我们很快将要了解的,已验证的路由允许我们将任何路由引用为一个普通字符串,不同的是它经过编译器的验证,确保它是有效的(这使得将损坏的链接、表单、邮件等发送到生产环境变得更加困难)

换句话说,路由器依赖于宏来构建更快、更安全的应用程序。还要记住,Elixir 中的宏只是编译时宏,这在代码编译后提供了极大的稳定性。Phoenix 还通过 mix phx.routes 为所有定义的路由提供自省。

生成路由

有关在应用程序中生成路由的信息,请参阅 Phoenix.VerifiedRoutes 文档,了解基于 ~p 的路由生成,这是使用编译时验证生成路由路径和 URL 的首选方法。

Phoenix 还支持生成函数帮助程序,这是 Phoenix v1.6 及更早版本中的默认机制。我们将在下面进行探讨。

帮助程序(已弃用)

默认情况下,Phoenix 会在您的路由器中生成一个名为 Helpers 的模块,该模块包含命名帮助程序,帮助开发人员生成和保持其路由的最新状态。可以通过将 helpers: false 传递给 use Phoenix.Router 来禁用帮助程序。

帮助程序会根据控制器名称自动生成。例如,路由

get "/pages/:page", PageController, :show

将生成以下命名帮助程序

MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello")
"/pages/hello"

MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, "hello", some: "query")
"/pages/hello?some=query"

MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello")
"http://example.com/pages/hello"

MyAppWeb.Router.Helpers.page_url(conn_or_endpoint, :show, "hello", some: "query")
"http://example.com/pages/hello?some=query"

如果路由包含类似通配符的模式,则必须将这些模式的参数作为列表提供

MyAppWeb.Router.Helpers.page_path(conn_or_endpoint, :show, ["hello", "world"])
"/pages/hello/world"

在命名 URL 帮助程序中生成的 URL 基于 :url:http:https 的配置。但是,如果您需要手动控制 URL 生成,URL 帮助程序也允许您传入一个 URI 结构体

uri = %URI{scheme: "https", host: "other.example.com"}
MyAppWeb.Router.Helpers.page_url(uri, :show, "hello")
"https://other.example.com/pages/hello"

可以使用 :as 选项自定义命名帮助程序。给定路由

get "/pages/:page", PageController, :show, as: :special_page

命名帮助程序将是

MyAppWeb.Router.Helpers.special_page_path(conn, :show, "hello")
"/pages/hello"

作用域和资源

在 Phoenix 应用程序中,将所有路由命名空间在应用程序作用域下是很常见的

scope "/", MyAppWeb do
  get "/pages/:id", PageController, :show
end

上面的路由将分发到 MyAppWeb.PageController。这种语法不仅对开发人员来说很方便,因为我们不必在所有路由上重复 MyAppWeb. 前缀,而且它还允许 Phoenix 减少对 Elixir 编译器的压力。如果我们改为编写

get "/pages/:id", MyAppWeb.PageController, :show

Elixir 编译器将推断路由器直接依赖于 MyAppWeb.PageController,但事实并非如此。通过使用作用域,Phoenix 可以正确地提示 Elixir 编译器控制器不是路由器的实际依赖项。这将提供更快的编译时间。

作用域允许我们在任何路径上,甚至在帮助程序名称上进行作用域

scope "/v1", MyAppWeb, host: "api." do
  get "/pages/:id", PageController, :show
end

例如,上面的路由将在路径 "/api/v1/pages/1" 上匹配,命名路由将是 api_v1_page_path,这与提供给 scope/2 选项的值相符。

与所有路径一样,您可以定义动态片段,这些片段将在控制器中作为参数应用

scope "/api/:version", MyAppWeb do
  get "/pages/:id", PageController, :show
end

例如,上面的路由将在路径 "/api/v1/pages/1" 上匹配,并且在控制器中,params 参数将包含一个键为 :version、值为 "v1" 的映射。

Phoenix 还提供了一个 resources/4 宏,允许开发人员为给定资源生成“RESTful”路由

defmodule MyAppWeb.Router do
  use Phoenix.Router

  resources "/pages", PageController, only: [:show]
  resources "/users", UserController, except: [:delete]
end

最后,Phoenix 附带了一个 mix phx.routes 任务,它可以很好地格式化给定路由器中的所有路由。我们可以使用它来验证上面路由器中包含的所有路由

$ mix phx.routes
page_path  GET    /pages/:id       PageController.show/2
user_path  GET    /users           UserController.index/2
user_path  GET    /users/:id/edit  UserController.edit/2
user_path  GET    /users/new       UserController.new/2
user_path  GET    /users/:id       UserController.show/2
user_path  POST   /users           UserController.create/2
user_path  PATCH  /users/:id       UserController.update/2
           PUT    /users/:id       UserController.update/2

还可以将路由器作为参数显式传递给任务

$ mix phx.routes MyAppWeb.Router

有关更多信息,请查看 scope/2resources/4

管道和插头

当请求到达 Phoenix 路由器时,它会通过管道进行一系列转换,直到请求被分发到所需的路由。

此类转换是通过插头定义的,如 Plug 规范中所定义的那样。一旦管道被定义,它就可以通过每个作用域进行管道化。

例如

defmodule MyAppWeb.Router do
  use Phoenix.Router

  pipeline :browser do
    plug :fetch_session
    plug :accepts, ["html"]
  end

  scope "/" do
    pipe_through :browser

    # browser related routes and resources
  end
end

Phoenix.RouterPlug.ConnPhoenix.Controller 中导入函数,以帮助定义插头。在上面的示例中,fetch_session/2 来自 Plug.Conn,而 accepts/2 来自 Phoenix.Controller

请注意,路由器管道只在找到路由后才会调用。如果找不到匹配项,则不会调用任何插头。

如何组织我的路由?

在 Phoenix 中,我们倾向于定义几个管道,这些管道提供特定的功能。例如,上面的 pipeline :browser 包含适用于所有旨在通过浏览器访问的路由的插头。类似地,如果您还提供 :api 请求,您将拥有一个单独的 :api 管道来验证特定于您的端点的信息。

也许更重要的是,定义特定于身份验证和授权的管道也很常见。例如,您可能有一个管道要求所有用户都经过身份验证。另一个管道可能强制只有管理员用户才能访问某些路由。由于路由从上到下匹配,因此建议将经过身份验证/授权的路由放置在限制较少的路由之前,以确保它们首先匹配。

一旦您的管道被定义,您就在所需的作用域中重复使用这些管道,将您的路由分组到它们的管道周围。例如,假设您正在构建一个博客。任何人都可以阅读帖子,但只有经过身份验证的用户才能创建帖子。您的路由可能如下所示

pipeline :browser do
  plug :fetch_session
  plug :accepts, ["html"]
end

pipeline :auth do
  plug :ensure_authenticated
end

scope "/" do
  pipe_through [:browser, :auth]

  get "/posts/new", PostController, :new
  post "/posts", PostController, :create
end

scope "/" do
  pipe_through [:browser]

  get "/posts", PostController, :index
  get "/posts/:id", PostController, :show
end

请注意上面的路由如何分布在不同的作用域中。虽然这种分离一开始可能会令人困惑,但它有一个很大的好处:非常容易检查您的路由,并查看例如哪些路由需要身份验证,哪些路由不需要。这有助于审核并确保您的路由具有适当的作用域。

您可以创建任意数量的作用域。由于管道可以在作用域之间重复使用,因此它们有助于封装公共功能,并且您可以在定义的每个作用域上按需组合它们。

摘要

反射

返回请求的编译时路由信息和运行时路径参数。

返回带有当前作用域的别名前缀的完整别名。

返回带有当前作用域的路径前缀的完整路径。

函数

生成一个路由,以处理对给定路径的连接请求。

生成一个路由,以处理对给定路径的删除请求。

将给定路径上的请求转发到插头。

生成一个路由,以处理对给定路径的 get 请求。

生成一个路由,以处理对给定路径的 head 请求。

根据任意 HTTP 方法生成路由匹配。

生成一个路由来处理对给定路径的 options 请求。

生成一个路由来处理对给定路径的 patch 请求。

定义一个连接将要经过的插件(和管道)列表。

定义一个插件管道。

在管道内定义一个插件。

生成一个路由来处理对给定路径的 post 请求。

生成一个路由来处理对给定路径的 put 请求。

为资源定义“RESTful”路由。

从给定的路由器返回所有路由信息。

定义一个范围,其中路由可以嵌套。

定义一个具有给定路径的范围。

定义一个具有给定路径和别名的范围。

生成一个路由来处理对给定路径的 trace 请求。

反射

链接到此函数

route_info(router, method, path, host)

查看源代码

返回请求的编译时路由信息和运行时路径参数。

path 可以是字符串,也可以是 path_info 段。

返回一个包含以下键的元数据映射

  • :log - 配置的日志级别。例如 :debug
  • :path_params - 运行时路径参数的映射
  • :pipe_through - 路由范围的管道列表,例如 [:browser]
  • :plug - 要将路由分派到的插件,例如 AppWeb.PostController
  • :plug_opts - 调用插件时要传递的选项,例如::index
  • :route - 字符串路由模式,例如 "/posts/:id"

示例

iex> Phoenix.Router.route_info(AppWeb.Router, "GET", "/posts/123", "myhost")
%{
  log: :debug,
  path_params: %{"id" => "123"},
  pipe_through: [:browser],
  plug: AppWeb.PostController,
  plug_opts: :show,
  route: "/posts/:id",
}

iex> Phoenix.Router.route_info(MyRouter, "GET", "/not-exists", "myhost")
:error
链接到此函数

scoped_alias(router_module, alias)

查看源代码

返回带有当前作用域的别名前缀的完整别名。

对于将相同的简写别名处理应用于除路由定义中第二个参数以外的其他值很有用。

示例

scope "/", MyPrefix do
  get "/", ProxyPlug, controller: scoped_alias(__MODULE__, MyController)
end
链接到此函数

scoped_path(router_module, path)

查看源代码

返回带有当前作用域的路径前缀的完整路径。

函数

链接到此宏

connect(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由,以处理对给定路径的连接请求。

connect("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

delete(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由,以处理对给定路径的删除请求。

delete("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

forward(path, plug, plug_opts \\ [], router_opts \\ [])

查看源代码 (宏)

将给定路径上的请求转发到插头。

所有与转发前缀匹配的路径都将发送到转发的插件。这对于在应用程序之间共享路由器,甚至将大型路由器拆分为更小的路由器很有用。路由器管道将在转发连接之前调用。

但是,我们不建议转发到另一个端点。原因是您的应用程序和转发端点定义的插件将被调用两次,这可能会导致错误。

示例

scope "/", MyApp do
  pipe_through [:browser, :admin]

  forward "/admin", SomeLib.AdminDashboard
  forward "/api", ApiRouter
end
链接到此宏

get(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由,以处理对给定路径的 get 请求。

get("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

head(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由,以处理对给定路径的 head 请求。

head("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

match(verb, path, plug, plug_opts, options \\ [])

查看源代码 (宏)

根据任意 HTTP 方法生成路由匹配。

对于定义不在内置宏中的路由很有用。

通配符动词 :* 也可以用于匹配所有 HTTP 方法。

选项

  • :as - 配置命名助手。如果为 nil,则不生成助手。在专门使用已验证路由时无效
  • :alias - 配置是否应将作用域别名应用于路由。默认为 true,如果为 false,则禁用作用域。
  • :log - 用于记录路由分派的级别,可以设置为 false。默认为 :debug。路由分派包含有关如何处理路由的信息(调用哪个控制器操作、哪些参数可用以及使用了哪些管道),并且与插件级别的日志记录分开。要更改插件日志级别,请参见 https://hexdocs.cn/phoenix/Phoenix.Logger.html#module-dynamic-log-level
  • :private - 当路由匹配时要合并到连接中的私有数据映射
  • :assigns - 当路由匹配时要合并到连接中的数据映射
  • :metadata - 电信事件使用的元数据映射,以及由 route_info/4 返回
  • :warn_on_verify - 用于在 Phoenix.VerifiedRoutes 中,是否匹配此路由触发未匹配路由警告的布尔值。它对于忽略在验证路由时与之匹配的否则为通配符的路由定义很有用。默认为 false

示例

match(:move, "/events/:id", EventController, :move)

match(:*, "/any", SomeController, :any)
链接到此宏

options(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由来处理对给定路径的 options 请求。

options("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

patch(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由来处理对给定路径的 patch 请求。

patch("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

pipe_through(pipes)

查看源代码 (宏)

定义一个连接将要经过的插件(和管道)列表。

插件使用任何导入的 2 元函数的原子名称来指定,该函数接受一个 %Plug.Conn{} 和选项并返回一个 %Plug.Conn{};例如,:require_authenticated_user

管道在路由器中定义;有关更多信息,请参见 pipeline/2

pipe_through [:my_imported_function, :my_pipeline]
链接到此宏

pipeline(plug, list)

查看源代码 (宏)

定义一个插件管道。

管道在路由器根目录中定义,并且可以从任何作用域使用。

示例

pipeline :api do
  plug :token_authentication
  plug :dispatch
end

然后,作用域可以使用此管道作为

scope "/" do
  pipe_through :api
end

每次调用 pipe_through/1 时,都会将新的管道附加到之前给出的管道。

链接到此宏

plug(plug, opts \\ [])

查看源代码 (宏)

在管道内定义一个插件。

有关更多信息,请参见 pipeline/2

链接到此宏

post(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由来处理对给定路径的 post 请求。

post("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

put(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由来处理对给定路径的 put 请求。

put("/events/:id", EventController, :action)

参见 match/5 获取选项。

链接到此宏

resources(path, controller)

查看源代码 (宏)

参见 resources/4

链接到此宏

resources(path, controller, opts)

查看源代码 (宏)

参见 resources/4

链接到此宏

resources(path, controller, opts, list)

查看源代码 (宏)

为资源定义“RESTful”路由。

给定的定义

resources "/users", UserController

将包括到以下操作的路由

  • GET /users => :index
  • GET /users/new => :new
  • POST /users => :create
  • GET /users/:id => :show
  • GET /users/:id/edit => :edit
  • PATCH /users/:id => :update
  • PUT /users/:id => :update
  • DELETE /users/:id => :delete

选项

此宏接受一组选项

  • :only - 要为其生成路由的操作列表,例如:[:show, :edit]
  • :except - 要排除生成路由的操作列表,例如:[:delete]
  • :param - 此资源的参数名称,默认为 "id"
  • :name - 此资源的前缀。这用于命名助手,并作为嵌套资源中参数的前缀。默认值会自动从控制器名称派生,即 UserController 将具有名称 "user"
  • :as - 配置命名助手。如果为 nil,则不生成助手。在专门使用已验证路由时无效
  • :singleton - 为单例资源定义路由,这些资源由客户端在不引用 ID 的情况下查找。阅读以下内容以了解更多信息

单例资源

当资源需要在不引用 ID 的情况下查找时,因为它在给定上下文中只包含一个条目,则可以使用 :singleton 选项来生成一组特定于此类单个资源的路由

  • GET /user => :show
  • GET /user/new => :new
  • POST /user => :create
  • GET /user/edit => :edit
  • PATCH /user => :update
  • PUT /user => :update
  • DELETE /user => :delete

使用示例

resources "/account", AccountController, only: [:show], singleton: true

嵌套资源

此宏还支持传递嵌套的路由定义块。这有助于将子资源嵌套在其父级中以生成嵌套路由。

给定的定义

resources "/users", UserController do
  resources "/posts", PostController
end

将包括以下路由

user_post_path  GET     /users/:user_id/posts           PostController :index
user_post_path  GET     /users/:user_id/posts/:id/edit  PostController :edit
user_post_path  GET     /users/:user_id/posts/new       PostController :new
user_post_path  GET     /users/:user_id/posts/:id       PostController :show
user_post_path  POST    /users/:user_id/posts           PostController :create
user_post_path  PATCH   /users/:user_id/posts/:id       PostController :update
                PUT     /users/:user_id/posts/:id       PostController :update
user_post_path  DELETE  /users/:user_id/posts/:id       PostController :delete

从给定的路由器返回所有路由信息。

链接到此宏

scope(options, list)

查看源代码 (宏)

定义一个范围,其中路由可以嵌套。

示例

scope path: "/api/v1", alias: API.V1 do
  get "/pages/:id", PageController, :show
end

上面生成的路由将匹配路径 "/api/v1/pages/:id",并将分派到 API.V1.PageController 中的 :show 操作。还会生成一个名为 api_v1_page_path 的命名助手。

选项

支持的选项是

  • :path - 包含路径范围的字符串。
  • :as - 包含命名助手范围的字符串或原子。设置为 false 时,它会重置嵌套的助手范围。当仅使用经过验证的路由时,它没有效果。
  • :alias - 包含控制器范围的别名(原子)。设置为 false 时,它会重置所有嵌套的别名。
  • :host - 包含主机范围或前缀主机范围的字符串或字符串列表,例如 "foo.bar.com""foo."
  • :private - 当路由匹配时要合并到连接中的私有数据映射
  • :assigns - 当路由匹配时要合并到连接中的数据映射
  • :log - 用于记录路由分派的级别,可以设置为 false。默认为 :debug。路由分派包含有关如何处理路由的信息(调用哪个控制器操作、哪些参数可用以及使用了哪些管道),并且与插件级别的日志记录分开。要更改插件日志级别,请参见 https://hexdocs.cn/phoenix/Phoenix.Logger.html#module-dynamic-log-level
链接到此宏

scope(path, options, list)

查看源代码 (宏)

定义一个具有给定路径的范围。

此函数是以下内容的快捷方式

scope path: path do
  ...
end

示例

scope "/v1", host: "api." do
  get "/pages/:id", PageController, :show
end
链接到此宏

scope(path, alias, options, list)

查看源代码 (宏)

定义一个具有给定路径和别名的范围。

此函数是以下内容的快捷方式

scope path: path, alias: alias do
  ...
end

示例

scope "/v1", API.V1, host: "api." do
  get "/pages/:id", PageController, :show
end
链接到此宏

trace(path, plug, plug_opts, options \\ [])

查看源代码 (宏)

生成一个路由来处理对给定路径的 trace 请求。

trace("/events/:id", EventController, :action)

参见 match/5 获取选项。