iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0

上一篇我們從 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 分派。

Pipeline 由 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

在這裡區塊有四個函式呼叫,分別是 scopepipe_throughgetresource

pipe_through

pipe_through 就是說符合這個區塊的路徑,都要先流過這些 pipeline,才接著進行後續的分派。它可以接收一個 :atom 或是一個 :atom 的串列。這個例子裡符合 get "/"resources "/posts" 的請求,都會先流過之前提到的 :browser pipeline。

get

Phoenix.Router 內建了八個符合 HTTP 請求方法 ( HTTP request method) 的 macro,就是 get/4post/4put/4delete/4head/4trace/4connect/4options/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 只定義了 getputpostpatchdelete,其它的是 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 以外的都比對
  • 如果想要改變產生的 path 名稱,可以使用 :as 選項,若沒有指定,預設值是 Controller 的模組前綴:
resources "/baz", BazController, as: :hoo
# hoo_path GET /baz HelloPhxWeb.BazController :index
# ...
  • 如果想要改變 Controller 函式接收到的參數名稱,可以使用 :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

重點回顧

  • Router 接手 Endpoint 處理的 connection,比對 URI 路徑進行分派
  • Router 也是一顆 plug
  • pipeline 定義一群要依序流過的 plug
  • getpost 等 macro 比對單一路徑的單一 HTTP 請求方法
  • resources 會展開成符合 RESTful 規範的八個函式
  • resourcesgetpost等最終都會編譯成 match/5 函式,用 pattern matching 在呼叫時比對
  • mix phx.routes 可以檢視目前定義的路由清單及對應的模組與函式
  • resources 有很多參數可以用

明天要繼續這一節,解說 Router 的其它功能,如 scope及巢狀路由等等。
Happy hacking! 明天見。


上一篇
Plug 及 Endpoint
下一篇
Router.part_2
系列文
函數式編程: 從 Elixir & Phoenix 入門。31

尚未有邦友留言

立即登入留言