§HTTP 路由
§内置 HTTP 路由器
路由器是负责将每个传入的 HTTP 请求转换为 Action 的组件。
HTTP 请求被 MVC 框架视为一个事件。此事件包含两条主要信息
- 请求路径(例如
/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 支持以下参数类型
- String
- Int
- Long
- Double
- Float
- Boolean
- 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 文件中使用的每个控制器,路由器将在 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 应用程序的请求时出现问题。使用相对路由会很有用的几个示例包括
- 如果您的应用程序托管在 Web 网关后面,并且该网关在所有路由前添加了除
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://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 结果
在此文档中发现错误?此页面的源代码可以在 此处 找到。阅读完 文档指南 后,请随时贡献拉取请求。有疑问或建议要分享?转到 我们的社区论坛 与社区开始对话。