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是为单个应用动态定义于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
的调用都能正常工做。
当咱们为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
文件中的值。
在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。例以下面的show
route,42
是user_id
,17
是post_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"
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" do
和 scope "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
距离第一次在router中看到pipe_through :browser
已通过了好久了。如今让咱们讲讲它。
记得在Overview Guide中,咱们曾吧plugs描述为被堆积而且能以一个预设的顺序执行,就像管道同样。如今咱们将进一步了解这些plug堆栈是如何在router中工做的。
Pipelines就是plugs以特定的顺序堆在一块儿,而后起个名。它们容许咱们自定义行为,并按照对请求的处理变形。Phoenix为咱们提供了许多处理平常任务的pipelines。咱们能够自定义它们,也能够建立新的pipeline,以适应咱们的需求。
刚才生成的Phoenix应用定义了两个pipeline,叫作:browser
和:api
。在讨论它们以前,咱们先谈谈Endpoint plugs中的plug堆。
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
频道是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是个大话题,咱们花了许多笔墨在这里。从本教程中你须要记住的是:
only:
或except:
选项调整匹配函数从句的数量。as:
选项能够减小重复。