HTTP routing[翻译]


原文:HTTP routing

内置HTTP路由器

路由器的职责是负责转换每一个进入的HTTP请求给Action

一个HTTP请求可以被看做是由MVC框架的事件。这个事件包含两个主要的信息:

  • 请求路径(e.g. /clients/1542,/photos/list), 包含查询字符串
  • HTTP 方法 (e.g.GET,POST, …).

路由是在可被编译的conf/routes文件中被定义的。这意味着你可以在你的浏览器中直接看到路由的错误信息

依赖注入

Play支持生成两种路由器的类型,一个是依赖注入路由器,另一个是静态路由。默认的是依赖注入路由器,这也是在Play种子Activator模板中的样例,因此我们推荐你使用依赖注入Contrlller。如果你需要使用静态Contrller,你可以通过在你的build.sbt配置文件里添加下面这样的配置来转换到静态路由生成器:

routesGenerator:= StaticRoutesGenerator  

在Play的文档中的代码样例假定你使用了依赖注入路由生成器。如果你没有使用这个,你可以容易的改写代码样例到静态路由生成器,既可以使用@符号在路由的Controller的调用部分加上前缀,或者通过声明你的每一个Contrller为object而不是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

当你和String Interpolating Routing DSL也被称作SIRD路由整合时,或者当你使用了多个路由文件的子项目时,这非常有用。

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-解码,而是在传递给你的Controller之前,并在反向路由中解码。

动态部分跨越多个/

如果你想让动态部分可以捕获多个通过正斜杠分割的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)  

就像通配符路由一样,参数不会被路由器编码或反向路由器解码。你的职责是验证输入,确保它在这个环境下的意义。

调用Action生成器方法

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

如果方法没有定义任何参数,仅使用完全限定的方法名:

GET / controllers.Application.homePage()  

如果Action方法定义了一些参数,所有这些参数的值将会在请求的URI中搜索,要么从URI路径本身提取,要么从查询字符串中。

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

或者:

#从查询字符串中提取页面参数

GET / controllers.Application.show(page)  

相应的,在controllers.Application

Controller中定义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.ClientsController中相应的实现一个相同的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)  

带有默认值的参数

在传入的请求没有找到值的时候,你也可以提供一个默认值:

#Pagination links, like /clients?page=3

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

Optional类型的参数

你也可以指定一个不需要在所有的请求中都出现的Optional类型的参数:

#The version parameter is optional. E.g. /api/list-all?version=3.0

GET /api/list-all controllers.Api.list(version: Option[String])  

路由的优先级

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

反转路由

路由器也可以被用来从Scala调用内部生成URL。这让在一个单独的配置文件中集中所有的URI模式成为可能,因此你在重构你的应用是会更加自信。

对于路由文件中的每一个使用的Controller,路由器会在routes包中生成一个有相同签名的相同Action方法的‘Reverse Controller’,但是返回play.api.mvc.Call,而不是play.api.mvc.Action.

例如,如果你创建一个这样的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)  

然后你就可以使用controllers.routes.Application反转Controller反转URL到Action方法:

// Redirect to /hello/Bob

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

注意:每一个Controller包都有一个路由子包。因此Action controllers.admin.Application.hello

可以通过controllers.admin.routes.Application.hello(只要在路由文件中的这个路由之前没有其他可匹配的生成路径)被反转。

反转Action方法的原理很简单:它拿到你的参数,并在路由模式中替换它们。在路径段(:foo)的例子中,值在替换完成之前解码,对于正则模式和通配符模式,字符串在原始形态中被替换,因此值可以跨越多个段。确保当你把这些组件传递给反转路由时忘记了这些必须的组件,并避免传递无效的用户输入。

默认的Controller

Play有一个提供了几个有用的Action的默认Controller,这些可以在路由文件中直接调用:

#Redirects to https://www.playframework.com/ with 303 See Other

​GET /about controllers.Default.redirect(to="https://www.playframework.com/")

# 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 / 重定向到外部的站点,但是它也可以重定向到其他的Action(如上面的例子中的/posts)。

自定义路由

Play提供了一个 DSL 来定义嵌入的路由器,这个路由器被称为

String Interpolating Routing DSL,或者简写为SIRD。这个DSL有很多用途,包括嵌入一个轻量级的Play服务,提供自定义或更高级的路由功能给规范的Play应用,在测试方面甩REST服务几条街。

详见String Interpolating Routing DSL

祝青

继续阅读此作者的更多文章