文档

§HTTP 路由

§内置 HTTP 路由器

路由器是将每个传入的 HTTP 请求转换为操作调用的组件(控制器类中的公共方法)。

HTTP 请求被 MVC 框架视为一个事件。此事件包含两条主要信息

路由在 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 文件中使用的每个控制器,路由器将在 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 反转(只要在 routes 文件中之前没有其他路由恰好匹配生成的路径)。

反向操作方法的工作原理非常简单:它接受您的参数并将它们替换回路由模式。对于路径段(:foo),在进行替换之前对值进行编码。对于正则表达式和通配符模式,字符串以原始形式进行替换,因为值可能跨越多个段。在将它们传递给反向路由时,确保您按需转义这些组件,并避免传递未经验证的用户输入。

§相对路由

在某些情况下,返回相对路由而不是绝对路由可能很有用。由 play.mvc.Call 返回的路由始终是绝对的(它们以 / 开头),这会导致当对您的 Web 应用程序的请求被 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://playframework.com.cn/ with 303 See Other
GET   /about      controllers.Default.redirect(to = "https://playframework.com.cn/")

# 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 结果


在此文档中发现错误?此页面的源代码可以在 这里 找到。在阅读了 文档指南 之后,请随时贡献一个拉取请求。有疑问或建议要分享?请访问 我们的社区论坛,与社区开始对话。