查看源代码 路由

要求:本指南假设您已阅读过 入门指南 并成功运行了 Phoenix 应用 运行起来

要求:本指南假设您已阅读过 请求生命周期指南

路由器是 Phoenix 应用的主要枢纽。它们将 HTTP 请求匹配到控制器动作,连接实时通道处理程序,并定义一系列作用于一组路由的管道转换。

Phoenix 生成的路由器文件,lib/hello_web/router.ex,看起来会像这样

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

  # Other scopes may use custom stacks.
  # scope "/api", HelloWeb do
  #   pipe_through :api
  # end
  # ...
end

路由器和控制器模块名称都将以您为应用提供的名称作为前缀,后缀为 Web

该模块的第一行,use HelloWeb, :router,只是在我们的特定路由器中提供 Phoenix 路由器功能。

作用域在本指南中拥有自己的部分,因此我们不会在这里花时间讨论 scope "/", HelloWeb do 块。 pipe_through :browser 行将在本指南的“管道”部分得到全面介绍。现在,您只需要知道管道允许将一组插头应用于不同的路由集。

然而,在作用域块内,我们有了第一个实际路由

get "/", PageController, :home

get 是一个 Phoenix 宏,对应于 HTTP 动词 GET。类似的宏存在于其他 HTTP 动词中,包括 POST、PUT、PATCH、DELETE、OPTIONS、CONNECT、TRACE 和 HEAD。

为什么使用宏?

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

我们使用 getpostputdelete 来定义您的路由。我们使用宏来实现两个目的

  • 它们定义了路由引擎,用于每个请求,以选择将请求分派到哪个控制器。由于宏的存在,Phoenix 将所有路由编译成一个巨大的 case 语句,其中包含模式匹配规则,这些规则由 Erlang VM 高度优化。

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

换句话说,路由器依赖于宏来构建速度更快、更安全的应用程序。还要记住,Elixir 中的宏只是编译时存在的,这在代码编译后提供了很多稳定性。正如我们将在下一节中学习到的,Phoenix 还通过 mix phx.routes 为所有定义的路由提供自省功能。

检查路由

Phoenix 提供了一个非常棒的工具来调查应用程序中的路由:mix phx.routes

让我们看看它是如何工作的。转到新生成的 Phoenix 应用程序的根目录并运行 mix phx.routes。您应该看到类似以下内容,其中包含您当前拥有的所有路由

$ mix phx.routes
GET  /  HelloWeb.PageController :home
...

上面的路由告诉我们,任何针对应用程序根目录的 HTTP GET 请求都将由 HelloWeb.PageControllerhome 动作处理。

资源

除了像 getpostput 这样的 HTTP 动词宏外,路由器还支持其他宏。其中最重要的一个是 resources。让我们将资源添加到 lib/hello_web/router.ex 文件中,如下所示

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
  resources "/users", UserController
  ...
end

现在,我们还没有实际的 HelloWeb.UserController,这并不重要。

再次在项目根目录中运行 mix phx.routes。您应该看到类似以下内容

...
GET     /users           HelloWeb.UserController :index
GET     /users/:id/edit  HelloWeb.UserController :edit
GET     /users/new       HelloWeb.UserController :new
GET     /users/:id       HelloWeb.UserController :show
POST    /users           HelloWeb.UserController :create
PATCH   /users/:id       HelloWeb.UserController :update
PUT     /users/:id       HelloWeb.UserController :update
DELETE  /users/:id       HelloWeb.UserController :delete
...

这是 HTTP 动词、路径和控制器动作的标准矩阵。有一段时间,这被称为 RESTful 路由,但现在大多数人认为这是一个误称。让我们分别看看它们。

  • 发送到 /users 的 GET 请求将调用 index 动作以显示所有用户。
  • 发送到 /users/:id/edit 的 GET 请求将调用 edit 动作,并带有一个 ID,从数据存储中检索单个用户,并将信息显示在用于编辑的表单中。
  • 发送到 /users/new 的 GET 请求将调用 new 动作,以显示一个用于创建新用户的表单。
  • 发送到 /users/:id 的 GET 请求将调用 show 动作,并带有一个 ID,以显示由该 ID 标识的单个用户。
  • 发送到 /users 的 POST 请求将调用 create 动作,以将新用户保存到数据存储中。
  • 发送到 /users/:id 的 PATCH 请求将调用 update 动作,并带有一个 ID,以将更新后的用户保存到数据存储中。
  • 发送到 /users/:id 的 PUT 请求也将调用 update 动作,并带有一个 ID,以将更新后的用户保存到数据存储中。
  • 发送到 /users/:id 的 DELETE 请求将调用 delete 动作,并带有一个 ID,以从数据存储中删除单个用户。

如果我们不需要所有这些路由,我们可以使用 :only:except 选项选择性地过滤特定动作。

假设我们有一个只读的帖子资源。我们可以像这样定义它

resources "/posts", PostController, only: [:index, :show]

运行 mix phx.routes 显示我们现在只有定义了到 index 和 show 动作的路由。

GET     /posts      HelloWeb.PostController :index
GET     /posts/:id  HelloWeb.PostController :show

类似地,如果我们有一个评论资源,并且我们不想提供删除评论的路由,我们可以像这样定义路由。

resources "/comments", CommentController, except: [:delete]

现在运行 mix phx.routes 显示我们拥有除 DELETE 请求到 delete 动作以外的所有路由。

GET    /comments           HelloWeb.CommentController :index
GET    /comments/:id/edit  HelloWeb.CommentController :edit
GET    /comments/new       HelloWeb.CommentController :new
GET    /comments/:id       HelloWeb.CommentController :show
POST   /comments           HelloWeb.CommentController :create
PATCH  /comments/:id       HelloWeb.CommentController :update
PUT    /comments/:id       HelloWeb.CommentController :update

Phoenix.Router.resources/4 宏描述了用于自定义资源路由的其他选项。

经过验证的路由

Phoenix 包含 Phoenix.VerifiedRoutes 模块,该模块使用 ~p 符号对路由器路径与您的路由器进行编译时检查。例如,您可以在控制器、测试和模板中编写路径,编译器将确保这些路径与您的路由器中定义的路由实际匹配。

让我们看看它是如何工作的。在项目的根目录中运行 iex -S mix。我们将定义一个临时示例模块,该模块构建一对 ~p 路由路径。

iex> defmodule RouteExample do
...>   use HelloWeb, :verified_routes
...>
...>   def example do
...>     ~p"/comments"
...>     ~p"/unknown/123"
...>   end
...> end
warning: no route path for HelloWeb.Router matches "/unknown/123"
  iex:5: RouteExample.example/0

{:module, RouteExample, ...}
iex>

请注意,对现有路由 ~p"/comments" 的第一次调用没有产生任何警告,但错误的路由路径 ~p"/unknown/123" 产生了编译器警告,正如预期的那样。这很重要,因为它允许我们编写应用程序中原本硬编码的路径,并且编译器会在我们编写错误的路由或更改路由结构时通知我们。

Phoenix 项目开箱即用地设置为允许在整个 Web 层(包括测试)中使用经过验证的路由。例如,在您的模板中,您可以渲染 ~p 链接

<.link href={~p"/"}>Welcome Page!</.link>
<.link href={~p"/comments"}>View Comments</.link>

或在控制器中发出重定向

redirect(conn, to: ~p"/comments/#{comment}")

对路由路径使用 ~p 可确保我们的应用程序路径和 URL 与路由器定义保持一致。编译器将为我们捕获错误,并在我们更改应用程序其他地方引用的路由时通知我们。

关于经过验证的路由的更多信息

查询字符串路径怎么办?您可以直接添加查询字符串键值对,或者提供键值对字典,例如

~p"/users/17?admin=true&active=false"
"/users/17?admin=true&active=false"

~p"/users/17?#{[admin: true]}"
"/users/17?admin=true"

如果我们需要完整的 URL 而不是路径怎么办?只需将您的路径用对 Phoenix.VerifiedRoutes.url/1 的调用包装起来,该调用在任何 ~p 可用的地方都被导入

url(~p"/users")
"http://localhost:4000/users"

url 调用将从为每个环境设置的配置参数中获取主机、端口、代理端口和 SSL 信息,以构造完整的 URL。我们将在专门的指南中详细讨论配置。现在,您可以查看项目中的 config/dev.exs 文件以查看这些值。

嵌套资源

也可以在 Phoenix 路由器中嵌套资源。假设我们还有一个 posts 资源,它与 users 存在多对一的关系。也就是说,一个用户可以创建多个帖子,而一个帖子只属于一个用户。我们可以通过在 lib/hello_web/router.ex 中添加一个嵌套路由来表示这一点,如下所示

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

当我们现在运行 mix phx.routes 时,除了上面我们看到的 users 路由外,我们还得到了以下路由集

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

我们看到这些路由中的每一个都将帖子作用域到用户 ID。对于第一个,我们将调用 PostControllerindex 动作,但我们将传入 user_id。这意味着我们将只显示该用户的全部帖子。相同的范围适用于所有这些路由。

构建嵌套路由的路径时,需要在路由定义中将 ID 插入到它们应该在的位置。对于以下 show 路由,42user_id17post_id

user_id = 42
post_id = 17
~p"/users/#{user_id}/posts/#{post_id}"
"/users/42/posts/17"

经过验证的路由也支持 Phoenix.Param 协议,但我们现在不需要关心 Elixir 协议。只需知道,一旦我们开始使用像 %User{}%Post{} 这样的结构构建应用程序,我们就可以直接将这些数据结构插入到 ~p 路径中,Phoenix 将提取正确的字段用于该路由。

~p"/users/#{user}/posts/#{post}"
"/users/42/posts/17"

请注意,我们不需要插入 user.idpost.id?如果我们后来决定使 URL 更漂亮,并开始使用 slug 而不是 ID,这将非常方便。我们不需要更改任何 ~p

作用域路由

作用域是一种将路由分组到一个公共路径前缀和作用域插头集的方法。我们可能希望这样做来实现管理员功能、API,尤其是针对版本化的 API。假设在一个网站上,我们有用户生成的评论,这些评论需要先由管理员批准。这些资源的语义非常不同,它们可能不会共享相同的控制器。作用域使我们能够隔离这些路由。

面向用户的评论的路径看起来像一个标准资源。

/reviews
/reviews/1234
/reviews/1234/edit
...

管理评论路径可以用 /admin 作为前缀。

/admin/reviews
/admin/reviews/1234
/admin/reviews/1234/edit
...

我们通过一个作用域路由来实现这一点,该路由将路径选项设置为/admin,就像这样。我们可以将这个作用域嵌套在另一个作用域中,但现在,让我们将其单独设置在根目录,在lib/hello_web/router.ex中添加以下内容

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/reviews", ReviewController
end

我们定义了一个新的作用域,其中所有路由都以/admin为前缀,所有控制器都在HelloWeb.Admin命名空间下。

再次运行mix phx.routes,除了之前的路由集,我们还得到以下内容

...
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete
...

看起来不错,但这里有一个问题。请记住,我们希望用户界面审查路由/reviews和管理员路由/admin/reviews。如果现在在我们的路由器中将用户界面审查添加到根作用域中,就像这样

scope "/", HelloWeb do
  pipe_through :browser

  ...
  resources "/reviews", ReviewController
end

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/reviews", ReviewController
end

然后我们运行mix phx.routes,我们得到每个作用域路由的输出

...
GET     /reviews                 HelloWeb.ReviewController :index
GET     /reviews/:id/edit        HelloWeb.ReviewController :edit
GET     /reviews/new             HelloWeb.ReviewController :new
GET     /reviews/:id             HelloWeb.ReviewController :show
POST    /reviews                 HelloWeb.ReviewController :create
PATCH   /reviews/:id             HelloWeb.ReviewController :update
PUT     /reviews/:id             HelloWeb.ReviewController :update
DELETE  /reviews/:id             HelloWeb.ReviewController :delete
...
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete

如果我们有很多资源都由管理员处理呢?我们可以将它们全部放在同一个作用域中,就像这样

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/images",  ImageController
  resources "/reviews", ReviewController
  resources "/users",   UserController
end

以下是mix phx.routes告诉我们的内容

...
GET     /admin/images            HelloWeb.Admin.ImageController :index
GET     /admin/images/:id/edit   HelloWeb.Admin.ImageController :edit
GET     /admin/images/new        HelloWeb.Admin.ImageController :new
GET     /admin/images/:id        HelloWeb.Admin.ImageController :show
POST    /admin/images            HelloWeb.Admin.ImageController :create
PATCH   /admin/images/:id        HelloWeb.Admin.ImageController :update
PUT     /admin/images/:id        HelloWeb.Admin.ImageController :update
DELETE  /admin/images/:id        HelloWeb.Admin.ImageController :delete
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete
GET     /admin/users             HelloWeb.Admin.UserController :index
GET     /admin/users/:id/edit    HelloWeb.Admin.UserController :edit
GET     /admin/users/new         HelloWeb.Admin.UserController :new
GET     /admin/users/:id         HelloWeb.Admin.UserController :show
POST    /admin/users             HelloWeb.Admin.UserController :create
PATCH   /admin/users/:id         HelloWeb.Admin.UserController :update
PUT     /admin/users/:id         HelloWeb.Admin.UserController :update
DELETE  /admin/users/:id         HelloWeb.Admin.UserController :delete

太棒了,正是我们想要的。注意每个路由和控制器是如何正确地进行命名空间的。

作用域也可以任意嵌套,但你应该谨慎操作,因为嵌套有时会使代码变得混乱且难以理解。也就是说,假设我们有一个版本化的 API,其中定义了图像、评论和用户的资源。那么从技术上讲,我们可以像这样设置版本化 API 的路由

scope "/api", HelloWeb.Api, as: :api do
  pipe_through :api

  scope "/v1", V1, as: :v1 do
    resources "/images",  ImageController
    resources "/reviews", ReviewController
    resources "/users",   UserController
  end
end

你可以运行mix phx.routes来查看这些定义的样子。

有趣的是,我们可以使用具有相同路径的多个作用域,只要我们注意不要重复路由即可。以下路由器完全可以定义两个相同路径的作用域

defmodule HelloWeb.Router do
  use Phoenix.Router
  ...
  scope "/", HelloWeb do
    pipe_through :browser

    resources "/users", UserController
  end

  scope "/", AnotherAppWeb do
    pipe_through :browser

    resources "/posts", PostController
  end
  ...
end

如果我们确实重复了一个路由(这意味着两个路由具有相同的路径),我们会收到这个熟悉的警告

warning: this clause cannot match because a previous clause at line 16 always matches

管道

在这份指南中,我们已经走了很远,但没有讨论我们在路由器中看到的第一个代码行之一:pipe_through :browser。现在是时候解决这个问题了。

管道是一系列可以附加到特定作用域的插件。如果你不熟悉插件,我们有一个关于插件的深入指南

路由定义在作用域内,作用域可以通过多个管道。一旦路由匹配,Phoenix 会调用与该路由关联的所有管道中定义的所有插件。例如,访问/将通过:browser管道,因此将调用其所有插件。

Phoenix 默认定义了两个管道,:browser:api,它们可用于许多常见任务。反过来,我们可以自定义它们,以及创建新的管道来满足我们的需求。

:browser:api管道

顾名思义,:browser管道为渲染浏览器请求的路由做准备,而:api管道为生成 API 数据的路由做准备。

:browser管道有六个插件:plug :accepts, ["html"] 定义了接受的请求格式或格式。:fetch_session,它自然地获取会话数据并将其提供给连接。:fetch_live_flash,它从 LiveView 获取任何闪存消息并将它们与控制器闪存消息合并。然后,插件:put_root_layout 将存储根布局以用于渲染目的。后面的:protect_from_forgery:put_secure_browser_headers,保护表单提交免受跨站点伪造攻击。

目前,:api管道只定义了plug :accepts, ["json"]

路由器在一个作用域内定义的路由上调用管道。作用域之外的路由没有管道。尽管不建议使用嵌套作用域(参见上面的版本化 API 示例),但如果我们在嵌套作用域内调用pipe_through,路由器将调用来自父作用域的所有pipe_through,然后是嵌套作用域。

这些词语都堆积在一起。让我们看一些例子来理清它们的意思。

这是从一个新生成的 Phoenix 应用程序中获取的路由器的另一个示例,这次/api作用域已取消注释,并添加了一个路由。

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

  # Other scopes may use custom stacks.
  scope "/api", HelloWeb do
    pipe_through :api

    resources "/reviews", ReviewController
  end
  # ...
end

当服务器接受请求时,请求将始终首先通过我们端点中的插件,之后将尝试匹配路径和 HTTP 动词。

假设请求匹配我们的第一个路由:一个 GET 请求到/。路由器将首先通过:browser管道传递该请求(它将获取会话数据、获取闪存并执行伪造保护),然后将请求调度到PageControllerhome操作。

相反,假设请求匹配由resources/2宏定义的任何路由。在这种情况下,路由器将通过:api管道传递它(目前仅执行内容协商),然后将其进一步调度到HelloWeb.ReviewController 的正确操作。

如果没有任何路由匹配,则不会调用任何管道,并会引发 404 错误。

创建新的管道

Phoenix 允许我们使用以下参数在路由器的任何位置创建我们自己的自定义管道:一个用于新管道名称的原子和一个包含我们想要的所有插件的块。使用pipeline/2 宏调用它

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :auth do
    plug HelloWeb.Authentication
  end

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

    resources "/", ReviewController
  end
end

上面假设存在一个名为HelloWeb.Authentication 的插件,它执行身份验证,现在是:auth管道的部分。

请注意,管道本身是插件,因此我们可以将管道插入另一个管道中。例如,我们可以重写上面的auth管道来自动调用browser,简化下游管道调用

  pipeline :auth do
    plug :browser
    plug :ensure_authenticated_user
    plug :ensure_user_owns_review
  end

  scope "/reviews", HelloWeb do
    pipe_through :auth

    resources "/", ReviewController
  end

如何组织我的路由?

在 Phoenix 中,我们倾向于定义几个管道,它们提供特定的功能。例如,:browser:api管道旨在分别被特定客户端、浏览器和 http 客户端访问。

也许更重要的是,定义特定于身份验证和授权的管道也很常见。例如,你可能有一个管道要求所有用户都经过身份验证。另一个管道可能强制执行只有管理员用户才能访问某些路由。

定义好管道后,你就可以在所需的作用域中重用管道,将路由分组到它们的管道周围。例如,回到我们的评论示例。假设任何人都可以阅读评论,但只有经过身份验证的用户才能创建评论。你的路由可能如下所示

pipeline :browser do
  ...
end

pipeline :auth do
  plug HelloWeb.Authentication
end

scope "/" do
  pipe_through [:browser]

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

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

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

请注意,在上面的代码中,路由是如何跨不同作用域拆分的。虽然这种分离一开始可能令人困惑,但它有一个很大的好处:检查路由并查看例如需要身份验证的所有路由以及哪些路由不需要身份验证非常容易。这有助于审核并确保你的路由具有正确的范围。

你可以创建尽可能少或尽可能多的作用域。由于管道可以在作用域之间重用,因此它们有助于封装通用功能,你可以根据需要将它们组合在定义的每个作用域上。

转发

Phoenix.Router.forward/4宏可用于将所有以特定路径开头的请求发送到特定插件。假设我们有一个系统的一部分(甚至可以是单独的应用程序或库)负责在后台运行作业,它可能有自己的 Web 界面用于检查作业的状态。我们可以使用以下方法转发到这个管理界面

defmodule HelloWeb.Router do
  use HelloWeb, :router

  ...

  scope "/", HelloWeb do
    ...
  end

  forward "/jobs", BackgroundJob.Plug
end

这意味着所有以/jobs开头的路由都将发送到HelloWeb.BackgroundJob.Plug 模块。在插件内部,你可以匹配子路由,例如/pending/active,它们显示了某些作业的状态。

我们甚至可以将forward/4宏与管道混合使用。如果我们想确保用户已通过身份验证并且是管理员才能查看作业页面,我们可以在路由器中使用以下方法。

defmodule HelloWeb.Router do
  use HelloWeb, :router

  ...

  scope "/" do
    pipe_through [:authenticate_user, :ensure_admin]
    forward "/jobs", BackgroundJob.Plug
  end
end

这意味着authenticate_userensure_admin 管道中的插件将在BackgroundJob.Plug 之前调用,允许它们相应地发送适当的响应并停止请求。

可以在模块插件的init/1 回调中接收到的opts 作为第三个参数传递。例如,也许后台作业允许你设置要显示在页面上的应用程序名称。这可以通过以下方式传递

forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix"

还有一个第四个可以传递的router_opts 参数。这些选项在Phoenix.Router.scope/2 文档中概述。

BackgroundJob.Plug 可以实现为插件指南中讨论的任何模块插件。但请注意,不建议转发到另一个 Phoenix 端点。这是因为你的应用程序和转发端点定义的插件将被调用两次,这可能会导致错误。

摘要

路由是一个很大的主题,我们在这里已经涵盖了很多内容。从本指南中要带走的重要要点是

  • 以 HTTP 动词名称开头的路由扩展到匹配函数的一个子句。
  • resources声明的路由扩展到匹配函数的 8 个子句。
  • 资源可以使用only:except: 选项来限制匹配函数子句的数量。
  • 所有这些路由都可以嵌套。
  • 所有这些路由都可以限定到给定的路径。
  • 使用经过验证的路由,使用~p 进行编译时路由检查