§HTTP 路由
§內建 HTTP 路由器
路由器是負責將每個傳入 HTTP 請求轉換為 Action 的元件。
MVC 架構將 HTTP 請求視為事件。此事件包含兩大資訊
- 請求路徑(例如
/clients/1542
、/photos/list
),包含查詢字串 - HTTP 方法(例如
GET
、POST
、…)。
路由定義在經過編譯的 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 支援的任何有效方法(GET
、PATCH
、POST
、PUT
、DELETE
、HEAD
)。
§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 支援以下參數類型
- 字串
- 整數
- 長整數
- 雙精度浮點數
- 單精度浮點數
- 布林值
- UUID
- 其他支援類型的 AnyVal 包裝器
如果您有不同的類型並想要實作它,您可以查看 請求繫結器
§具有固定值的參數
有時您會希望對參數使用固定值
# 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 閘道重寫時,可能會導致問題。使用相對路徑會很有用的範例包括
- 將應用程式主機在網頁閘道之後,而該閘道會在所有路徑之前加上
conf/routes
檔案中設定以外的字串,並將您的應用程式根目錄設定在它預期以外的路徑。 - 在動態呈現樣式表時,您需要資產連結為相對路徑,因為它們最後可能會由 CDN 從不同的 URL 提供服務。
要能夠產生相對路徑,您需要知道要讓目標路徑相對應到什麼(開始路徑)。開始路徑可以從目前的 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 結果
在此文件發現錯誤?此頁面的原始碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。