上一篇我們從 Endpoint 手中接過了 connection,裡面有經過初步處理的 HTTP request 內容。
在大多數的情況下,開發者很少需要自己寫一個 plug。而是用 plug 函式庫提供的各種包裝好的機制,處理接收到的 request,並依情況呼叫不同的函式。上篇提到的 Endpoint 是一種 plug,而這篇要來解釋的 router,也是一種包裝特化過的 plug。
來看一下專案裡 lib/hello_phx_web/router.ex
的內容:
defmodule HelloPhxWeb.Router do
use HelloPhxWeb, :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 "/", HelloPhxWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/posts", PostController
end
# Other scopes may use custom stacks.
# scope "/api", HelloPhxWeb do
# pipe_through :api
# end
end
在模組宣告的下一行,寫了 use HelloPhxWeb, :router
。當看到用的是了我們專案 namespace 的 HelloPhxWEb
,你就知道那個模組是我們有機會亂改的,這部份將在之後 web.ex
的章節說明。
pipeline
往下看有兩個 pipeline
區塊,pipeline
代表一群 plug 串在一起。這裡我們區分出 :browser
及 :api
兩組 pipeline。因為同樣是 HTTP request,由一般使用者使用瀏覽器所發出的請求,以及 API 呼叫的請求,常常會需要不同的驗證機制,警告訊息及安全功能等等,所以我們用 pipeline 來區分不同的請求要通過哪些 plug。
除了 :browser
及 :api
兩個 pipeline 之外,會很常依不同的場景,來切出一系列不同的 pipeline,依場景決定哪些要通過哪些 pipeline。舉例來說,常會有個 :auth
pipeline 用來告訴所有需要驗證登入才能存取的頁面,要先通過這一群驗證帳號用的函式處理後,才往 Controller 分派。
plug
組成而在 pipeline 裡,有一行行的 plug :something
,這就是組成這個 pipeline 的成員。我們可以看到 :browser
pipeline 裡,有 :accepts
,:fetch_session
及 :fetch_flash
等五個 plug。就如同在endpoint 裡一樣,由網頁瀏覽器發出的請求,會依序流經這些 plug,並在最後將處理完成的 connection 向下傳遞。
接下來是各個路徑比對區塊,這是 router 運作的核心機制。我們再來仔細看一下其中一個區塊的內容:
scope "/", HelloPhxWeb do
pipe_through :browser # Use the default browser stack
get "/", PageController, :index
resources "/posts", PostController
end
在這裡區塊有四個函式呼叫,分別是 scope
、pipe_through
、get
及 resource
。
pipe_through
pipe_through
就是說符合這個區塊的路徑,都要先流過這些 pipeline,才接著進行後續的分派。它可以接收一個 :atom
或是一個 :atom
的串列。這個例子裡符合 get "/"
及 resources "/posts"
的請求,都會先流過之前提到的 :browser
pipeline。
get
Phoenix.Router 內建了八個符合 HTTP 請求方法 ( HTTP request method) 的 macro,就是 get/4
、post/4
、put/4
、delete/4
、 head/4
、trace/4
、connect/4
及 options/4
,用來讓你比對對單一路徑 (URI) 的一種請求。而這一句:
get "/", PageController, :index
在編譯之後,會被展開成這樣的函式:
def match(:get, "/", PageController, :index, [])
也就是當 HTTP 請求發生,進入我們的應用程式後,會依請求的方法及路徑呼叫 match/5
函式。而在 pattern matching 後,會找到第一個符合的區塊進行呼叫。
而因為是 pattern matching,所以根路徑 /
的比對放在子路徑 /post
的比對上方也沒關係。但是隨著我們加入愈來愈多路徑,就像一般的多區塊函式一樣,是從上方的宣告往下找,一旦找到符合的就停止了。因此我們還是可能會疏忽寫出永遠無法被執行的路徑比對。不過在編譯時期,當 Phoenix 發現我們寫出這類重覆的區塊時,他會顯示警告訊息 (但還是能夠編譯)。
NOTE 1.: 也有 patch/4
可以用。
NOTE 2: Plug.Router
只定義了 get
、put
、post
、patch
、delete
,其它的是 Phoenix.Router
才有實作的。但在 Phoenix 中,不管是哪一個 macro,都是使用 Phoenix 提供的包裝層。
resources
除了正規的 HTTP 請求方法之外,最重要的就是符合 RESTful 概念的 resource
了,這個 macro 會將路徑比對展開成八個 match/5
函式。
Phoenix 提供了一個很好用的工具來檢視已定義的路由比對。我們可以在 shell 裡輸入 mix phx.routes
,就會列出目前已定義的路由清單,以及對應的 Controller:
$ mix phx.routes
page_path GET / HelloPhxWeb.PageController :index
post_path GET /posts HelloPhxWeb.PostController :index
post_path GET /posts/:id/edit HelloPhxWeb.PostController :edit
post_path GET /posts/new HelloPhxWeb.PostController :new
post_path GET /posts/:id HelloPhxWeb.PostController :show
post_path POST /posts HelloPhxWeb.PostController :create
post_path PATCH /posts/:id HelloPhxWeb.PostController :update
PUT /posts/:id HelloPhxWeb.PostController :update
post_path DELETE /posts/:id HelloPhxWeb.PostController :delete
resource
可用的選項:only
及 :except
來只比對部份的 HTTP 請求方法:resources "/foo", FooController, only: [:index, :show]
#=> 只比對 :index 及 :show
resources "/bar", BarController, except: [:delete]
#=> 除了 :delete 以外的都比對
:as
選項,若沒有指定,預設值是 Controller 的模組前綴:resources "/baz", BazController, as: :hoo
# hoo_path GET /baz HelloPhxWeb.BazController :index
# ...
:param
選項。若不指定預設是 "id"
:resources "/games", GameController, param: "title", only: [:show]
# game_path GET /games/:title HelloPhxWeb.GameController :show
:id
來取得不同的項目) 時,可以使用 :singleton
選項。那麼產生的 routes 就會改變:resources "/team", TeamController, singleton: true
# team_path GET /team/edit HelloPhxWeb.TeamController :edit
# team_path GET /team/new HelloPhxWeb.TeamController :new
# team_path GET /team HelloPhxWeb.TeamController :show
# team_path POST /team HelloPhxWeb.TeamController :create
# team_path PATCH /team HelloPhxWeb.TeamController :update
# PUT /team HelloPhxWeb.TeamController :update
# team_path DELETE /team HelloPhxWeb.TeamController :delete
get
、post
等 macro 比對單一路徑的單一 HTTP 請求方法resources
會展開成符合 RESTful 規範的八個函式resources
、get
、post
等最終都會編譯成 match/5
函式,用 pattern matching 在呼叫時比對mix phx.routes
可以檢視目前定義的路由清單及對應的模組與函式resources
有很多參數可以用明天要繼續這一節,解說 Router 的其它功能,如 scope
及巢狀路由等等。
Happy hacking! 明天見。