§HTTP 路由
§内置 HTTP 路由器
路由器是将每个传入的 HTTP 请求转换为操作调用的组件(控制器类中的公共方法)。
HTTP 请求被 MVC 框架视为一个事件。此事件包含两条主要信息
- 请求路径(例如
/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 文件中使用的每个控制器,路由器将在 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 网关重写时出现问题。使用相对路由的一些有用示例包括
- 将应用程序托管在 Web 网关后面,该网关将所有路由都加上前缀,而不是在您的
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://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 结果
在此文档中发现错误?此页面的源代码可以在 这里 找到。在阅读了 文档指南 之后,请随时贡献一个拉取请求。有疑问或建议要分享?请访问 我们的社区论坛,与社区开始对话。