Freewind @ Thoughtworks scala java javascript dart 工具 编程实践 月结 math python english [comments admin] [feed]

(2013-01-01) 02. HTTP路由

广告: 云梯:翻墙vpn (省10元) 土行孙:科研用户翻墙http proxy (有优惠)

(本文译者是爱国者)

内置的Http路由

在Play中,路由是负责将Http请求转换为Action的组件。

在MVC框架中,一个Http请求被看成一个事件。这种事件包含两种信息:

路由匹配信息定义在conf/routes文件中,和Play 1.x不同,该文件会被纳入编译范畴。也就是说,你可以直接在浏览器中看到路由匹配的错误。

[[images/routesError.png]]

路由语法

conf/routes文件是路由配置文件。所有的Http路由信息都在该文件定义。每条路由信息占一行,由Http请求方法,URI模式和Action生成函数组成。下面是routes文件的一部分内容:

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

这个例子定义了一个路由信息,意思是当客户端向服务器发起形如`GET /clients/123`的请求时,由控制器Clients中的Action生成函数`show`进行处理,其中请求路径上的数字`123`会赋值给`id`变量.

你可以给`route`文件添加注释,只需在每一行的开头加上`#`,比如:

# 这里是注释 
# Display a client
GET   /clients/:id          controllers.Clients.show(id: Long)

#### Http方法

Play支持所有Http请求方法,包括`GET`,`POST`,`PUT`,`DELETE`,`HEAD`等

#### URI模式

URI模式用于匹配Http路由请求路径

##### 静态模式

假如你要准确匹配形如`GET /clients/all`这样的Http请求,你可以这样定义:

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

##### 动态模式

假如客户端希望能根据不同的客户ID查询客户信息,比`GET /clients/1`查询ID为1的客户信息,`GET /clients/2`查询ID为2的客户信息,你可以这样定义:

GET   /clients/:id          controllers.Clients.show(id: Long)
> 注意:你需要在`show`函数声明一个名字为`id`的参数,假如你使用其他名字,比如`clientId`,那么Play不会将`:id`的值传递过去. 此外,你还可以在URI模式上指定更多变量,比如
# 获取某个部门(根据部门ID)的男性或女性员工的信息
GET /departments/:id/gender/:gender controllers.Department.show(id: Long, String gender)
> 注意:URI模式的动态部分,如上例中的`:id`,是采用正则表达式`[^/]+`进行匹配的。也就是说,形如`/client/:id`的URI模式能匹配`/clients/123` ,但不能匹配`/clients/id/123`。

##### 如何让URI模式的动态部分匹配多个路径段?

你可以使用形如`*id`来匹配多个URI路径段,这时Play会使用`.*`正则表达式进行匹配。下面的路由会匹配形如`GET /files/images/logo.png`或`GET images/logo.png`这样的Http请求:

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

##### 使用正则表达式

要对URI模式的动态部分实现更细粒度的匹配,就需要编写正则表达式。比如,客户的ID只有数字组成,那么可以这样定义路由规则:

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

其中`[0-9]+`是正则表达式。

#### 调用Action生成函数

路由定义的最后一部分是处理函数。该函数又叫做Controller Action method,它必须返回一个`play.api.mvc.Action`对象。

如果该函数没有定义任何参数,则给出函数的全路径名并以`()`结束。看下面的例子:

GET   /                     controllers.Application.homePage()

如果action函数定义了参数,那么Play会将URI路径或查询字符串上相应的值传递给函数参数。看下面的例子:

# 将URI路径上的:page值传递给show函数的参数page
GET   /:page                controllers.Application.show(page)

又如:

# 如果客户端发起的请求带有查询字符串?page=XXX,则Play将XXX传递给show函数的参数page
GET   /                     controllers.Application.show(page)

同时,我们要在`controllers.Application`控制器中定义`show`函数:

def show(page: String) = Action {
    loadContentFromDatabase(page).map { htmlContent =>
        Ok(htmlContent).as("text/html")
    }.getOrElse(NotFound)
}

##### 参数类型

在定义路由规则时,对于字符串参数,可以省略参数的类型。如果你希望Play把参数转换成某个Scala类型,那么就需要在参数名后声明类型。 比如,客户ID都由整数组成,你可以这样定义路由:

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

这样Play会自动将`:id`转换成`Long`类型。如果你将`: Long`省略,那么Play会把`id`的类型视为`String`. 根据上述路由的定义,你要相应修改`show`函数的定义:

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

##### 参数固定值

有时候你希望给controller action method的参数提供固定值。请看例子:

# Extract the page parameter from the path, or fix the value for /
GET   /                     controllers.Application.show(page = "home")
GET   /:page                controllers.Application.show(page)

##### 默认参数

可以给参数提供默认值,如果无法从Http请求中解析出参数值

GET   /clients              controllers.Clients.list(page: Int ?= 1)

这个例子中,如果客户端发起的请求形如`GET /clients?page=3`,那么`page`的值是3;如果`GET /clients`,那么`page`的值为1

#### 路由的优先级

假如多条路由规则匹配某个Http请求,Play会按照路由规则在`route`文件中声明的顺序,匹配第一条路由规则

#### 反向路由

`route`文件定义了URL和scala函数之间的映射。你可以通过Http请求调用scala函数,也可以通过调用一个scala函数来模拟一次Http请求。Play在编译时会在`routes`包下给每个控制器(`controller`)生成反向控制器(`reverse controller`)。这些`reverse controller`含有跟`controller`中的action生成函数一样的函数(相同函数名和函数参数),只是返回`play.api.mvc.Call`对象。 一个`play.api.mvc.Call`表示一个Http调用,包含HTTP方法和URI.

比如,我在`controller.Application`定义了一个`hello`函数:

package controllers

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

object Application extends Controller {

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

}

然后在`conf/routes`文件中定义路由规则:

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

当Play编译时,会在`routes`包下生成一个反向控制器`Application`. 你可以使用`routes.Application.hello("Bob")`模拟客户端发起`GET /hello/Bob`的请求

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

原文:https://github.com/playframework/Play20/wiki/ScalaRouting

comments powered by Disqus