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。
相關文章
相關標籤/搜索