§HTTP 路由
§內建 HTTP 路由器
路由器是將每個傳入 HTTP 要求轉換為動作呼叫(控制器類別中的公開方法)的元件。
MVC 架構會將 HTTP 要求視為事件。此事件包含兩項主要資訊
- 要求路徑(例如
/clients/1542
、/photos/list
),包括查詢字串。 - HTTP 方法(GET、POST、…)。
路由定義在已編譯的 conf/routes
檔案中。這表示您會直接在瀏覽器中看到路由錯誤
§依賴注入
Play 的預設路由產生器會建立一個路由器類別,它會在加註 @Inject
的建構函式中接受控制器執行個體。這表示該類別適合與依賴注入搭配使用,也可以使用建構函式手動建立執行個體。
在 Play 2.7.0 之前,Play 支援靜態路由產生器,它支援將動作定義為 static
方法。由於 Play 不再依賴靜態狀態,因此不再支援此功能。如果您想使用自己的靜態狀態,您仍可以使用控制器中的執行個體方法。
§路由檔案語法
conf/routes
是路由器使用的組態檔案。此檔案會列出應用程式所需的所有路由。每個路由都包含一個 HTTP 方法和與動作方法呼叫相關聯的 URI 模式。
我們來看看路由定義的樣子
GET /clients/:id controllers.Clients.show(id: Long)
注意:在動作呼叫中,參數類型會出現在參數名稱之後,就像在 Scala 中一樣。
每個路由都從 HTTP 方法開始,接著是 URI 模式。路由的最後一個元素是呼叫定義。
您也可以使用 #
字元將註解新增到路由檔案
# Display a client.
GET /clients/:id controllers.Clients.show(id: Long)
也可以透過在路由前面加上以 +
開頭的行來套用修改器。這可以變更某些 Play 組件的行為。其中一個這樣的修改器是「nocsrf」修改器,用於繞過 CSRF 篩選器
+ nocsrf
POST /api/new controllers.Api.newThing()
§HTTP 方法
HTTP 方法可以是 HTTP 支援的任何有效方法(GET
、PATCH
、POST
、PUT
、DELETE
、HEAD
、OPTIONS
)。
§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)
就像萬用字元路由一樣,參數不會由路由器解碼或由反向路由器編碼。您有責任驗證輸入,以確保在該內容中是有意義的。
§呼叫動作產生器方法
路由定義的最後一部分是呼叫。此部分必須定義對動作方法的有效呼叫。
如果方法未定義任何參數,請只提供完全限定的方法名稱
GET / controllers.Application.homePage()
如果動作方法定義參數,則會在請求 URI 中搜尋對應的參數值,從 URI 路徑本身或查詢字串中擷取。
# Extract the page parameter from the path.
# i.e. http://myserver.com/index
GET /:page controllers.Application.show(page)
或
# Extract the page parameter from the query string.
# i.e. http://myserver.com/?page=index
GET / controllers.Application.show(page)
以下是 controllers.Application
控制器中對應的 show
方法定義
public Result show(String page) {
String content = Page.getContentOf(page);
return ok(content).as("text/html");
}
§參數類型
對於類型為 String
的參數,參數類型是可選的。如果您希望 Play 將輸入參數轉換為特定的 Scala 類型,您可以新增明確的類型
GET /clients/:id controllers.Clients.show(id: Long)
然後在控制器中對應的動作方法參數使用相同的類型
public Result show(Long id) {
Client client = clientService.findById(id);
return ok(views.html.Client.show(client));
}
注意:參數類型使用後綴語法指定。此外,通用類型使用
[]
符號指定,而不是像 Java 中的<>
。例如,List[String]
與 Java 中的List<String>
類型相同。
§具有固定值的參數
有時您會希望對參數使用固定值
# 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 ?= null)
# or
GET /api/list-all controllers.Api.listOpt(version: java.util.Optional[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: java.util.List[String])
# or
# E.g. /api/list-int-items?item=1&item=42
GET /api/list-int-items controllers.Api.listIntItems(item: java.util.List[Integer])
§將目前的請求傳遞給動作方法
您也可以將目前的請求傳遞給動作方法。只要將其新增為參數即可
GET / controllers.Application.dashboard(request: Request)
以及對應的動作方法
public Result dashboard(Http.Request request) {
return ok("Hello, your request path " + request.path());
}
Play 會自動偵測類型為 Request
的路由參數(這是 play.mvc.Http.Request
的匯入),並將實際的請求傳遞到對應的動作方法的參數中。當然,您可以將 Request
參數與其他參數混合,而且 Request
參數位於哪個位置並不重要。
§路由優先順序
許多路由可以符合同一個請求。如果發生衝突,會使用第一個路由(依據宣告順序)。
§反向路由
路由器可以用於從 Java 呼叫中產生一個 URL。這使得您可以在單一設定檔中集中所有 URI 模式,因此在重構應用程式時可以更有信心。
對於路由檔案中使用的每個控制器,路由器會在 routes
套件中產生一個「反向控制器」,具有相同的動作方法、相同的簽章,但傳回 play.mvc.Call
而不是 play.mvc.Result
。
play.mvc.Call
定義一個 HTTP 呼叫,並提供 HTTP 方法和 URI。
例如,如果您建立一個像這樣的控制器
package controllers;
import play.*;
import play.mvc.*;
public class Application extends Controller {
public Result hello(String name) {
return ok("Hello " + name + "!");
}
}
如果您在 conf/routes
檔案中對應它
# Hello action
GET /hello/:name controllers.Application.hello(name)
您接著可以使用 controllers.routes.Application
反向控制器,反向 hello
動作方法的 URL
// Redirect to /hello/Bob
public Result index() {
return redirect(controllers.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 play.*;
import play.mvc.*;
public class Relative extends Controller {
public Result helloview(Http.Request request) {
ok(views.html.hello.render("Bob", request));
}
public Result hello(String name) {
return ok("Hello " + name + "!");
}
}
如果您在 conf/routes
檔案中對應它
GET /foo/bar/hello controllers.Relative.helloview(request: Request)
GET /hello/:name controllers.Relative.hello(name)
然後您可以使用反向路由器定義相對路徑,就像之前一樣,並包含對 relativeTo(play.mvc.RequestHeader requestHeader)
的額外呼叫
@(name: String, request: Http.RequestHeader)
<h1>Hello @name</h1>
<a href="@routes.Relative.hello(name)">Absolute Link</a>
<a href="@routes.Relative.hello(name).relativeTo(request)">Relative Link</a>
注意:從控制器傳遞的
Http.Request
會在檢視參數中轉換成Http.RequestHeader
。
當要求 /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 /
會重新導向到外部網站,但也可以重新導向到另一個動作(例如,上述範例中的 /posts
)。
§進階路由
請參閱 路由 DSL。
下一步:處理 HTTP 結果
在這個文件中發現錯誤?這個頁面的原始程式碼可以在 這裡 找到。在閱讀 文件指南 後,請隨時提交拉取請求。有問題或建議要分享?請前往 我們的社群論壇 與社群展開對話。