Elixir Phoenix 指南 - 路由 - 精简翻译

356 阅读16分钟

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 动词 getpostput等宏,还有一个重要的 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 会调用 index action,用于展示所有的 users。
  • GET /users/:id/edit 会调用 edit action,会带着一个 ID 来获取某个 user 的信息,并且展示在编辑页面。
  • GET /users/new 会调用 new action,展示一个创建 user 的页面。
  • GET /users/:id 会调用 show action,带着一个 ID,用来展示某个 user 的详情。
  • POST /users 会调用 create action,用于创建一个新的 user。
  • PATCH 和 PUT /users/:id 会调用 update action,来保存某个 user 更新后的信息。
  • DELETE /users/:id 会调用 delete action,用于删除某个用户。

如果我们不需要所有这些路由,我们可以选择性地使用 :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.PageControllerpage_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.Helperslib/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。 对于第一个,我们将调用 PostControllerindex操作,但我们会传入一个 user_id。 这意味着我们将只显示这个用户的所有帖子。 适用于所有这些路由。

当为嵌套路由调用路径 helper 函数时,我们需要按照它们在路由定义中出现的顺序传递 ID。 对于下面的 show route,42user_id17post_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 并执行伪造保护——然后将请求分派给 PageControllerindex 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_userensure_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 经验,应该能很好理解。