Play 框架手册(2) – HTTP路由

router 组件负责将 http 请求交给对应的 Action 处理(一个 static/public 的控制器方法)。

一个http请求在mvc框架里被当作一个mvc事件看待。这个事件包含了两个主要信息:

  • 包含在查询字符串里的请求路径 (比如/clients/1542, /photos/list)。
  • HTTP方法(GET, POST, PUT, DELETE)

2.1.关于REST

Representational state transfer (表述性状态转移REST)是一种应用于分布式超媒体系统(如www)的软件架构设计风格(注意:不是标准)。

REST规定了几个关键的设计原则:

  • 应用程序功能都被当作资源看待
  • 每个资源都有唯一的可寻址URI地址
  • 所有的资源共享同一个接口来传送客户端和资源间的状态
  • 其表现形式为每个超链接都不带有参数

如果你正在使用http,那么这些接口都是通过一系列可用的http方法进行定义。协议用于访问属于下列状态的资源:

  • 客户端服务器Client-server
  • 无状态的Stateless
  • 可缓存的Cacheable
  • 可分层的Layered

如果某个应用程序遵循主要的REST设计原则,那么这个应用程序就是RESTful的。play框架很容易创建RESTful风格的应用程序:

  • Play router把所有的URI和HTTP方法都进行解析,并把请求路由一一对应到相应的java调用。基于正则表达式的URL范示使这个操作过程更灵活。
  • 协议是stateless的,也就是说你不能在服务器上为两个连接的请求存储任何状态。
  • Play把http当作关键特性进行考虑,因此play框架提供了让你完全访问http信息的能力。

2.2.routes 文件语法

Router使用conf/routes文件作为配置文件。此文件列出了所有应用程序所需要的路由。每个路由都由一个HTTP方法+ URI 范示来表示一个Java调用。

示例:

GET    /clients/{id}             Clients.show

每个route开始于一个http方法,接着是一个URL范示,最后一个元素是java调用定义。

你可以使用#为路由添加一个注释

# Display a client
GET    /clients/{id}             Clients.show           

HTTP方法

HTTP方法可以是以下几个http协议支持的任何方法:

  • GET
  • POST
  • PUT
  • DELETE
  • HEAD

这些方法也支持 WS(web service)作为 action 方法来指明一个 WebSocket 请求。

如果使用*作为http方法,则route将匹配所有的http方法请求,如:

*   /clients/{id}             Clients.show           

路由将接受下面两个请求:

GET /clients/1541
PUT /clients/1212

URI 范示 Pattern

URI范示定义了路由的请求路径。请求路径的某些部分可以是动态的。任何动态的部分都必须使用大括号进行界定{…},如:

/clients/all

将精确匹配:

/clients/all

但…

/clients/{id}

可以匹配下面的请求:

/clients/12121
/clients/toto

一个URI范示可以有多个动态部分:

/clients/{id}/accounts/{accountId}

对于动态部分的默认匹配策略使用的是正则表达式 /[^/]+/,因此,我们可以为动态部分定义自己的正则表达式。

下面的正则表达式只能匹配数字值作为id:

/clients/{<[0-9]+>id}

下面的表达式只允许4至10个小写字母作为id:

/clients/{<[a-z]{4,10}>id}

在这里,可以使用任何可用的正则表达式。

注意!

凡是被命名了的动态部分,控制器随后可以从http参数map里得到动态部分的值。

默认情况下,play会把URL后的反斜线/当作不同的值,比如:

GET     /clients         Clients.index

此路由只会匹配/clients URL路径,不会匹配/clients/。如果你打算匹配这两个URL路径,那么你需要在路径后增加一个/?作为结束标志,比如:

GET     /clients/?       Clients.index

注意:

在这里,除了反斜线外,URI范示不能含有任何可选部分。

Java 调用定义

route定义的最后一部分就是java调用,通过使用action方法的完整名称来进行定义。action方法必须是控制类的public static void方法,而且这个控制器类必须定义于controllers包里,且是play.mvc.Controller的子类。

如果控制器类不在controllers包下,则需要在控制器类名称前添加java包名称。默认的controllers包可以直接使用,因此在此包下的控制器不需要单独指定包名。

GET    /admin             admin.Dashboard.index           

把404当作action来用

你可以直接使用400作为路由action,用于标记那些应用程序必须忽略的URL路径,如忽略网站ico请求:

# Ignore favicon requests
GET     /favicon.ico            404

指派静态参数

在某种情况下,你可能会重复使用一个存在的基于某些参数值的路由,如下:

public static void page(String id) {
    Page page = Page.findById(id);
    render(page);
}

相应的route:

GET    /pages/{id}        Application.page

现在,我打算为一个id为’hame’的页面定义一个URL别名,我可以定义一个具有静态参数的其他路由:

GET    /home              Application.page(id:'home')
GET    /pages/{id}        Application.page

当页面的id值为‘home’时,第一个路由与第二路由造价。但是,既然第一个具有更高优先级,那么这个路由将用作Application.page方法处理id为‘home’时的默认路由。

变量和脚本

在路由里,也可使用${…} 语法来在路由中使用变量,使用%{…}语法来在路由中使用脚本,和你在模板中使用变量和脚本的情况是一样的,比如:

%{ context = play.configuration.getProperty('context', '') }%

# Home page
GET    ${context}         Secure.login
GET    ${context}/        Secure.login

另外一个例子就是在CRUD模块里的路由文件使用crud.types标签来循环调用所有的model types,以为每种类型生成控制器路由定义。

2.3.路由优先级

许多路由定义可能会匹配同一个请求,如果产生了冲突,那么只采用第一个路由(遵照下面的声明顺序)。

比如:

GET    /clients/all       Clients.listAll
GET    /clients/{id}      Clients.show

照此定义,则下面的URL:

/clients/all

将调用第一个路由指定的Clients.listAll方法(那怕第二个路由也同样匹配这个URL)。

2.4.服务器静态资源

staticDir: mapping

使用特定的action staticDir,可以用来定位每个你打算当作静态资源容器进行发布的文件夹。

比如:

GET    /public/           staticDir:public

当提供的请求含有/public/* 路径时,Play将把应用程序/public目录下的文件发布出去。

其优先权和标准的路由优先权相同。

staticFile: mapping

也可直接映射一个URL路径到一个静态文件。

# Serve index.html static file for home requests
GET     /home                   staticFile:/public/html/index.html

2.5.URL 编码

由于不可能对URL解码或重新编码 (比如你并能确定URL里的斜线是否就是真正的斜线或%2F), URL应该明确进行编码。play默认使用UTF-8进行编码,但你也可以使用ISO-8859-1, UTF-16BE, UTF-16LE, UTF-16当成默认的WebEncoding配置参数。详见http://download.oracle.com/javase/1.4.2/docs/api/java/nio/charset/Charset.html

比如:

映射/stéphane时可以使用如下方法:

GET /st%C3%A9phane Application.stephane

2.6.反转路由:用于生成某些URL

Router可以用于在一个java调用里生成一个URL。因此,你就可以把所有的URI范示集中到一个配置文件里,这样,在重构应用程序时,你就更有把握了。

比如,下面的路由定义:

GET    /clients/{id}      Clients.show

在代码里,同样可以生成URL,以便调用Clients.show:

map.put("id", 1541);
// 结果为 GET /clients/1541
String url = Router.reverse("Clients.show", map).url;

URL生成器已经被集成到许多框架的组件里,你根本就不需要使用Router.reverse进行操作。

比如,如果你添加的参数并没有包含到URI范示里,那么这些参数将加到查询字符串里:

map.put("id", 1541);
map.put("display", "full");
// 结果为:GET /clients/1541?display=full
String url = Router.reverse("Clients.show", map).url;

优先权顺序再次用于查找最特别的路由,以生成URL。

2.7.设置内容风格(CSS)

Play选择依照request.format的值来为http response选择合适的media type。 这个值通过文件扩展名确定哪个视图模板将会被使用,同时设置response的Content-type的值为Play的mime-types.properties文件映射格式确定的媒体类型。

play请求的默认格式为html。因此,index()控制器方法的默认模板就是index.html文件。如果指定了一个不同的格式,就需要选择对应格式的模板。

你可以在调用render方法前以编程的方式设定格式。比如,为了使用媒体类型为text/css的CSS,你可这样处理:
request.format = “css”;

然而,更清晰的方法就是在rourtes文件里使用URL来指定格式。你可以通过为控制器方法指定格式来特定的路由添加格式,比如:下面的路由将处理来自/index.xml的请求,Application.index()方法将设置格式为xml,并使用index.xml模板进行渲染。

GET    /index.xml         Application.index(format:'xml')  

类似的还有:

GET    /stylesheets/dynamic_css   css.SiteCSS(format:'css')

在下面的路由里,Play也可直接从URL里提取格式:

GET    /index.{format}    Application.index 

在这个路由里,/index.xml请求将设置格式为xml,同时使用XML模板进行渲染; /index.txt请求将使用原始的text模板进行渲染。

Play也可使用http内容协商自动设置格式。

2.8.HTTP 内容协商 negotiation

play和其他RESTful架构一样,直接使用http提供的功能,而不是试着隐藏http或是在其之上放置一个抽象层。http的内容协商(Content negotiation)特性允许http服务器依照http客户端请求的媒体类型为同一个URL提供不同的媒体类型media types。客户端特定的可接受的内容类型是通过Accept header确定的,比如需要一个xml response就定义为:

Accept: application/xml

客户端可以指定多个媒体类型,并且可以指定(/)来表示可接受任意媒体类型:

Accept: application/xml, image/png, */*

传统的Web浏览器总是在其Accept header里包含了通配符:这样,他们就可以接收任意媒体类型,当然包括play提供的默认html类型。内容协商更多是用于定制的客户端,比如一个要求返回JSON的Ajax请求,或一个e-book阅读器需要的PDF或EPUB版本的文档。

从http headers开始设置内容类型

如果Accept header包含了text/html或application/xhtml以及作为/通配符的结果时, Play选择其默认格式html。如果通配符值为空时,默认格式将不被选择。

Play对html, txt, json和 xml媒体格式提供了内建支持。比如,下面定义了一个用于渲染某些数据的控制器方法:

public static void index() { 
   final String name = "Peter Hilton"; 
   final String organisation = "Lunatech Research"; 
   final String url = "http://www.lunatech-research.com/"; 
   render(name, organisation, url); 
} 

在一个浏览器里,如果请求的URL映射到这个方法时,那么play将渲染index.html模板,因为浏览器发送的Accept header里包含了text/html值。

通过设置请求格式为xml,Play响应的结果Accept header类型为:text/xml,同时使用index.xml模块进行渲染,比如:

<?xml version="1.0"?> 
<contact> 
<name>${name}</name> 
<organisation>${organisation}</organisation> 
<url>${url}</url> 
</contact> 

Accept header内建的格式对应的格式以及对应的模板文件见下表(以index()控制器方法为例):

Accept header Format Template file name Mapping
null null index.html Default template extension for null format
image/png null index.html 媒体类型没有映射到格式
*/*, image/png html index.html 默认媒体类型映射到html格式
text/html html index.html Built-in format
application/xhtml html index.html Built-in format
text/xml xml index.xml Built-in format
application/xml xml index.xml Built-in format
text/plain txt index.txt Built-in format
text/javascript json index.json Built-in format
application/json, */* json index.json Built-in format, default media type ignored

定制格式

通过检查请求header和设置相应用的格式,可以为自己的自定义类型添加内容协商,因此只需在http请求选择相应的媒体类型时设置这个自定义格式即可。比如,为了在控制器里实现带有text/x-vcard的vCard功能,需要所有请求处理之前对定制格式进行检查:

@Before 
static void setFormat() { 
    if (request.headers.get("accept").value().equals("text/x-vcard")) { 
        request.format = "vcf"; 
    } 
} 

现在,一个Accept: text/x-vcard header请求将会渲染index.vcf模板,比如:

BEGIN:VCARD 
VERSION:3.0 
N:${name} 
FN:${name} 
ORG:${organisation} 
URL:${url} 
END:VCARD  

继续讨论

当Router明确了哪个java调用将用于处理已经接收的http请求,play就会调用这个java调用。见Controllers 节,以了解控制器是如何工作的。


前一篇:
后一篇:

发表评论