HTTP 路由

内置的HTTP路由

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

通过MVC框架,一个HTTP请求被视为一个事件。这个事件包含两个主要的信息:

  • 请求路径 (例如: /clients/1542, /photos/list), 包括查询字符串
  • HTTP 方法 (例如: GET, POST, 等).

路由定义在conf/routes 文件中, 它会被编译。也即你会直接在浏览器中看到路由错误:

""

依赖注入

Play 支持生成二种类型的路由, 一种是依赖注入路由, 另一种是静态路由。默认为静态路由, 但是如果你使用Play seed Activator模板来创建一个新Play应用程序, 你的项目会在build.sbt中包括以下配置,告诉项目使用注入路由:

routesGenerator := InjectedRoutesGenerator

Play的文档中的代码例子假定你使用的是注入路由生成器。如果你不使用这个, 你可以慢慢全面适应静态路由生成器的代码示例, 通过在路由定义尾部controller调用那一部分的前面添加一个@符号做为前缀, 或者每个controllers声明为object 而不是class

路由文件语法

路由的配置使用conf/routes 文件。这个文件列出应用程序中需要的所有路由。每个路由都包含了一个HTTP方法和 URI模式, 两者关联到一个Action 生成器调用。

让我们看看路由定义是什么样子的:

GET   /clients/:id          controllers.Clients.show(id: Long)

每个路由都以HTTP方法开头, 后面跟着URI模式。最后是方法调用的定义。

你还可以添加注释到路由文件, 用# 字符。

# 显示一个 client
GET   /clients/:id          controllers.Clients.show(id: Long)

HTTP 方法

HTTP 方法可以是HTTP支持的任何有效方法 (GET, POST, PUT, DELETE, HEAD).

URI 模式

URI 模式定义路由的请求路径。部分请求路径可以是动态的。

静态路径

举例, 为精确匹配传入的GET /clients/all 请求, 你可以定义这个路由:

GET   /clients/all          controllers.Clients.list()

动态路径

如果你想定义一个路由,它根据ID检索 client, 则需要添加一个动态部分:

GET   /clients/:id          controllers.Clients.show(id: Long)

注意:一个URI模式可以有多个动态部分。

动态部分的匹配策略是通过正则表达式[^/]+ 定义的, 这意味着任何动态部分定义为:id 只会匹配一个URI部分。

匹配跨越几个/ 符号的动态部分

如果你想一个动态部分匹配多个由斜杠分隔开的URI路径段, 你可以使用*id 语法来定义, 它使用.+ 正则表达式:

GET   /files/*name          controllers.Application.download(name)

像 GET /files/images/logo.png 这样的请求, 动态部分 name 匹配的是images/logo.png 值。

用自定义正则表达式匹配动态部分

你也可以为动态部分定义你自己的正则表达式, 使用$id<regex> 语法:

GET   /items/$id<[0-9]+>    controllers.Items.show(id: Long)

调用Action生成器方法

路由定义的最后一部分是方法调用。这个部分必须定义一个返回 play.api.mvc.Action 值的有效方法, 通常是一个 controller action 方法。

如果该方法不带任何参数, 则只需给出完整方法名:

GET   /                     controllers.Application.homePage()

如果action方法定义了一些参数, 所有这些参数则会在请求URI中查找, 不管是从URI路径自身提取,还是从查询字符串中查找。

# 从路径中提取 page 参数
GET   /:page                controllers.Application.show(page)

或者:

# 从查询字符串中提取 page 参数
GET   /                     controllers.Application.show(page)

这里是对应的, show 方法定义在controllers.Application controller中:

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)

相应的,show 方法同样定义在controllers.Clients controller:

def show(id: Long) = Action {
  Client.findById(id).map { client =>
    Ok(views.html.Clients.display(client))
  }.getOrElse(NotFound)
}

设定参数为固定值

有时候你会想为参数设定一个固定值:

# 从路径提取 page 参数, 或为 “/” 设定一个固定值 
GET   /                     controllers.Application.show(page = "home")
GET   /:page                controllers.Application.show(page)

设定参数默认值

您也可以提供一个默认值,当传入的请求中找不到任何相关的值时,就使用默认参数:

# 分页链接, 像 /clients?page=3
GET   /clients              controllers.Clients.list(page: Int ?= 1)

可选参数

你也可以指定一个可选参数,它不需要出现在所有请求中:

# version 参数是可选的。例如 /api/list-all?version=3.0
GET   /api/list-all         controllers.Api.list(version: Option[String])

路由优先级

多个路由可以匹配到同一个请求。如果有冲突, 第一个定义的路由(按声明的顺序)会被使用。

反向路由

路由也可以从Scala内部调用来生成URL。这样就可以将你所有的URI模式集中在一个单独的配置文件里, 让你在重构应用程序时更有把握。

对于路由文件中使用的每个controller, 路由会在routes 包中生成一个‘反向controller’ , 其中有同样的action方法和同样的签名, 但返回一个play.api.mvc.Call 而非play.api.mvc.Action

play.api.mvc.Call 定义了一个HTTP调用,并提供HTTP方法和URI。

举例, 如果你创建了一个像这样的controller:

package controllers

import play.api._
import play.api.mvc._

class Application extends Controller {

  def hello(name: String) = Action {
    Ok("Hello " + name + "!")
  }

}

并且在conf/routes 文件中设置它:

# Hello action
GET   /hello/:name          controllers.Application.hello(name)

你可以反转URL到hello action方法, 通过使用controllers.routes.Application 反向controller:

// Redirect to /hello/Bob
def helloBob = Action {
  Redirect(routes.Application.hello("Bob"))
}