文件

§HTTP 路由

§內建 HTTP 路由器

路由器是負責將每個傳入 HTTP 請求轉換為 Action 的元件。

MVC 架構將 HTTP 請求視為事件。此事件包含兩大資訊

路由定義在經過編譯的 conf/routes 檔案中。這表示您會直接在瀏覽器中看到路由錯誤

§依賴注入

Play 的預設路由產生器會建立一個路由器類別,該類別接受在 @Inject 註解的建構函式中控制項執行個體。這表示該類別適合與依賴注入搭配使用,也可以使用建構函式手動建立執行個體。

在 Play 2.7.0 之前,Play 支援靜態路由產生器,允許將控制項定義為 object 而不是 class。由於 Play 不再依賴靜態狀態,因此不再支援此功能。如果您希望使用自己的靜態狀態,您仍然可以在 class 控制項中執行此操作。

§路由檔案語法

conf/routes 是路由器使用的設定檔。此檔案列出應用程式所需的所有路由。每個路由包含 HTTP 方法和 URI 模式,兩者都與對 Action 產生器的呼叫相關聯。

讓我們看看路由定義的樣子

GET   /clients/:id          controllers.Clients.show(id: Long)

每個路由都從 HTTP 方法開始,接著是 URI 模式。最後一個元素是呼叫定義。

您也可以使用 # 字元在路由檔案中加入註解。

# Display a client.
GET   /clients/:id          controllers.Clients.show(id: Long)

您可以透過使用「->」後接指定的前置詞,來告訴路由檔案使用特定前置詞下的不同路由器

->      /api                        api.MyRouter

這在與 字串內插路由 DSL(也稱為 SIRD 路由)結合使用時特別有用,或是在處理使用多個路由檔案進行路由的 子專案 時。

也可以透過在路由前面加上從 + 開始的一行,來套用修改器。這可以改變特定 Play 元件的行為。其中一個這樣的修改器是「nocsrf」修改器,用來略過 CSRF 過濾器

+ nocsrf
POST  /api/new              controllers.Api.newThing

§HTTP 方法

HTTP 方法可以是 HTTP 支援的任何有效方法(GETPATCHPOSTPUTDELETEHEAD)。

§URI 模式

URI 模式定義路由的請求路徑。請求路徑的部分可以是動態的。

§靜態路徑

例如,要完全符合傳入的 GET /clients/all 請求,您可以定義這個路由

GET   /clients/all          controllers.Clients.list()

§動態部分

如果您想要定義一個透過 ID 擷取客戶端的路由,您需要加入一個動態部分

GET   /clients/:id          controllers.Clients.show(id: Long)

注意:一個 URI 模式可以有多個動態部分。

動態部分的預設比對策略由正規表示式 [^/]+ 定義,表示定義為 :id 的任何動態部分都將完全比對一個 URI 路徑區段。與其他模式類型不同,路徑區段在路由中會自動解碼 URI,在傳遞給您的控制器之前,並在反向路由中編碼。

§動態區段跨越多個/

如果你想要動態區段擷取多個 URI 路徑區段,並以正斜線分隔,你可以使用 *id 語法定義動態區段,又稱為萬用字元模式,它使用 .* 正規表示式

GET   /files/*name          controllers.Application.download(name)

在此,對於類似 GET /files/images/logo.png 的請求,name 動態區段會擷取 images/logo.png 值。

請注意,跨越多個 / 的動態區段不會由路由器解碼或由反向路由器編碼。你有責任驗證原始 URI 區段,就像你會對任何使用者輸入進行驗證一樣。反向路由器只會執行字串串接,所以你需要確保產生的路徑有效,而且不會包含多個前導斜線或非 ASCII 字元。

§使用自訂正規表示式的動態區段

你也可以使用 $id<regex> 語法,為動態區段定義自己的正規表示式

GET   /items/$id<[0-9]+>    controllers.Items.show(id: Long)

就像萬用字元路由一樣,參數不會由路由器解碼或由反向路由器編碼。你有責任驗證輸入,以確保它在那個內容中是有意義的。

§呼叫動作產生器方法

路由定義的最後一部分是呼叫。這部分必須定義對傳回 play.api.mvc.Action 值的方法的有效呼叫,這通常會是控制器動作方法。

如果該方法未定義任何參數,只需提供完全限定的方法名稱

GET   /                     controllers.Application.homePage()

如果動作方法定義了一些參數,所有這些參數值都將在請求 URI 中搜尋,從 URI 路徑本身或查詢字串中擷取。

# Extract the page parameter from the path.
GET   /:page                controllers.Application.show(page)

# Extract the page parameter from the query string.
GET   /                     controllers.Application.show(page)

這是對應的,controllers.Application 控制器中的 show 方法定義

def show(page: String) = Action {
  loadContentFromDatabase(page)
    .map { htmlContent => Ok(htmlContent).as("text/html") }
    .getOrElse(NotFound)
}

§參數類型

對於 String 類型的參數,參數類型是可選的。如果您希望 Play 將輸入參數轉換為特定的 Scala 類型,您可以明確地輸入參數

GET   /clients/:id          controllers.Clients.show(id: Long)

並在 controllers.Clients 控制器中對應的 show 方法定義中執行相同的操作

def show(id: Long) = Action {
  Client
    .findById(id)
    .map { client => Ok(views.html.Clients.display(client)) }
    .getOrElse(NotFound)
}

Play 支援以下參數類型

如果您有不同的類型並想要實作它,您可以查看 請求繫結器

§具有固定值的參數

有時您會希望對參數使用固定值

# Extract the page parameter from the path, or fix the value for /
GET   /                     controllers.Application.show(page = "home")
GET   /:page                controllers.Application.show(page)

§具有預設值的參數

您還可以提供預設值,如果在輸入請求中找不到值,將使用此預設值

# Pagination links, like /clients?page=3
GET   /clients              controllers.Clients.list(page: Int ?= 1)

§可選參數

您還可以指定一個可選參數,它不必存在於所有請求中

# The version parameter is optional. E.g. /api/list-all?version=3.0
GET   /api/list-all         controllers.Api.list(version: Option[String])

§清單參數

您還可以為重複的查詢字串參數指定清單參數

# The item parameter is a list.
# E.g. /api/list-items?item=red&item=new&item=slippers
GET   /api/list-items      controllers.Api.listItems(item: List[String])
# or
# E.g. /api/list-int-items?item=1&item=42
GET   /api/list-int-items  controllers.Api.listIntItems(item: List[Int])

§路由優先順序

許多路由可以符合相同的請求。如果發生衝突,將使用第一個路由(按宣告順序)。

§反向路由

路由器也可從 Scala 呼叫內產生 URL。這使得您能將所有 URI 模式集中在單一設定檔中,讓您在重構應用程式時更能放心。

對於路由檔案中使用的每個控制器,路由器會在 routes 套件中產生一個「反向控制器」,具有相同的動作方法、相同的簽章,但傳回 play.api.mvc.Call,而非 play.api.mvc.Action

play.api.mvc.Call 定義 HTTP 呼叫,並提供 HTTP 方法和 URI。

例如,如果您建立一個控制器如下

package controllers
  import javax.inject.Inject

  import play.api._
  import play.api.mvc._

  class Application @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
    def hello(name: String) = Action {
      Ok("Hello " + name + "!")
    }
  }

如果您在 conf/routes 檔案中對其進行對應

# Hello action
GET   /hello/:name          controllers.Application.hello(name)

接著,您可以使用 controllers.routes.Application 反向控制器,反向 hello 動作方法的 URL

// Redirect to /hello/Bob
def helloBob = Action {
  Redirect(routes.Application.hello("Bob"))
}

注意:每個控制器套件都有 routes 子套件。因此,動作 controllers.Application.hello 可透過 controllers.routes.Application.hello 反向(只要路由檔案中沒有其他路由先與產生的路徑相符)。

反向動作方法運作相當簡單:它會取得您的參數,並將它們代回路由模式。在路徑區段 (:foo) 的情況下,會在代換前對值進行編碼。對於正規表示式和萬用字元模式,會以原始形式代換字串,因為值可能橫跨多個區段。傳遞給反向路由時,請務必依需要對這些元件進行跳脫,並避免傳遞未驗證的使用者輸入。

§相對路由

在回傳相對路徑而非絕對路徑時,可能會有些情況是有用的。play.mvc.Call 回傳的路徑永遠都是絕對路徑(以 / 開頭),當您的網頁應用程式的要求被 HTTP 代理伺服器、負載平衡器和 API 閘道重寫時,可能會導致問題。使用相對路徑會很有用的範例包括

要能夠產生相對路徑,您需要知道要讓目標路徑相對應到什麼(開始路徑)。開始路徑可以從目前的 RequestHeader 擷取。因此,要產生相對路徑,您必須傳入目前的 RequestHeader 或開始路徑作為 String 參數。

例如,給定像這樣的控制器端點

package controllers

import javax.inject._

import play.api.mvc._

@Singleton
class Relative @Inject() (cc: ControllerComponents) extends AbstractController(cc) {
  def helloview: Action[AnyContent] = Action { implicit request => Ok(views.html.hello("Bob")) }

  def hello(name: String): Action[AnyContent] = Action {
    Ok(s"Hello $name!")
  }
}

注意:目前的請求會透過宣告 implicit request 而隱含傳遞給檢視範本

如果您在 conf/routes 檔案中對其進行對應

GET     /foo/bar/hello              controllers.Relative.helloview
GET     /hello/:name                controllers.Relative.hello(name)

然後您可以使用反向路由器定義相對路徑,就像以前一樣,並包含額外的 relative 呼叫

@(name: String)(implicit request: RequestHeader)

<h1>Hello @name</h1>

<a href="@routes.Relative.hello(name)">Absolute Link</a>
<a href="@routes.Relative.hello(name).relative">Relative Link</a>

注意:從控制器傳遞的 Request 會轉換成 RequestHeader,並在檢視參數中標記為 implicit。然後會隱含地傳遞給 relative 的呼叫

在請求 /foo/bar/hello 時,產生的 HTML 將會看起來像這樣

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Bob</title>
    </head>
    <body>
      <a href="/hello/Bob">Absolute Link</a>
      <a href="../../hello/Bob">Relative Link</a>
    </body>
</html>

§預設控制器

Play 包含一個 Default 控制器,它提供一些有用的動作。這些動作可以直接從路由檔案呼叫

# Redirects to https://play.dev.org.tw/ with 303 See Other
GET   /about      controllers.Default.redirect(to = "https://play.dev.org.tw/")

# Responds with 404 Not Found
GET   /orders     controllers.Default.notFound

# Responds with 500 Internal Server Error
GET   /clients    controllers.Default.error

# Responds with 501 Not Implemented
GET   /posts      controllers.Default.todo

在此範例中,GET /about 會重新導向到外部網站,但也可以重新導向到另一個動作(例如上述範例中的 /posts)。

§自訂路由

Play 提供了一個用於定義嵌入式路由的 DSL,稱為字串內插路由 DSL,簡稱 SIRD。此 DSL 有許多用途,包括嵌入輕量級 Play 伺服器、為一般 Play 應用程式提供自訂或更進階的路由功能,以及模擬 REST 服務以進行測試。

請參閱 字串內插路由 DSL

下一頁:處理 HTTP 結果


在此文件發現錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。