Routing
路由器是 Phoenix 应用程序的主要枢纽。它们将 HTTP 请求与 controller action 相匹配,连接实时通道处理程序,将 pipeline 应用于路由集合。
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, {HelloWeb.LayoutView, :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, :index
end
# Other scopes may use custom stacks.
# scope "/api", HelloWeb do
# pipe_through :api
# end
# ...
end
路由模块和控制器模块名称都将以应用程序的名称为前缀,后缀为 Web。
该模块的第一行use HelloWeb,:router,只是让我们能在路由中使用 Phoenix 框架提供的路由能力。
scopes 会在后面单独介绍,因此我们不会花时间在 scope "/", HelloWeb do 块上。 pipe_through :browser 这行将在后面的 “Pipelines” 介绍。现在,您只需要知道 pipelines 允许将一组 plugs 应用于不同的路由集。
在 scope 块内,我们有我们的第一个实际的 路由:
get "/", PageController, :index
get 是一个 Phoenix 的宏,对应的是 HTTP 动作 GET。类似的宏还有,POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE 和 HEAD。
查看路由
Phoenix 有一个很棒的查看路由表的工具:mix phx.routes。
具体来看一下。在一个新生成的 Phoenix 项目下,执行 mix phx.routes。你将会看到当前所有的路由:
$ mix phx.routes
page_path GET / HelloWeb.PageController :index
...
这个路由表示,到根路径的 GET 请求会被 HelloWeb.PageController 下的 index action 处理。
page_path 在Phoenix 中是一种路径 helper,后面再详细说说。
Resources 资源
除了常见的 HTTP 动词 get、post、put等宏,还有一个重要的 resources。在 lib/hello_web/router.ex 添加一个 resource 看看:
scope "/", HelloWeb do
pipe_through :browser
get "/", PageController, :index
resources "/users", UserController
...
end
现在还没有 HelloWeb.UserController,不过没关系。
再次执行 mix phx.routes,你将看到:
...
user_path GET /users HelloWeb.UserController :index
user_path GET /users/:id/edit HelloWeb.UserController :edit
user_path GET /users/new HelloWeb.UserController :new
user_path GET /users/:id HelloWeb.UserController :show
user_path POST /users HelloWeb.UserController :create
user_path PATCH /users/:id HelloWeb.UserController :update
PUT /users/:id HelloWeb.UserController :update
user_path DELETE /users/:id HelloWeb.UserController :delete
...
这是标准的 HTTP verbs, paths, 和 controller actions 对照表。有一段时间,这被称为 RESTful 路由,但现在大多数人认为这是误称。单独来看看:
- GET
/users会调用indexaction,用于展示所有的 users。 - GET
/users/:id/edit会调用editaction,会带着一个 ID 来获取某个 user 的信息,并且展示在编辑页面。 - GET
/users/new会调用newaction,展示一个创建 user 的页面。 - GET
/users/:id会调用showaction,带着一个 ID,用来展示某个 user 的详情。 - POST
/users会调用createaction,用于创建一个新的 user。 - PATCH 和 PUT
/users/:id会调用updateaction,来保存某个 user 更新后的信息。 - DELETE
/users/:id会调用deleteaction,用于删除某个用户。
如果我们不需要所有这些路由,我们可以选择性地使用 :only 和 :except 选项来过滤特定操作。
假设我们有一个只读的帖子资源。 我们可以这样定义:
resources "/posts", PostController, only: [:index, :show]
运行 mix phx.routes 表明我们现在只定义了指向 index 和 show 操作的路由。
post_path GET /posts HelloWeb.PostController :index
post_path GET /posts/:id HelloWeb.PostController :show
同样,如果我们有一个评论资源,并且我们不想提供删除它的路由,我们可以定义这样的路由。
resources "/comments", CommentController, except: [:delete]
运行 mix phx.routes 现在显示我们拥有除删除操作的 DELETE 请求之外的所有路由。
comment_path GET /comments HelloWeb.CommentController :index
comment_path GET /comments/:id/edit HelloWeb.CommentController :edit
comment_path GET /comments/new HelloWeb.CommentController :new
comment_path GET /comments/:id HelloWeb.CommentController :show
comment_path POST /comments HelloWeb.CommentController :create
comment_path PATCH /comments/:id HelloWeb.CommentController :update
PUT /comments/:id HelloWeb.CommentController :update
Phoenix.Router.resources/4 宏描述了自定义资源路由的附加选项。
Path helpers
路径 helpers 是动态定义的函数。 它们允许我们获取指定的 controller-action 对应的路径。 每个路径 helper 的名称都源自路由定义中使用的controller的名称。 对于我们的controller HelloWeb.PageController,page_path 是返回路径的函数,在本例中是我们应用程序的根路径。(这段不好翻译)。
让我们看看它的实际效果。 在项目的根目录下运行 iex -S mix。 当我们使用 endpoint 或 connection和action 作为参数调用路由 helper 上的 page_path 函数时,它会将路径返回给我们。
HelloWeb.Router.Helpers.page_path(HelloWeb.Endpoint, :index)
"/"
这很重要,因为我们可以在模板中使用 page_path 函数来链接到应用程序的根目录。 然后我们可以在我们的模板中使用这个helper:
<%= link "Welcome Page!", to: Routes.page_path(@conn, :index) %>
请注意,路径 helper 是在 Router.Helpers 模块上为单个应用程序动态定义的。 对我们来说,就是 HelloWeb.Router.Helpers。
我们可以使用 Routes.page_path 而不是完整的 HelloWeb.Router.Helpers.page_path 的原因是因为 HelloWeb.Router.Helpers 在 lib/hello_web.ex 中定义的 view_helpers/0 块中默认别名为 Routes。 这个定义通过 use HelloWeb, :view 可用于我们的模板。
当然,我们可以使用 HelloWeb.Router.Helpers.page_path(@conn, :index) 代替,但为了简洁起见,惯例是使用别名版本。 请注意,别名仅自动设置用于视图、控制器和模板 - 在这些之外,您需要全名,或者在模块定义中使用 alias HelloWeb.Router.Helpers, as Routes 自己为其设置别名。
使用路径 helper 可以很容易地确保我们的控制器、视图和模板链接到我们的路由实际可以处理的页面。
More on path helpers 其他
当我们为 user 资源运行 mix phx.routes 时,它会将 user_path 作为每个输出行的路径 helper 函数列出来。 以下是每个动作的含义:
iex> alias HelloWeb.Router.Helpers, as: Routes
iex> alias HelloWeb.Endpoint
Routes.user_path(Endpoint, :index)
"/users"
iex> Routes.user_path(Endpoint, :show, 17)
"/users/17"
iex> Routes.user_path(Endpoint, :new)
"/users/new"
iex> Routes.user_path(Endpoint, :create)
"/users"
iex> Routes.user_path(Endpoint, :edit, 37)
"/users/37/edit"
iex> Routes.user_path(Endpoint, :update, 37)
"/users/37"
iex> Routes.user_path(Endpoint, :delete, 17)
"/users/17"
带有 query 字符串的呢? 通过添加可选的键值对的第四个参数,路径 helper 将在 query 字符串中返回这些对。
iex> Routes.user_path(Endpoint, :show, 17, admin: true, active: false)
"/users/17?admin=true&active=false"
如果我们需要完整的 URL 而不是路径怎么办? 只需将 _path 替换为 _url:
iex> Routes.user_url(Endpoint, :index)
"http://localhost:4000/users"
*_url 函数将从为每个环境设置的配置参数中获取构建完整 URL 所需的主机、端口、代理端口和 SSL 信息。 我们将在其自己的指南中更详细地讨论配置。 现在,您可以查看自己项目中的 config/dev.exs 文件以查看这些值。
只要有可能,最好传递一个 conn(或您的视图中的 @conn)来代替 endpoint 模块。
嵌套路由
也可以在 Phoenix 路由器中嵌套资源。 假设我们还有一个与用户具有多对一关系的帖子资源。 也就是说,一个用户可以创建很多帖子,而一个帖子只属于一个用户。 我们可以通过在 lib/hello_web/router.ex 中添加一个嵌套路由来表示,如下所示:
resources "/users", UserController do
resources "/posts", PostController
end
当我们现在运行 mix phx.routes 时,除了我们在上面为用户看到的路由之外,我们还得到了以下一组路由:
...
user_post_path GET /users/:user_id/posts HelloWeb.PostController :index
user_post_path GET /users/:user_id/posts/:id/edit HelloWeb.PostController :edit
user_post_path GET /users/:user_id/posts/new HelloWeb.PostController :new
user_post_path GET /users/:user_id/posts/:id HelloWeb.PostController :show
user_post_path POST /users/:user_id/posts HelloWeb.PostController :create
user_post_path PATCH /users/:user_id/posts/:id HelloWeb.PostController :update
PUT /users/:user_id/posts/:id HelloWeb.PostController :update
user_post_path DELETE /users/:user_id/posts/:id HelloWeb.PostController :delete
...
我们看到每一个路由都将帖子的范围限定为某个用户 ID。 对于第一个,我们将调用 PostController 的 index操作,但我们会传入一个 user_id。 这意味着我们将只显示这个用户的所有帖子。 适用于所有这些路由。
当为嵌套路由调用路径 helper 函数时,我们需要按照它们在路由定义中出现的顺序传递 ID。 对于下面的 show route,42 是 user_id,17 是 post_id。 让我们记住在开始之前给我们的 HelloWeb.Endpoint 起别名。
iex> alias HelloWeb.Endpoint
iex> HelloWeb.Router.Helpers.user_post_path(Endpoint, :show, 42, 17)
"/users/42/posts/17"
同样,如果在函数后面添加 键值对,这些就会转为 query 字符串。
iex> HelloWeb.Router.Helpers.user_post_path(Endpoint, :index, 42, active: true)
"/users/42/posts?active=true"
如果我们没有给 Helpers 用别名引用(注意,只有 views,templates,controllers 内才会自动使用别名),这里我们在 iex 中,所以我们得自己手动使用别名:
iex> alias HelloWeb.Router.Helpers, as: Routes
iex> alias HelloWeb.Endpoint
iex> Routes.user_post_path(Endpoint, :index, 42, active: true)
"/users/42/posts?active=true"
Scoped routes
Scopes 是一种对路由进行分组的方法,当我们需要考虑 path 的公共前缀或应用一组 plugs 时。 我们可能希望为 后台管理功能、API、尤其是版本化的 API 用上 scope。 比如我们在网站上有用户生成的评论,并且这些评论首先需要得到管理员的批准。 这些资源的语义完全不同,它们可能不共享同一个controller,Scopes 让我们能够隔离开这些 routes。
面向用户的评论的 path 看起来像一个标准资源。
/reviews
/reviews/1234
/reviews/1234/edit
...
管理后台的评论的 path,可能有 /admin 前缀。
/admin/reviews
/admin/reviews/1234
/admin/reviews/1234/edit
...
这时我们就需要用到 scoped route,设置一个 /admin scope。我们可以在一个 scope 内嵌套 scope,也可以直接在最外面设置 scope,在 /lib/hello_web/router.ex 编辑:
scope "/admin", HelloWeb.Admin do
pipe_through :browser
resources "/reviews", ReviewController
end
我们定义了一个新的 scope,它里面的路由都会有一个 /admin前缀,对应的所有的 controller 都会有一个 HelloWeb.Admin命名空间。
执行 mix phx.routes,除了之前的路由,又增加了如下的这些:
...
review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index
review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit
review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new
review_path GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show
review_path POST /admin/reviews HelloWeb.Admin.ReviewController :create
review_path PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update
PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update
review_path 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,我们得到的是:
...
review_path GET /reviews HelloWeb.ReviewController :index
review_path GET /reviews/:id/edit HelloWeb.ReviewController :edit
review_path GET /reviews/new HelloWeb.ReviewController :new
review_path GET /reviews/:id HelloWeb.ReviewController :show
review_path POST /reviews HelloWeb.ReviewController :create
review_path PATCH /reviews/:id HelloWeb.ReviewController :update
PUT /reviews/:id HelloWeb.ReviewController :update
review_path DELETE /reviews/:id HelloWeb.ReviewController :delete
...
review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index
review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit
review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new
review_path GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show
review_path POST /admin/reviews HelloWeb.Admin.ReviewController :create
review_path PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update
PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update
review_path DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete
实际的路由是对了,但是路径 helper review_path 每行都是一样的,也就是两者得到了相同的 helper,显然不对。
我们可以使用 as: :admin 选项来修复这个问题:
scope "/admin", HelloWeb.Admin, as: :admin do
pipe_through :browser
resources "/reviews", ReviewController
end
再执行 mix phx.routes,就得到了我们想要的:
...
review_path GET /reviews HelloWeb.ReviewController :index
review_path GET /reviews/:id/edit HelloWeb.ReviewController :edit
review_path GET /reviews/new HelloWeb.ReviewController :new
review_path GET /reviews/:id HelloWeb.ReviewController :show
review_path POST /reviews HelloWeb.ReviewController :create
review_path PATCH /reviews/:id HelloWeb.ReviewController :update
PUT /reviews/:id HelloWeb.ReviewController :update
review_path DELETE /reviews/:id HelloWeb.ReviewController :delete
...
admin_review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index
admin_review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit
admin_review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new
admin_review_path GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show
admin_review_path POST /admin/reviews HelloWeb.Admin.ReviewController :create
admin_review_path PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update
PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update
admin_review_path DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete
path helpers 也能正确的返回了。执行 iex -S mix 试试。
iex> HelloWeb.Router.Helpers.review_path(HelloWeb.Endpoint, :index)
"/reviews"
iex> HelloWeb.Router.Helpers.admin_review_path(HelloWeb.Endpoint, :show, 1234)
"/admin/reviews/1234"
如果我们有很多很多需要后台处理的资源呢?我们可以将他们放在相同的 scope 下:
scope "/admin", HelloWeb.Admin, as: :admin do
pipe_through :browser
resources "/images", ImageController
resources "/reviews", ReviewController
resources "/users", UserController
end
执行 mix phx.routes 看看:
...
admin_image_path GET /admin/images HelloWeb.Admin.ImageController :index
admin_image_path GET /admin/images/:id/edit HelloWeb.Admin.ImageController :edit
admin_image_path GET /admin/images/new HelloWeb.Admin.ImageController :new
admin_image_path GET /admin/images/:id HelloWeb.Admin.ImageController :show
admin_image_path POST /admin/images HelloWeb.Admin.ImageController :create
admin_image_path PATCH /admin/images/:id HelloWeb.Admin.ImageController :update
PUT /admin/images/:id HelloWeb.Admin.ImageController :update
admin_image_path DELETE /admin/images/:id HelloWeb.Admin.ImageController :delete
admin_review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index
admin_review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit
admin_review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new
admin_review_path GET /admin/reviews/:id HelloWeb.Admin.ReviewController :show
admin_review_path POST /admin/reviews HelloWeb.Admin.ReviewController :create
admin_review_path PATCH /admin/reviews/:id HelloWeb.Admin.ReviewController :update
PUT /admin/reviews/:id HelloWeb.Admin.ReviewController :update
admin_review_path DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete
admin_user_path GET /admin/users HelloWeb.Admin.UserController :index
admin_user_path GET /admin/users/:id/edit HelloWeb.Admin.UserController :edit
admin_user_path GET /admin/users/new HelloWeb.Admin.UserController :new
admin_user_path GET /admin/users/:id HelloWeb.Admin.UserController :show
admin_user_path POST /admin/users HelloWeb.Admin.UserController :create
admin_user_path PATCH /admin/users/:id HelloWeb.Admin.UserController :update
PUT /admin/users/:id HelloWeb.Admin.UserController :update
admin_user_path DELETE /admin/users/:id HelloWeb.Admin.UserController :delete
这很棒,正是我们想要的。 注意每个路由、path helper 和 controller 是如何正确命名的。
Scope 也可以任意嵌套,但你应该小心,因为嵌套有时会使我们的代码混乱和不清晰。 话虽如此,假设我们有一个版本化的 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 看看输出是什么样的。
有趣的是,只要小心不要重复路由,我们就可以使用具有相同路径的多个 scope。 以下路由器非常适合为同一路径定义两个 scope :
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
Pipelines
我们在本指南中已经走了很长一段路,但没有谈论我们在路由中看到的第一行:pipe_through :browser。 是时候解决这个问题了。
管道是一系列的 plugs,然后将这些plugs 应用到 scope 中。 如果您不熟悉 plugs,回上一章去瞧瞧吧。
路由是在 scope 内定义的,scope 可以通过应用多个管道。 一旦路由匹配,Phoenix 会调用与该路由关联的所有管道中定义的所有 plugs 。 例如,访问 / 将通过 :browser 管道进行管道传输,从而调用它的所有 plugs。
Phoenix 默认定义了两个管道,:browser 和 :api,它们可以用于许多常见的任务。 反过来,我们可以自定义它们并创建新的管道来满足我们的需求。
:browser 和 :api 管道
正如它们的名字所暗示的,:browser 管道为处理浏览器请求的路由准备的,而 :api 管道是为 API 生成数据的路由所准备的。
:browser 管道有六个 plugs: plug :accepts, ["html"] 定义了接受的请求格式。 :fetch_session,它自然会获取会话数据并使其在连接中可用。 :fetch_live_flash,它从 LiveView 中获取任何 flash 消息并将它们与控制器 flash 消息合并。 然后,插件 :put_root_layout 将存储根布局以进行渲染。 后面的 :protect_from_forgery 和 :put_secure_browser_headers 保护表单提交免受跨站点伪造。
当前,:api 管道只定义了 plug :accepts, ["json"]。
管道会被 scope 内定义的路由调用。 scope 之外的路由没有管道。 尽管不鼓励使用嵌套 scope(参见上面的版本化 API 示例),但如果我们在嵌套scope内调用 pipe_through,将依次从父 scope 调用所有 pipe_through,然后是子的。
这么多新名词,让我们看一些例子来解开它们的含义。
下面是来自新生成的 Phoenix 应用程序的路由的另一个视图,这一次将 /api scope取消注释并添加了一个路由。
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {HelloWeb.LayoutView, :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, :index
end
# Other scopes may use custom stacks.
scope "/api", HelloWeb do
pipe_through :api
resources "/reviews", ReviewController
end
# ...
end
当服务器接受请求时,请求总是首先通过我们 endpoint 中的 plugs,然后它会尝试匹配路径和 HTTP 动词。
假设请求匹配我们的第一个路由:GET /。 路由将首先将该请求通过 :browser 管道进行管道传输——该管道将获取会话数据、获取 flash 并执行伪造保护——然后将请求分派给 PageController 的 index action。
相反,假设请求匹配由 resources/2 宏定义的任何路由。 在这种情况下,路由将通过 :api 管道(目前仅执行内容协商)对其进行管道传输,然后进一步分派到 HelloWeb.ReviewController 对应的 action。
如果没有路由匹配到,没有管道会被调用,然后报 404 错误处理。
让我们进一步扩展这些想法。 如果我们需要通过 :browser 和一个或多个自定义管道传输请求怎么办? 我们简单地通过 pipe_through 列表进行管道传输,Phoenix 将按顺序调用它们。
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {HelloWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
...
scope "/reviews" do
pipe_through [:browser, :review_checks, :other_great_stuff]
resources "/", HelloWeb.ReviewController
end
end
下面是两个 scope 拥有两个不同的管道的例子:
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {HelloWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
...
scope "/", HelloWeb do
pipe_through :browser
resources "/posts", PostController
end
scope "/reviews", HelloWeb do
pipe_through [:browser, :review_checks]
resources "/", ReviewController
end
end
通常,管道在 scoping 内的行为与您预期的一样。 在此示例中,所有路由都将通过 :browser 管道进行管道传输。 但是,只有 reviews 资源路由会通过 :review_checks 管道。 由于我们在管道列表中声明了两个管道 pipe_through [:browser, :review_checks],Phoenix 将在按顺序对他们进行 pipe_through。
创建新的 pipelines
Phoenix 允许我们在路由的任何位置创建自己的自定义管道。 为此,我们使用以下参数调用 pipeline/2 宏:新管道名称的原子和包含我们想要的所有 plugs 的块。
defmodule HelloWeb.Router do
use HelloWeb, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, {HelloWeb.LayoutView, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
end
pipeline :review_checks do
plug :ensure_authenticated_user
plug :ensure_user_owns_review
end
scope "/reviews", HelloWeb do
pipe_through [:browser, :review_checks]
resources "/", ReviewController
end
end
请注意,管道本身就是 plug ,因此我们可以将一个管道插入另一个管道。 例如,我们可以重写上面的 review_checks 管道以自动调用 browser,简化下游管道调用:
pipeline :review_checks do
plug :browser
plug :ensure_authenticated_user
plug :ensure_user_owns_review
end
scope "/reviews", HelloWeb do
pipe_through :review_checks
resources "/", ReviewController
end
Forward
Phoenix.Router.forward/4 宏可用于将所有以特定路径开头的请求发送到特定的 plug。 假设我们系统的某部分是负责(它甚至可以是一个单独的应用程序或库)在后台运行任务,它可以有自己的 Web 界面来检查作业的状态。 我们可以使用以下命令转发到这个管理界面:
defmodule HelloWeb.Router do
use HelloWeb, :router
...
scope "/", HelloWeb do
...
end
forward "/jobs", BackgroundJob.Plug
end
这意味着所有以 /jobs 开头的路由都将被发送到 HelloWeb.BackgroundJob.Plug 模块。 在 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_user 和 ensure_admin 的 plugs 将在 BackgroundJob.Plug 之前被调用,从而允许它们发送适当的响应并相应地停止请求。
在模块 Plug 中的 init/1 回调中收到的 opts 可以作为第三个参数传递。 例如,后台作业可能允许您设置要在页面上显示的应用程序的名称。 这可以通过:
forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix"
可以传递第四个 router_opts 参数。 Phoenix.Router.scope/2 文档中概述了这些选项。
BackgroundJob.Plug 可以实现为 Plug 指南中讨论的任何模块 Plug。 请注意,虽然不建议转发到另一个 Phoenix endpoint。 这是因为您的应用程序定义的 plugs 和转发的 endpoint 将被调用两次,这可能会导致错误。
小结
路由是一个很大的话题,我们在这里讨论了很多内容。 从本指南中的要点是:
- 以 HTTP 动词开头的路由扩展为 match 函数的单个子句。
- 使用
resources声明的路由扩展为 match 函数的 8 个子句。 - 资源可以通过使用
only:或except:选项来限制 match 函数子句的数量。 - 这些路由中的任何一个都可以嵌套。
- 这些路由中的任何一个都可以限定为给定的路径。
- 在 scope 中使用
as:选项可以减少重复。 - 使用 scope 路由的 helper 选项可以消除不好获取路径的问题。
完
大部分是机翻,然后做的微调,语言表达能力有限,将就着看吧。机翻还是 google 翻译的好。很多名词难以翻译,直接用就好了。
Phoenix 中的路由,整体思路跟 Rails 中的很相似,如果有 Rails 经验,应该能很好理解。