檢視原始碼 路由

需求:本指南假設您已看過入門指南,並建立一個 正常運作的 Phoenix 應用程式

需求:本指南假設您已看過請求生命週期指南

路由器是 Phoenix 應用程式的核心。它們會將 HTTP 請求配對至控制器動作,連接即時頻道處理常式,並定義一系列管線轉換,範圍會侷限在特定路由組。

Phoenix 生成的路由器檔案為 lib/hello_web/router.ex,會類似以下範例:

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

  # Other scopes may use custom stacks.
  # scope "/api", HelloWeb do
  #   pipe_through :api
  # end
  # ...
end

路由器和控制器模組名稱都會加上您為應用程式命名,並加上 Web 字尾。

此模組的第一行 use HelloWeb, :router,只是單純讓 Phoenix 函式可以在我們的路由器中使用。

範圍在這個指南中有自己的章節,所以這邊不會花時間說明 scope "/", HelloWeb do 這個區塊。 pipe_through :browser 這行會在本指南的「管線」章節中完整說明。現在,您只要知道管線允許將一組插頭套用至不同路由組即可。

然而,在範圍區塊內,我們有第一個實際的路由,也就是

get "/", PageController, :home

get 是 Phoenix 巨集,對應到 HTTP 動詞 GET。針對其他 HTTP 動詞,也有類似的巨集,包括 POST、PUT、PATCH、DELETE、OPTIONS、CONNECT、TRACE 和 HEAD。

為何要使用巨集?

Phoenix 極力減少使用巨集。然而,您可能已經注意到 Phoenix.Router 大量依賴巨集。這是為什麼呢?

我們使用 getpostputdelete 來定義您的路由。我們使用巨集有兩個目的:

  • 它們會定義路由引擎,用於每個請求中選擇派送請求的控制器。由於巨集,Phoenix 會將您的所有路由編譯成一個大型 case-statement 形式的樣式比對規則,這會經過 Erlang 虛擬機器大量最佳化。

  • 針對你定義的每個路由,我們也會定義元資料來實作 Phoenix.VerifiedRoutes。正如我們很快就會學到的,驗證路由讓我們得以參考任何路由,彷彿它只是一個純文字的字串,差別在於此路由經編譯器驗證為有效路由(大幅降低傳送壞掉的連結、表單、郵件等至生產環境的可能性)

換句話說,路由器仰賴巨集來建構更快速、更安全的應用程式。也要記得巨集在 Elixir 中只在編譯時才作用,因此在程式碼編譯過後能夠提供相當程度的穩定性。正如我們接下來學到的,Phoenix 也透過 mix phx.routes 提供所有已定義路由的內省功能。

查看路由

Phoenix 提供一個極佳的工具來探查應用程式中的路由:mix phx.routes

讓我們看看這是怎麼運作的。前往一個剛產生出來的 Phoenix 應用程式根目錄,然後執行 mix phx.routes。你應該會看到類似以下的結果,由你目前擁有的所有路由所產生

$ mix phx.routes
GET  /  HelloWeb.PageController :home
...

上述路由告訴我們,任何針對應用程式根目錄的 GET HTTP 要求都會由 HelloWeb.PageControllerhome action 來處理。

資源

除了 HTTP 動詞(如 getpostput)的巨集之外,路由器也支援其他巨集。其中最重要的巨集是 resources。我們來把一個資源加入到我們 lib/hello_web/router.ex 檔案中,如下所示

scope "/", HelloWeb do
  pipe_through :browser

  get "/", PageController, :home
  resources "/users", UserController
  ...
end

現在我們沒有 HelloWeb.UserController 也沒關係。

在專案根目錄再次執行 mix phx.routes。你應該會看到類似以下的結果

...
GET     /users           HelloWeb.UserController :index
GET     /users/:id/edit  HelloWeb.UserController :edit
GET     /users/new       HelloWeb.UserController :new
GET     /users/:id       HelloWeb.UserController :show
POST    /users           HelloWeb.UserController :create
PATCH   /users/:id       HelloWeb.UserController :update
PUT     /users/:id       HelloWeb.UserController :update
DELETE  /users/:id       HelloWeb.UserController :delete
...

這是一個由 HTTP 動詞、路徑和控制項動作所組成的標準矩陣。一陣子以前,這稱為 RESTful 路由,但如今多數人認為這是一個誤稱。讓我們逐一檢視。

  • 針對 /users 的 GET 要求會呼叫 index action 來顯示所有使用者。
  • 針對 /users/:id/edit 的 GET 要求會呼叫 edit action,並提供一個 ID 來從資料庫中擷取一個個別使用者,然後將資訊呈現成一個可編輯的表單。
  • 針對 /users/new 的 GET 要求會呼叫 new action 來提供一個用於建立新使用者的表單。
  • 針對 /users/:id 的 GET 要求會呼叫 show action,並提供一個 ID 來顯示由該 ID 識別的個別使用者。
  • 送出 POST 要求至 /users 會呼叫 create 動作,將新的使用者儲存至資料儲存體。
  • 送出 PATCH 要求至 /users/:id 會呼叫 update 動作,並附上 ID 將更新的使用者儲存至資料儲存體。
  • 送出 PUT 要求至 /users/:id 亦會呼叫 update 動作,並附上 ID 將更新的使用者儲存至資料儲存體。
  • 送出 DELETE 要求至 /users/:id 會呼叫 delete 動作,並附上 ID 將個別使用者從資料儲存體移除。

如果不需要所有這些路由,我們可以使用 :only:except 選項選擇性地濾除特定動作。

假設我們有一个只讀文章資源,可以如下定義

resources "/posts", PostController, only: [:index, :show]

執行 mix phx.routes 顯示現在只有定義至索引和顯示動作的路由。

GET     /posts      HelloWeb.PostController :index
GET     /posts/:id  HelloWeb.PostController :show

類似地,如果我們有一個留言資源,但不想提供路由來刪除它,可以如下定義路由。

resources "/comments", CommentController, except: [:delete]

執行 mix phx.routes 現在顯示我們有所有路由,除了刪除動作的 DELETE 要求。

GET    /comments           HelloWeb.CommentController :index
GET    /comments/:id/edit  HelloWeb.CommentController :edit
GET    /comments/new       HelloWeb.CommentController :new
GET    /comments/:id       HelloWeb.CommentController :show
POST   /comments           HelloWeb.CommentController :create
PATCH  /comments/:id       HelloWeb.CommentController :update
PUT    /comments/:id       HelloWeb.CommentController :update

Phoenix.Router.resources/4 巨集描述了自訂資源路由的其他選項。

已驗證路由

Phoenix 包含 Phoenix.VerifiedRoutes 模組,它使用 ~p 編號提供編譯時間檢查路由路徑相對於路由的方法。例如,您可以在控制器、測試和範本中撰寫路徑,編譯器會確保它們實際上與路由中定義的路徑相符。

讓我們看看實際操作。在專案的根目錄執行 iex -S mix。我們會定義一個一次性範例模組,用來建立幾個 ~p 路徑。

iex> defmodule RouteExample do
...>   use HelloWeb, :verified_routes
...>
...>   def example do
...>     ~p"/comments"
...>     ~p"/unknown/123"
...>   end
...> end
warning: no route path for HelloWeb.Router matches "/unknown/123"
  iex:5: RouteExample.example/0

{:module, RouteExample, ...}
iex>

請注意,針對現有路由 ~p"/comments" 的第一次呼叫不會產生警告,但錯誤的路由路徑 ~p"/unknown/123" 會產生編譯器警告,這正符合預期。這很重要,因為它允許我们在應用程式中撰寫其他硬編碼路徑,而編譯器會在我們撰寫錯誤路徑或變更路由結構時通知我們。

Phoenix 專案已預先設定,允許在整個 Web 層級(包括測試)中使用已驗證路由。例如,可以在範本中呈現 ~p 連結

<.link href={~p"/"}>Welcome Page!</.link>
<.link href={~p"/comments"}>View Comments</.link>

或者在控制器中發出重新導向

redirect(conn, to: ~p"/comments/#{comment}")

使用 ~p 設定路由路徑可確保我們的應用程式路徑和 URL 與路由定義保持最新。編譯器會協助我們找出錯誤,並在我們變更應用程式其他地方參考的路由時通知我們。

已驗證路由的詳細內容

查詢字串路徑呢?您可以直接新增查詢字串金鑰值,或提供金鑰值對的字典,例如

~p"/users/17?admin=true&active=false"
"/users/17?admin=true&active=false"

~p"/users/17?#{[admin: true]}"
"/users/17?admin=true"

如果需要完整 URL 而非路徑怎麼辦?只要使用可於所有已導入 ~p 的位置呼叫 Phoenix.VerifiedRoutes.url/1 來包覆路徑

url(~p"/users")
"https://127.0.0.1:4000/users"

url 呼叫會取得架構、連接埠、代理伺服器連接埠和 SSL 資訊,以從每個環境設定的組態參數建構完整的 URL。我們會在其他指南中詳細說明組態。目前,您可以查看您自己專案中的 config/dev.exs 檔案,即可看到那些值。

巢狀資源

在 Phoenix 路由器中,也可以巢狀資源。假設我們也有個 post 資源,它與 user 有一對多關係。也就是說,一個使用者可以建立許多貼文,而且個別貼文只屬於一個使用者。我們可以透過在 lib/hello_web/router.ex 中新增巢狀路由來呈現,如下所示

resources "/users", UserController do
  resources "/posts", PostController
end

現在當我們執行 mix phx.routes 時,除了上述 user 的路由之外,我們還會得到以下一組路由

...
GET     /users/:user_id/posts           HelloWeb.PostController :index
GET     /users/:user_id/posts/:id/edit  HelloWeb.PostController :edit
GET     /users/:user_id/posts/new       HelloWeb.PostController :new
GET     /users/:user_id/posts/:id       HelloWeb.PostController :show
POST    /users/:user_id/posts           HelloWeb.PostController :create
PATCH   /users/:user_id/posts/:id       HelloWeb.PostController :update
PUT     /users/:user_id/posts/:id       HelloWeb.PostController :update
DELETE  /users/:user_id/posts/:id       HelloWeb.PostController :delete
...

我們看到每個路由都會將貼文範圍限定在使用者 ID 上。對於第一個路由,我們會呼叫 PostControllerindex 動作,但是我們會傳入 user_id。這表示我們只會顯示特定使用者的所有貼文。所有這些路由都套用相同的範圍限定。

建構巢狀路由的路徑時,我們需要插入適當位置的 ID。以下 show 路由中,42user_id,而 17post_id

user_id = 42
post_id = 17
~p"/users/#{user_id}/posts/#{post_id}"
"/users/42/posts/17"

已驗證路由也支援 Phoenix.Param 協定,但是目前我們還不必關注 Elixir 協定。只要知道,一旦我們開始使用像 %User{}%Post{} 的結構來建構應用程式時,我們就能將這些資料結構直接插入到 ~p 路徑,而 Phoenix 會挑選出正確的欄位供路由使用。

~p"/users/#{user}/posts/#{post}"
"/users/42/posts/17"

注意到我們不必插入 user.idpost.id 嗎?如果我們稍後決定讓 URL 更美觀,並開始使用簡潔網址,這將特別有用。我們無需變更任何 ~p

範圍限定路由

範圍是一種將路由群組在一個共用路徑字首和範疇設定的插入元件組合的方式。我們可能這麼做以管理功能、API,特別是版本化的 API。假設我們有一個網站有使用者產生的評論,而且這些評論必須先由管理員核准。這些資源的語義完全不同,而且可能沒有共用同一個控制器。範圍讓我們可以區分這些路由。

使用者面向評論的路徑看起來像標準資源。

/reviews
/reviews/1234
/reviews/1234/edit
...

管理評論路徑可以加上 /admin 的字首。

/admin/reviews
/admin/reviews/1234
/admin/reviews/1234/edit
...

我們利用有範疇路由的 /admin 來達成這種目的。我們可以將這個範圍巢狀在其他範圍之內,不過我們先將它自己新增到 lib/hello_web/router.ex 的根目錄中

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/reviews", ReviewController
end

我們定義新的範圍,在這個範圍內所有路由的前面都加上 /admin 字首,而且所有控制器都在 HelloWeb.Admin 的命名空間之下。

再次執行 mix phx.routes,我們可以在先前路線的集合中取得下列項目

...
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete
...

看起來不錯,但有一個問題。記得我們想要同時有使用者面向的評論路由 /reviews 和管理評論路由 /admin/reviews。如果我們現在在根範圍的路徑中新增使用者面向評論,就像這樣

scope "/", HelloWeb do
  pipe_through :browser

  ...
  resources "/reviews", ReviewController
end

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/reviews", ReviewController
end

而且我們執行 mix phx.routes,我們可以取得每個範圍路由的輸出結果

...
GET     /reviews                 HelloWeb.ReviewController :index
GET     /reviews/:id/edit        HelloWeb.ReviewController :edit
GET     /reviews/new             HelloWeb.ReviewController :new
GET     /reviews/:id             HelloWeb.ReviewController :show
POST    /reviews                 HelloWeb.ReviewController :create
PATCH   /reviews/:id             HelloWeb.ReviewController :update
PUT     /reviews/:id             HelloWeb.ReviewController :update
DELETE  /reviews/:id             HelloWeb.ReviewController :delete
...
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete

假設我們有很多資源全部都是由管理員處理,那麼我們可以將全部放入同一個範圍中,就像這樣

scope "/admin", HelloWeb.Admin do
  pipe_through :browser

  resources "/images",  ImageController
  resources "/reviews", ReviewController
  resources "/users",   UserController
end

這是 mix phx.routes 告訴我們的

...
GET     /admin/images            HelloWeb.Admin.ImageController :index
GET     /admin/images/:id/edit   HelloWeb.Admin.ImageController :edit
GET     /admin/images/new        HelloWeb.Admin.ImageController :new
GET     /admin/images/:id        HelloWeb.Admin.ImageController :show
POST    /admin/images            HelloWeb.Admin.ImageController :create
PATCH   /admin/images/:id        HelloWeb.Admin.ImageController :update
PUT     /admin/images/:id        HelloWeb.Admin.ImageController :update
DELETE  /admin/images/:id        HelloWeb.Admin.ImageController :delete
GET     /admin/reviews           HelloWeb.Admin.ReviewController :index
GET     /admin/reviews/:id/edit  HelloWeb.Admin.ReviewController :edit
GET     /admin/reviews/new       HelloWeb.Admin.ReviewController :new
GET     /admin/reviews/:id       HelloWeb.Admin.ReviewController :show
POST    /admin/reviews           HelloWeb.Admin.ReviewController :create
PATCH   /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
PUT     /admin/reviews/:id       HelloWeb.Admin.ReviewController :update
DELETE  /admin/reviews/:id       HelloWeb.Admin.ReviewController :delete
GET     /admin/users             HelloWeb.Admin.UserController :index
GET     /admin/users/:id/edit    HelloWeb.Admin.UserController :edit
GET     /admin/users/new         HelloWeb.Admin.UserController :new
GET     /admin/users/:id         HelloWeb.Admin.UserController :show
POST    /admin/users             HelloWeb.Admin.UserController :create
PATCH   /admin/users/:id         HelloWeb.Admin.UserController :update
PUT     /admin/users/:id         HelloWeb.Admin.UserController :update
DELETE  /admin/users/:id         HelloWeb.Admin.UserController :delete

這真是太棒了,完全符合我們的需求。注意每個路由和控制器都有適當的命名空間。

範圍也可以無限巢狀,不過您執行時要小心,因為巢狀有時會讓我們的程式碼混淆不清。話說回來,假設我們有一個版本化的 API,而且這個 API 定義了圖片、評論和使用者這幾個資源。那麼從技術上來講,我們可以設定版本化 API 的路由,就像這樣

scope "/api", HelloWeb.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 phx.routes 來查看這些定義的樣子。

有趣的是,我們可以使用多個有相同路徑的範圍,只要我們小心不要複製路由即可。以下的路由器對於定義在同一路徑上的兩個範圍而言是完全正確的

defmodule HelloWeb.Router do
  use Phoenix.Router
  ...
  scope "/", HelloWeb do
    pipe_through :browser

    resources "/users", UserController
  end

  scope "/", AnotherAppWeb do
    pipe_through :browser

    resources "/posts", PostController
  end
  ...
end

如果我們複製路由(表示有兩個路由有相同路徑),我們會收到熟悉的警告訊息

warning: this clause cannot match because a previous clause at line 16 always matches

佇列

在本指南中,我們已經走了很長一段路,卻沒有提到路由器中我們看到的首列指令之一:pipe_through :browser。現在是時候來修正這個問題了。

管道是一系列可以附加到特定範圍的插件。如果您不熟悉插件,我們提供一個深入指南來深入了解它們。

路由在範圍內定義,而範圍可以透過多個管道。一旦路由相符,Phoenix 會呼叫與該路由相關的所有管道中定義的所有插件。例如,存取 / 會透過 :browser 管道,進而呼叫其所有插件。

Phoenix 預設定義了兩個管道,:browser:api,可用於許多常見的工作。反過來,我們可以自訂它們並建立新管道以滿足我們的需求。

:browser:api 管道

顧名思義,:browser 管道是針對提供瀏覽器要求的路由準備的,而 :api 管道是針對產生 API 資料的路由準備的。

:browser 管道有六個插件:plug :accepts, ["html"] 定義可接受的請求格式或格式。:fetch_session 自然會擷取會話資料,並在連線中提供該資料。:fetch_live_flash 會從 LiveView 擷取任何閃存訊息並將它們與控制器閃存訊息合併。然後,:put_root_layout 插件會儲存根部配置以進行呈現。稍後,:protect_from_forgery:put_secure_browser_headers 會保護表單貼文免於跨站點偽造。

目前,:api 管道只定義了 plug :accepts, ["json"]

路由器在範圍內定義的路由上呼叫管道。範圍外的路徑沒有管道。雖然不建議使用巢狀範圍(請參閱上述版本化 API 範例),如果我們在巢狀範圍內呼叫 pipe_through,路由器會從父範圍呼叫所有 pipe_through,然後再呼叫巢狀範圍內的 pipe_through

以上是許多字彙集合在一起。讓我們來看一些範例,以釐清其含義。

以下是來自新產生的 Phoenix 應用程式的路由的另一種檢視方式,這次是將 /api 範圍取消註解,並新增一個路由。

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloWeb do
    pipe_through :browser

    get "/", PageController, :home
  end

  # Other scopes may use custom stacks.
  scope "/api", HelloWeb do
    pipe_through :api

    resources "/reviews", ReviewController
  end
  # ...
end

當伺服器接受請求時,請求將始終先通過端點中的插件,然後它會嘗試匹配路徑和 HTTP 動詞。

假設這個請求符合我們的路由第一個:GET 至 /。路由器會先透過 :browser 管線轉發這個請求 - 會擷取 session 資料,擷取快閃訊息,並執行偽造保護 - 在它將請求傳送至 PageControllerhome 動作之前。

相反地,假設請求符合由 resources/2 巨集定義的任一路由。在這種情況下,路由器會透過 :api 管線轉發它 — 目前只執行內容協商 — 在它進一步傳送至 HelloWeb.ReviewController 的正確動作之前。

如果沒有任何路由符合,則不呼叫任何管線,並引發 404 錯誤。

建立新管線

Phoenix 讓我們在路由器中的任何位置建立自訂管線。要建立新管線,我們需要呼叫 pipeline/2 巨集,搭配下列引數:一個原子用於新管線的名稱,以及具有所有我們想要的插頭的區塊。

defmodule HelloWeb.Router do
  use HelloWeb, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_live_flash
    plug :put_root_layout, html: {HelloWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :auth do
    plug HelloWeb.Authentication
  end

  scope "/reviews", HelloWeb do
    pipe_through [:browser, :auth]

    resources "/", ReviewController
  end
end

以上假設存在稱為 HelloWeb.Authentication 的插頭,用於執行身份驗證,並且現在是 :auth 管線的一部分。

請注意管線本身是插頭,因此我們可以將管線插入另一個管線。例如,我們可以重新撰寫上述 auth 管線,自動呼叫 browser,簡化下游管線呼叫

  pipeline :auth do
    plug :browser
    plug :ensure_authenticated_user
    plug :ensure_user_owns_review
  end

  scope "/reviews", HelloWeb do
    pipe_through :auth

    resources "/", ReviewController
  end

如何整理我的路由?

在 Phoenix 中,我們傾向定義一些管線,提供特定的功能。例如,:browser:api 管線分別旨在供特定用戶端、瀏覽器和 http 用戶端存取。

也許更重要的是,定義專門用於身份驗證和授權的管線也非常常見。例如,您可能有一個管線需要所有用戶認證過。另一個管線可能強制只有管理員用戶才能存取某些路由。

定義管線後,您可以在所需的範圍內重複使用管線,將路由群組圍繞各自的管線。例如,回到我們的評分範例。假設任何人都可以閱讀評分,但只有經過身份驗證的用戶才能建立評分。您的路由可以長這樣

pipeline :browser do
  ...
end

pipeline :auth do
  plug HelloWeb.Authentication
end

scope "/" do
  pipe_through [:browser]

  get "/reviews", PostController, :index
  get "/reviews/:id", PostController, :show
end

scope "/" do
  pipe_through [:browser, :auth]

  get "/reviews/new", PostController, :new
  post "/reviews", PostController, :create
end

請注意上述路由是如何拆分為不同範圍的。起初分離時會令人困惑,但也有一個很大的好處,那就是可以很輕鬆地檢查路由,並查看所有路由,例如哪些需要驗證,哪些不需要。這有助於審核並確保路由具有正確的範圍。

您可以建立任意多或少的範圍。由於管道可以在範圍內重複使用,因此有助於封裝通用功能,並且可以在定義的每一個範圍中依需要組合它們。

轉送

可以使用 Phoenix.Router.forward/4 巨集,將從特定路徑開始的所有要求傳送至特定插頭。假設我們有一個系統的部分負責執行背景工作(甚至可以是另一個應用程式或程式庫),它可以有自己的網路介面來檢查工作狀態。我們可以使用以下方式轉送至這個管理介面

defmodule HelloWeb.Router do
  use HelloWeb, :router

  ...

  scope "/", HelloWeb do
    ...
  end

  forward "/jobs", BackgroundJob.Plug
end

這表示所有從 /jobs 開始的路由都將傳送至 HelloWeb.BackgroundJob.Plug 模組。在插頭內,您可以在子路由上進行比對,例如 /pending/active,它們會顯示某些工作的狀態。

我們甚至可以將 forward/4 巨集與管道混合。如果我們要確保使用者已經過驗證而且是管理員,才能查看工作頁面,我們可以在路由中使用下列指令。

defmodule HelloWeb.Router do
  use HelloWeb, :router

  ...

  scope "/" do
    pipe_through [:authenticate_user, :ensure_admin]
    forward "/jobs", BackgroundJob.Plug
  end
end

這表示 authenticate_userensure_admin 管道中的插頭會在 BackgroundJob.Plug 之前被呼叫,允許它們傳送適當的回應,並適當地暫停要求。

模組插頭的 init/1 回呼中接收到的 opts 可以作為第三個引數傳遞。例如,背景工作可能會讓您設定應用程式名稱在頁面上顯示。這可以使用下列方式傳遞

forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix"

可以傳遞第四個 router_opts 引數。在 Phoenix.Router.scope/2 文件中概述了這些選項。

BackgroundJob.Plug 可以作為 Plug 指南 中討論的任何模組插頭實作。但請注意,不建議轉發至另一個 Phoenix 端點。這是因為您的應用程式定義的插頭和轉發的端點會被呼叫兩次,這可能會導致錯誤。

摘要

路由是一個重要的主題,我們在這裡已經涵蓋了很多內容。從本指南中獲得的重要觀念是

  • 以 HTTP 動作名稱開頭的路由會擴充至比對函數的單一子句。
  • resources 宣告的路由會擴充到配對函數的 8 個子句。
  • 資源可以使用 only:except: 選項限制配對函數子句的數量。
  • 任何這些路由都可能是巢狀的。
  • 任何這些路由都可以範圍限定在既定的路徑。
  • 使用 ~p 的驗證路由進行編譯時路由檢查