上一篇我們從 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_throughpipe_through 就是說符合這個區塊的路徑,都要先流過這些 pipeline,才接著進行後續的分派。它可以接收一個 :atom 或是一個 :atom 的串列。這個例子裡符合 get "/" 及 resources "/posts" 的請求,都會先流過之前提到的 :browser pipeline。
getPhoenix.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! 明天見。