Phoenix官方教程 (三) 路由

router是Phoenix应用的中心。它们将匹配HTTP请求匹配到控制器动做,接通实时频道管理,并为做用域中间件定义了一系列的管道变换来设置route。html

Phoenix生成的router文件web/router.ex, 看上去会是这样:web

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

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

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

  scope "/", HelloPhoenix do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

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

你给定的应用名称将会替代router模块和控制器名称中的HelloPhoenix数据库

模块的第一行use HelloPhoenix.Web, :router,简单地使得Phoenix router函数在咱们的router中可用。json

Scopes在教程中有它本身的章节,因此咱们不会花太多时间在scope "/", HelloPhoenix do 上。 pipe_through :browser这一行,在Pipeline章节会详细讲解。如今咱们只须要知道,pipelines能为不一样的routes执行一套中间件变换。api

然而在scope块中,有咱们的第一个route:浏览器

get "/", PageController, :index

get是一个Phoenix宏,它定义了match/3函数的一个从句。它对应了HTTP变量GET。相似的宏还对印着其它HTTP变量如POST,PUT,PATCH, DELETE, OPTIONS, CONNECT, TRACE 和 HEAD。服务器

这些宏的第一个参数是路径。这里,是应用的根,/。后面的两个参数是咱们但愿用于处理这个请求的控制器和动做。这些宏还能接受别的设置,咱们将在后面看到。session

若是这是咱们的router模块中惟一的route,那么宏展开后,match/3函数的从句会是:app

def match(conn, "GET", ["/"])

match/3函数内部创建了链接,并调用了匹配到的控制其动做。socket

当咱们添加更多route,更多match函数的从句会被添加到咱们的router模块。它们会和Elixir中其它任何多从句函数同样运做。它们会被从头开始尝试,第一个和参项(verb和路径)匹配成功的从句会被执行。匹配成功后,搜索会中止,并且没有从句会再被尝试。

这意味着有可能基于HTTP verb和path创造一个永远不会被匹配的route,不管controller和action。

若是咱们创造了一个有歧义的route,touter仍然回编译,但会发出警告。让咱们来看看。

定义scope "/", HelloPhoenix do在router块的底部。

get "/", RootController, :index

而后在你项目的根目录运行$ mix compile。你会看见如下警告:

web/router.ex:1: warning: this clause cannot match because a previous clause at line 1 always matches
Compiled web/router.ex

Phoenix提供了一个伟大的工具来查看应用中的routes,mix任务phoenix.routes

让咱们看看它是如何工做的。进入Phoenix应用的根目录,运行$ mix phoenix.routes。(若是你没有完成上面的步骤,就须要运行$ mix do deps.get, compile,在运行routes任务以前。)你会看到相似下列的内容,由咱们如今惟一的route生成:

$ mix phoenix.routes
page_path  GET  /  HelloPhoenix.PageController :index

这个输出代表任何对应用根部的HTTP GET请求都会由HelloPhoenix.PageController中的index动做来操做。

page_path是一个Phoenix调用path helper的例子,咱们很快会讲到它。

资源

除了相似get, post, 和 put的HTTP verbs,router还支持其它宏。其中最重要的是resources,它会展开成match/3的8个从句。

让咱们在web/router.ex中添加一个resources:

scope "/", HelloPhoenix do
  pipe_through :browser # Use the default browser stack

  get "/", PageController, :index
  resources "/users", UserController
end

出于演示目的,就算没有HelloPhoenix.UserController也不要紧。

而后进入项目根部,运行$ mix phoenix.routes

你会看到相似这样的东西:

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

固然,你的项目名会取代HelloPhoenix

这是一个HTTP verbs,paths,和控制器动做的标准矩阵。让咱们分别查看它们,以稍稍不一样的顺序。

  • /users的GET请求会调用index动做来展现全部users。
  • /users/:id的GET请求会调用show动做,附带id,来展现一个由ID选定的user
  • /users/new的GET请求会调用new动做,来展现一个表格,用于建立新user。
  • /users的POST请求会调用create动做,来保存一个新user到数据库。
  • /users/:id/edit的GET请求会调用edit动做附带一个ID,以从数据库获取特定的user,并将信息呈如今一个表格中用于编辑。
  • /users/:id的PATCH请求会调用update动做,附带一个ID,以保存更新后的user到数据库。
  • /users/:id的PUT请求做用和上面同样。
  • /users/:id的DELETE请求会调用delete动做,附带一个ID,以从数据库删除指定的user。

若是咱们以为用不到全部的routes,咱们能够选择使用:only:except选项。

假设咱们有一个只读的posts resource。咱们能够这样定义:

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

运行$ mix phoenix.routes,证实咱们如今只有到index和show动做的routes定义了。

post_path  GET     /posts      HelloPhoenix.PostController :index
post_path  GET     /posts/:id  HelloPhoenix.PostController :show

相似地,若是咱们有一个comments resource,不但愿提供删除的route,咱们能够这样定义route。

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

运行$ mix phoenix.routes,会发现咱们除了对delete动做的DELETE请求没有,其它全都有。

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

Path Helpers

Path helpers是为单个应用动态定义于Router.Helpers模块的函数。对咱们来讲,它是HelloPhoenix.Router.Helpers。它们的名字是从route定义所使用的控制器的名字中衍生出来的。咱们的控制器是HelloPhoenix.PageController,而page_path是一个会返回到应用根部的path的函数。

百闻不如一见。在项目根部运行$ iex -S mix。当咱们在router helpers上调用page_path函数,以Endpoint或connection和action做为参数,它会返回path。

iex> HelloPhoenix.Router.Helpers.page_path(HelloPhoenix.Endpoint, :index)
"/"

这颇有用,由于咱们能够在模板中使用page_path函数来连接到咱们的应用根部。注意:若是函数调用太长了,咱们能够在应用的主view中包含import HelloPhoenix.Router.Helpers

<a href="<%= page_path(@conn, :index) %>">To the Welcome Page!</a>

更多信息,请移步View Guide

当咱们必须修改router中route的path时,因为path helpers是基于routers动态定义的,因此模板中任何对page_path的调用都能正常工做。

关于Path Helpers的更多

当咱们为user resource运行phoenix.routes任务时,它列出了path helper函数的输出,做为每一行的user_path。这里有每一个action的翻译:

iex> import HelloPhoenix.Router.Helpers
iex> alias HelloPhoenix.Endpoint
iex> user_path(Endpoint, :index)
"/users"

iex> user_path(Endpoint, :show, 17)
"/users/17"

iex> user_path(Endpoint, :new)
"/users/new"

iex> user_path(Endpoint, :create)
"/users"

iex> user_path(Endpoint, :edit, 37)
"/users/37/edit"

iex> user_path(Endpoint, :update, 37)
"/users/37"

iex> user_path(Endpoint, :delete, 17)
"/users/17"

若是path中有查询字符串呢?经过添加键值对做为可选参数,path helpers将会以查询字符串返回这些对。

iex> user_path(Endpoint, :show, 17, admin: true, active: false)
"/users/17?admin=true&active=false"

若是咱们须要一个完整url而非path呢?用_url代替_path就能够了:

iex(3)> user_url(Endpoint, :index)
"http://localhost:4000/users"

应用端点立刻会有它们本身的教程了。如今,就把它们当成会从router那里接管请求的实体。包括开启app/server,实施配置,以及实施全部请求共有的plugs。

_url函数会从每一个环境的配置参数设置中获取宿主,端口,代理端口和SSL,这些构成完整URL所需的信息。咱们将会在配置本身的章节详细地讨论它。如今,你能够看看/config/dev.exs文件中的值。

嵌套Resources

在Phoenix router中也能够嵌套resources。假设咱们有一个posts资源,它和users是一对多的关系。意思是,一我的写好多文,而一篇文只属于一我的。咱们能够经过在web/router.ex中添加一个嵌套route来表示它:

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

当咱们运行$ mix phoenix.routes,咱们获得了一些新的routes:

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

咱们看到这些routes都将posts限制到了一个user ID。咱们会调用PostController index动做,但还会传送一个user_id。这暗示了咱们会只显示某个user的全部posts。相同的限制应用于全部这些routes。

当咱们为嵌套routes调用path helper函数时,咱们须要按定义中的顺序传送IDs。例以下面的showroute,42user_id17post_id。记住在开始以前alias咱们的HelloPhoenix.Endpoint

iex> alias HelloPhoenix.Endpoint
iex> HelloPhoenix.Router.Helpers.user_post_path(Endpoint, :show, 42, 17)
"/users/42/posts/17"

再一次,若是咱们在函数调用的末尾添加一个键值对,它会添加到query字符串中。

iex> HelloPhoenix.Router.Helpers.user_post_path(Endpoint, :index, 42, active: true)
"/users/42/posts?active=true"

Scoped Routes

Scopes能够将有着相同path前缀的routes分组,并设置一套plug中间件。咱们可能在管理员功能,APIs,尤为是版本化的APIs中会用到它。假设在一个站点,咱们有用户生成检测,而进入这个检测须要管理员权限。这些资源的语义会很不一样,并且它们可能不共用控制器。Scopes使咱们可以隔离这些routes。

当users面对检测时,就像标准的资源同样。

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

管理员检测路劲的前缀能够是/admin

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

咱们经过一个设置了path选项到/admin的scoped route来完成这些。如今,咱们不要再嵌套这个scope到任何其它scope中了(就像scope "/", HelloPhoenix do)。

scope "/admin" do
  pipe_through :browser

  resources "/reviews", HelloPhoenix.Admin.ReviewController
end

注意Phoenix会假设path都是以斜杠开头的,因此scope "/admin" doscope "admin" do会产生相同结果。

还要注意,咱们定义这个scope的方法,咱们须要完整写出咱们控制器的名称,HelloPhoenix.Admin.ReviewController。咱们将在稍后修正它。

再次运行$ mix phoenix.routes,咱们又获得了一些新的routes:

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

看上去不错,但还有个问题。记得咱们想要用户面对reviews routes/reviews,以及管理员的/admin/reviews。若是咱们像这样引入用户面对的reviews到咱们的router:

scope "/", HelloPhoenix do
  pipe_through :browser
  . . .
  resources "/reviews", ReviewController
  . . .
end

scope "/admin" do
  resources "/reviews", HelloPhoenix.Admin.ReviewController
end

而后运行$ mix phoenix.routes,咱们会获得:

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

看上去一切正常,除了每行开头的review_path。咱们为用户面的和管理员的使用了同一个helper,这是不对的。咱们能够经过添加一个as: :admin选项到咱们的admin scope来修复它。

scope "/", HelloPhoenix do
  pipe_through :browser
  . . .
  resources "/reviews", ReviewController
  . . .
end

scope "/admin", as: :admin do
  resources "/reviews", HelloPhoenix.Admin.ReviewController
end

如今$ mix phoenix.routes的结果就是咱们想要的。

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

path helper如今也会想咱们预期的那样运做。运行$ iex -S mix并进行尝试。

iex(1)> HelloPhoenix.Router.Helpers.review_path(Endpoint, :index)
"/reviews"

iex(2)> HelloPhoenix.Router.Helpers.admin_review_path(Endpoint, :show, 1234)
"/admin/reviews/1234"

若是咱们有一些资源都须要管理员来处理呢?咱们能够像这样把它们放到同一个scope中:

scope "/admin", as: :admin do
  pipe_through :browser

  resources "/images",  HelloPhoenix.Admin.ImageController
  resources "/reviews", HelloPhoenix.Admin.ReviewController
  resources "/users",   HelloPhoenix.Admin.UserController
end

这里是$ mix phoenix.routes将会告诉咱们的:

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

很好,就是咱们想要的,但咱们可让它变得更好。注意每一个资源的控制器前都要加上HelloPhoenix.Admin。这很无聊也容易出错。咱们能够添加一个HelloPhoenix.Admin选项到咱们的scope声明中,就在scope path后面,这样全部的routes都会有正确完整的控制器名。

scope "/admin", HelloPhoenix.Admin, as: :admin do
  pipe_through :browser

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

如今再次运行$ mix phoenix.routes,你会看到和以前同样的结果。

此外,咱们能够将应用的全部routes嵌套进一个scope,它的别名就是咱们的Phoenix应用的名字,这样能咱们的控制器名称就不会重复。

在为新应用生成router时,Phoenix已经为咱们这样作了(请看本节的开头)。注意在defmodule中对HelloPhoenix.Router的使用:

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  scope "/", HelloPhoenix do
    pipe_through :browser

    get "/images", ImageController, :index
    resources "/reviews", ReviewController
    resources "/users",   UserController
  end
end

再次运行$ mix phoenix.routes,结果代表咱们全部的控制器都有正确的,完整的名字。

image_path   GET     /images            HelloPhoenix.ImageController :index
review_path  GET     /reviews           HelloPhoenix.ReviewController :index
review_path  GET     /reviews/:id/edit  HelloPhoenix.ReviewController :edit
review_path  GET     /reviews/new       HelloPhoenix.ReviewController :new
review_path  GET     /reviews/:id       HelloPhoenix.ReviewController :show
review_path  POST    /reviews           HelloPhoenix.ReviewController :create
review_path  PATCH   /reviews/:id       HelloPhoenix.ReviewController :update
             PUT     /reviews/:id       HelloPhoenix.ReviewController :update
review_path  DELETE  /reviews/:id       HelloPhoenix.ReviewController :delete
  user_path  GET     /users             HelloPhoenix.UserController :index
  user_path  GET     /users/:id/edit    HelloPhoenix.UserController :edit
  user_path  GET     /users/new         HelloPhoenix.UserController :new
  user_path  GET     /users/:id         HelloPhoenix.UserController :show
  user_path  POST    /users             HelloPhoenix.UserController :create
  user_path  PATCH   /users/:id         HelloPhoenix.UserController :update
             PUT     /users/:id         HelloPhoenix.UserController :update
  user_path  DELETE  /users/:id         HelloPhoenix.UserController :delete

尽管scope像resource同样能够嵌套,可是不鼓励这种行为,由于这会使得咱们的代码变得难以辨认。假设咱们有一个版本控制的API,包含为images,reviews和users定义的resources。从技术上咱们能够这样设置routes:

scope "/api", HelloPhoenix.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 phoenix.routes代表咱们获得了咱们想要的routes。

api_v1_image_path  GET     /api/v1/images            HelloPhoenix.Api.V1.ImageController :index
 api_v1_image_path  GET     /api/v1/images/:id/edit   HelloPhoenix.Api.V1.ImageController :edit
 api_v1_image_path  GET     /api/v1/images/new        HelloPhoenix.Api.V1.ImageController :new
 api_v1_image_path  GET     /api/v1/images/:id        HelloPhoenix.Api.V1.ImageController :show
 api_v1_image_path  POST    /api/v1/images            HelloPhoenix.Api.V1.ImageController :create
 api_v1_image_path  PATCH   /api/v1/images/:id        HelloPhoenix.Api.V1.ImageController :update
                    PUT     /api/v1/images/:id        HelloPhoenix.Api.V1.ImageController :update
 api_v1_image_path  DELETE  /api/v1/images/:id        HelloPhoenix.Api.V1.ImageController :delete
api_v1_review_path  GET     /api/v1/reviews           HelloPhoenix.Api.V1.ReviewController :index
api_v1_review_path  GET     /api/v1/reviews/:id/edit  HelloPhoenix.Api.V1.ReviewController :edit
api_v1_review_path  GET     /api/v1/reviews/new       HelloPhoenix.Api.V1.ReviewController :new
api_v1_review_path  GET     /api/v1/reviews/:id       HelloPhoenix.Api.V1.ReviewController :show
api_v1_review_path  POST    /api/v1/reviews           HelloPhoenix.Api.V1.ReviewController :create
api_v1_review_path  PATCH   /api/v1/reviews/:id       HelloPhoenix.Api.V1.ReviewController :update
                    PUT     /api/v1/reviews/:id       HelloPhoenix.Api.V1.ReviewController :update
api_v1_review_path  DELETE  /api/v1/reviews/:id       HelloPhoenix.Api.V1.ReviewController :delete
  api_v1_user_path  GET     /api/v1/users             HelloPhoenix.Api.V1.UserController :index
  api_v1_user_path  GET     /api/v1/users/:id/edit    HelloPhoenix.Api.V1.UserController :edit
  api_v1_user_path  GET     /api/v1/users/new         HelloPhoenix.Api.V1.UserController :new
  api_v1_user_path  GET     /api/v1/users/:id         HelloPhoenix.Api.V1.UserController :show
  api_v1_user_path  POST    /api/v1/users             HelloPhoenix.Api.V1.UserController :create
  api_v1_user_path  PATCH   /api/v1/users/:id         HelloPhoenix.Api.V1.UserController :update
                    PUT     /api/v1/users/:id         HelloPhoenix.Api.V1.UserController :update
  api_v1_user_path  DELETE  /api/v1/users/:id         HelloPhoenix.Api.V1.UserController :delete

有趣的是,咱们可让多个scopes具备相同的path,只要咱们注意不要重复routes。若是咱们重复了route,将会获得相似的警告。

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

两个scopes定义了相同的path,这个router依旧一切正常。

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

    resources "/users", UserController
  end

  scope "/", AnotherApp do
    pipe_through :browser

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

$ mix phoenix.routes的结果。

user_path  GET     /users           HelloPhoenix.UserController :index
user_path  GET     /users/:id/edit  HelloPhoenix.UserController :edit
user_path  GET     /users/new       HelloPhoenix.UserController :new
user_path  GET     /users/:id       HelloPhoenix.UserController :show
user_path  POST    /users           HelloPhoenix.UserController :create
user_path  PATCH   /users/:id       HelloPhoenix.UserController :update
           PUT     /users/:id       HelloPhoenix.UserController :update
user_path  DELETE  /users/:id       HelloPhoenix.UserController :delete
post_path  GET     /posts           AnotherApp.PostController :index
post_path  GET     /posts/:id/edit  AnotherApp.PostController :edit
post_path  GET     /posts/new       AnotherApp.PostController :new
post_path  GET     /posts/:id       AnotherApp.PostController :show
post_path  POST    /posts           AnotherApp.PostController :create
post_path  PATCH   /posts/:id       AnotherApp.PostController :update
           PUT     /posts/:id       AnotherApp.PostController :update
post_path  DELETE  /posts/:id       AnotherApp.PostController :delete

Pipeline

距离第一次在router中看到pipe_through :browser已通过了好久了。如今让咱们讲讲它。

记得在Overview Guide中,咱们曾吧plugs描述为被堆积而且能以一个预设的顺序执行,就像管道同样。如今咱们将进一步了解这些plug堆栈是如何在router中工做的。

Pipelines就是plugs以特定的顺序堆在一块儿,而后起个名。它们容许咱们自定义行为,并按照对请求的处理变形。Phoenix为咱们提供了许多处理平常任务的pipelines。咱们能够自定义它们,也能够建立新的pipeline,以适应咱们的需求。

刚才生成的Phoenix应用定义了两个pipeline,叫作:browser:api。在讨论它们以前,咱们先谈谈Endpoint plugs中的plug堆。

The Endpoint Plugs

Endpoints组织了全部请求都要用到的plugs,而且应用了它们,在和它们基本的:browser,:api,以及自定义管道一块儿被派遣到router以前。默认的Endpoint plugs作了很是多工做。下面按顺序介绍它们。

  • Plug.Static - 服务静态资源。因为这个plug排在logger以前,因此静态资源是没有登记的。

  • Plug.Logger - 记录来到的请求

  • Phoenix.CodeReloader - 为web目录中的全部路口开启了代码重载。它是在Phoenix应用中直接配置的。

  • Plug.Parsers - 解析请求的本体,当有一个解析器可用时。默认解析器能解析url编码,multipart和json(和poison一块儿)。当请求的内容类型不能被解析时,请求的本体就是没法触碰的。

  • Plug.MethodOverride - 为了POST请求,转换请求方法到PUT, PATCH 或 DELETE,伴随着一个合法的_method参数。

  • Plug.Head - 将HEAD请求转换为GET请求,并剥开响应体。

  • Plug.Session - 设置会话管理。 注意fetch_session/2仍然必须在使用会话前准确地被调用,由于这个plug只是设置了如何获取会话。

  • Plug.Router - 将router放进请求的循环

:browser:api管道

Phoenix定义了另外两个默认的管道。:browser:api。router会在匹配了一个route以后调用它们,假定咱们已经在一个封闭的scope中使用它们调用了pipe_through/1

就像它们的名字暗示的那样,:browser管道是为那些给浏览器渲染请求的routes提供的。:api管道视为那些给api产生数据的routes准备的。

:browser管道有五个plugs:plug :accepts, ["html"]定义了能被接受的请求格式,:fetch_session,会天然地获取会话并让其在链接中可用,:fetch_flash,会检索任何设置好的flash信息,以及:protect_from_forgery:put_secure_browser_headers,它们能保护posts免受跨站欺骗。

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

router在route定义时调用管道,它们都在scope中。若是没有定义scope,router会在全部routes上调用管道。尽管不鼓励嵌套scope,但若是咱们在一个嵌套scope中调用pipe_through,router将会先在父scope中导入pipe_through,而后是嵌套的scope。

多说无益,看看代码。

下面是咱们的Phoenix应用的router,此次添加了一个api scope和一个route。

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

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

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

  scope "/", HelloPhoenix do
    pipe_through :browser

    get "/", PageController, :index
  end

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

    resources "/reviews", ReviewController
  end
end

当服务器收到一个请求,这个请求老是会先通过Endpoint上的plugs,而后它会试图匹配path和HTTP verb。

假设请求匹配了咱们的第一个route:对/的GET。router会先将请求送至:browser管道 - 它会获取会话数据,获取flash,并执行欺骗保护 - 在请求被调遣到PageController index action以前。

相反,若是请求匹配到了宏resources/2定义的任何routes,router会将其送至:api管道 - 如今它什么都不会作 - 在将请求调遣到HelloPhoenix.ReviewController中正确的action以前。

若是咱们知道咱们的应用只会为浏览器渲染views,那么就能够经过删除api和scopes来简化router:

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

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

  pipe_through :browser

  get "/", HelloPhoenix.PageController, :index

  resources "/reviews", HelloPhoenix.ReviewController
end

删除了全部scopes意味着router必须对全部routes调用:browser管道。

让咱们思考一下。若是咱们须要将请求pipe不止一个管道呢?咱们能够简单地pipe_through一个管道列表,Phoenix会按顺序调用它们。

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
  ...

  scope "/reviews" do
    # Use the default browser stack.
    pipe_through [:browser, :review_checks, :other_great_stuff]

    resources "/", HelloPhoenix.ReviewController
  end
end

这里有另外一个例子,两个scopes有着不一样的管道:

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end
  ...

  scope "/", HelloPhoenix do
    pipe_through :browser

    resources "/posts", PostController
  end

  scope "/reviews", HelloPhoenix do
    pipe_through [:browser, :review_checks]

    resources "/", ReviewController
  end
end

一般,管道的scoping规则和你预期的同样。在这个例子中,全部routes会通过:browser管道。而后只有reviews资源routes会通过:review_checks管道。因为咱们是以pipe_through [:browser, :review_checks]来声明管道的,因此Phoenix会按顺序调用列表中的每一个管道。

创造新管道

Phoenix容许咱们在router的任何地方建立咱们的自定义管道。咱们只须要调用宏pipeline/2,伴随着这些参数:一个表明管道名的原子,一个包含了所需plugs的块。

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    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", HelloPhoenix do
    pipe_through :review_checks

    resources "/", ReviewController
  end
end

频道routes

频道是Phoenix中的实时组件,它很exciting。频道会为一个给定的主题处理进出socket的信息。频道routes,须要将请求匹配到socket和主题,才能调遣请求到正确的频道。(关于频道的更多信息,请看Channel Guide )。

咱们为lib/hello_phoenix/endpoint.ex中的endpoint加装了socket处理器。Socket处理器会处理验证回调和频道routes。

defmodule HelloPhoenix.Endpoint do
  use Phoenix.Endpoint

  socket "/socket", HelloPhoenix.UserSocket
  ...
end

而后,咱们要打开web/channels/user_socket.ex文件,使用channel/3宏定义咱们的频道routes。这些routes处理事件时会匹配一个频道的话题模式。若是咱们有一个名为RoomChannel的频道模块,和一个叫作"rooms:*"的话题,那么代码能够这样写。

defmodule HelloPhoenix.UserSocket do
  use Phoenix.Socket

  channel "rooms:*", HelloPhoenix.RoomChannel
  ...
end

话题只不过是字符串id。这种形式便于咱们在一个字符串中定义话题和子话题 - “topic:subtopic”。*是一个通配符,它容许咱们匹配任意子话题,因此"rooms:lobby""rooms:kitchen"都能匹配该route。

Phoenix抽象化了socket传送层,并使用两种传送机制 - WebSockets 和 Long-Polling。若是想让咱们的频道只使用一种机制,咱们能够设置via选项。

channel "rooms:*", HelloPhoenix.RoomChannel, via: [Phoenix.Transports.WebSocket]

每一个socket能够为多个频道处理请求。

channel "rooms:*", HelloPhoenix.RoomChannel, via: [Phoenix.Transports.WebSocket]
channel "foods:*", HelloPhoenix.FoodChannel

咱们能够把多个socket处理器堆放在endpoint中:

socket "/socket", HelloPhoenix.UserSocket
socket "/admin-socket", HelloPhoenix.AdminSocket

总结

routing是个大话题,咱们花了许多笔墨在这里。从本教程中你须要记住的是:

  • 以HTTP verb名为开头的routes会展开成匹配函数的一个从句。
  • 以'resources'为开头的routes会展开成匹配函数的八个从句。
  • resources能够经过only:except:选项调整匹配函数从句的数量。
  • 任何routes均可以嵌套。
  • 任何routes均可以被scoped到一个给定的path。
  • 在scope中使用as:选项能够减小重复。
  • 对scoped routes使用helper选项能够消除没法获取的paths。
相关文章
相关标签/搜索