文件

§HTTP 路由

§內建 HTTP 路由器

路由器是將每個傳入 HTTP 要求轉換為動作呼叫(控制器類別中的公開方法)的元件。

MVC 架構會將 HTTP 要求視為事件。此事件包含兩項主要資訊

路由定義在已編譯的 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 支援的任何有效方法(GETPATCHPOSTPUTDELETEHEADOPTIONS)。

§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 閘道重寫時,可能會導致問題。使用相對路徑會很有用的範例包括

要能夠產生相對路徑,您需要知道要讓目標路徑相對於什麼(起始路徑)。起始路徑可以從目前的 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 結果


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