文档

§HTTP 路由

§内置 HTTP 路由器

路由器是负责将每个传入的 HTTP 请求转换为 Action 的组件。

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

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

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

§相对路由

在某些情况下,返回相对路由而不是绝对路由可能很有用。play.mvc.Call 返回的路由始终是绝对的(它们以 / 开头),这会导致 HTTP 代理、负载均衡器和 API 网关重写对您的 Web 应用程序的请求时出现问题。使用相对路由会很有用的几个示例包括

为了能够生成相对路由,您需要知道将目标路由相对于哪个路由(起始路由)。起始路由可以从当前的 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://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 /about 重定向到外部网站,但也可以重定向到另一个操作(例如,上面的示例中的 /posts)。

§自定义路由

Play 提供了一个用于定义嵌入式路由器的 DSL,称为字符串插值路由 DSL,简称 SIRD。此 DSL 有很多用途,包括嵌入轻量级 Play 服务器,为常规 Play 应用程序提供自定义或更高级的路由功能,以及模拟 REST 服务以进行测试。

参见 字符串插值路由 DSL

下一步:操作 HTTP 结果


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