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:
選項能夠減小重複。