内置HTTP router
http router负责将HTTP请求翻译为Action。
MVC框架将每个HTTP请求视作一个事件。此事件包含两个主要部分:
- 请求路径(e.g. /clients/1542, /photos/list)
- HTTP方法(e.g. GET,POST,...)
路由在编译的 conf/routes 文件中定义。这意味着你将在浏览器中直接看到路由错误。
依赖注入
Play默认的路由生产器会创建一个路由类,产生的路由类在@Inject注解标记的构造函数中接受controller实例作为参数。这意味着它既能使用依赖注入,也能使用该构造函数来手动创建。
Play同时提供一个遗留的静态路由产生器,用于那些以object形式声明的controller。这并不是推荐的做法,因为它破坏了封装,让代码更加难以测试,而且也无法兼容Play的很多新API。
如果你需要使用静态controller,你可以通过在build.sbt中增加如下配置来启用静态路由器:
routesGenerator := StaticRoutesGenerator
Play文档中的例子假定你使用了注入式路由产生器。如果你想尝试静态路由,那么你可以在controller调用时增加@symbol注解前缀,或者直接将controller声明为object。
路由文件语法
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
这种方式在和结合使用时特别有用。如项目中包含子项目,且有多个路由文件时。
路由配置文件还有一种以+开头特殊配置。它可以用来改变Play组件的特定行为。如下面的 “nocsrf” 可以用来旁路:
+ nocsrfPOST /api/new controllers.Api.newThing
HTTP方法
支持HTTP协议本身支持的方法(GET,PATCH,POST,PUT,DELETE,HEAD)。
URI模式
URI模式定义了路由的请求路径。其中部分请求路径可以是动态的。
静态路径
举例来说,要精确匹配 GET /clients/all 请求,你可以定义如下路由:
GET /clients/all controllers.Clients.list()
动态部分
比如你想通过ID获取一个client,你可以在URI中增加如下动态部分:
GET /clients/:id controllers.Clients.show(id: Long)
注意:一个URI模式可以有多个动态部分。
默认的匹配策略由一个正则表达式定义 [^/]+,意味着任意的动态部分如 :id 将会被精确地匹配到一个URI路径段中。有别于其他模式类型,此路径段在传入你的controller前将自动URI解码,反向时也将自动编码。
跨多级 / 的动态路由
如果你希望一个动态部分可以匹配到多于一个的、由正斜杠分开的URI路径段,可以你用 * 通配符:
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)
正如通配路由一样,参数将不会变自动编解码。校验交由开发者自己进行。
调用Action生成器方法
路由定义的最后是方法调用。一个正确定义的方法调用需要返回一个 play.api.mvc.Action 值,通常情况下这是一个 controller方法。
如果方法没有定义任何参数,直接给出全限定方法名就好:
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类型的参数类型标记是可选的。如果希望Scala将输出的参数转换为特定的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)}
固定值的参数
有些时候需要使用固定值的参数:
# Extract the page parameter from the path, or fix the value for /GET / controllers.Application.show(page = "home")GET /:page controllers.Application.show(page)
默认值的参数
在请求中没有找到参数值时,Play可以使用你提供的默认值:
# Pagination links, like /clients?page=3GET /clients controllers.Clients.list(page: Int ?= 1)
可选参数
可以按照如下方式定义可选参数:
# The version parameter is optional. E.g. /api/list-all?version=3.0GET /api/list-all controllers.Api.list(version: Option[String])
路由优先级
多个路由可以匹配到同一个请求。如果遇到路由冲突的情况,第一个声明的路由将被使用。
反向路由
路由器也可以在Scala的方法调用中用来生成URL。它使得你可以将所有URI规则集中到一个配置文件中,这使得你在重构应用时更加清晰。
对于route文件中的每个controller,都会在此 routes 包中生成一个 ‘反向controller’,它们拥有同样的action方法,同样的签名,但是返回一个play.api.mvc.Call,而不是play.api.mvc.Action。
play.api.mvc.Call 定义一个HTTP调用,提供了HTTP方法和URI。
举例来说,如果你的controller长这样:
package controllersimport javax.inject.Injectimport 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 actionGET /hello/:name controllers.Application.hello(name)
你可以通过 controllers.routes.Application 反向控制器将URL反向为一个hello action方法:
// Redirect to /hello/Bobdef helloBob = Action { Redirect(routes.Application.hello("Bob"))}
注意:每个controller包下都有一个routes子包。 故 action controllers.Application.hello 可以通过 controllers.routes.Application.hello 来反向。
反向action的工作很简单:获取参数,并将它们替换入路由模板。比如对于(:foo)形式的路由段来说,它的值在替换前会被编码。对于正则和通配模式来说,字符串会以原始形式来替换,因为可能会跨多个路由段。因此在将参数传递到反向路由时,需要确保按照要求来escape,并过滤掉用户无效的输入。
相对路由
有些时候,相对路由比绝对路由更有用。play.mvc.Call 返回的路由永远是绝对的(既由 / 开始),这在访问你的web应用的请求被HTTP代理、负载均衡器、或者API网关重写时会导致问题。一些有用的相对路由例子如下:
- 应用程序托管在一个web网关之后,它会为所有路由加上 conf/routes 文件中没有配置的前缀内容,将你的应用置于不期望的路由上。
- 动态渲染样式表,你必须使用相对链接,因为它们可能通过CDN最终从不同的URL来提供服务。
为了生成相对路由,你需要知道相对于绝对路由的路径(start route)。你可以从当前的 RequestHeader中获取start route。因此,生成相对路由需要传入当前的RequestHeader或者String形式的start route参数。
以下面的controller endpoint为例:
package controllersimport javax.inject._import play.api.mvc._@Singletonclass Relative @Inject()(cc: ControllerComponents) extends AbstractController(cc) { def helloview() = Action { implicit request => Ok(views.html.hello("Bob")) } def hello(name: String) = Action { Ok(s"Hello $name!") }}
注意:当前request被隐式地传递给视图模板。
conf/routes 文件如下配置:
GET /foo/bar/hello controllers.Relative.helloviewGET /hello/:name controllers.Relative.hello(name)
你可以使用前面的反向路由,并包含一个额外的 relative 调用:
@(name: String)(implicit request: RequestHeader)Hello @name
Absolute LinkRelative Link
注意:从 controller 传入的 Request 被转换为 RequestHeader,并在视图中被标记为implicit。然后被隐式传给相对调用
当请求 /foo/bar/hello 时,生成的HTML看起来像下面这样:
Bob Absolute Link Relative Link
默认控制器
Play包含一个默认的控制器,提供了一些常用的工具。它们可以在route 文件中被直接引用:
# Redirects to https://www.playframework.com/ with 303 See OtherGET /about controllers.Default.redirect(to = "https://www.playframework.com/")# Responds with 404 Not FoundGET /orders controllers.Default.notFound# Responds with 500 Internal Server ErrorGET /clients controllers.Default.error# Responds with 501 Not ImplementedGET /posts controllers.Default.todo
上面的例子中, GET /about 重定向到一个外部页面,当然它也可以重定向到其它的action(如上面的 /posts例子)
自定义路由
Play提供了一个DSL用来定义内置的路由器,被称作String Interpolating Routing DSL,简写为sird。它有很多用途,如内置轻量级的Play server,提供自定义及更多高级路由功能,或者是用来mocking REST服务用来测试。