play的一大优势是可以将HTTP映射到JAVA API代码(Type-safe mapping from HTTP to an idiomatic Scala or Java API),完美的实现了RestFul架构。
Play使用 Controllers来实现MVC结构,如下图所示,Controller是用来连接服务器业务逻辑(business logic)和前台浏览器HTTP请求(HTTP requests)的桥梁。
一、Actions, Controllers and Results
- controller其实是一个继承了父类
play.mvc.Controller的类,在该类中定义了多个action的方法,该类位于controllers包中,如下图
- 每个action是一个JAVA方法,用于处理HTTP的请求并将处理结果发还给用户(Action可以看作是一个处理请求数据并产生一个结果放回给客户端的简单Java方法)
- 每个action方法的返回值类型均为Result(play.mvc.Result),每个Result均对应一个HTTP请求(HTTP response),代表Http响应发送到客户端,即
play.mvc.Results
类提供了多个helpers来产生标准的HTTP回应,如ok()构建了一个200状态的相应,它包含一个text/plain的响应体 - Results即结果。最简单的莫过于包含一个状态码、一组HTTP头和一个Http体的被发送到web客户端的Http Resul。这些Results被play.mvc.Result定义,play.mvc.Results 类提供了一些帮助产生标准Http Results的方法,下面的代码示例创建了各种Results:
public static Result index() { return ok("Hello world!"); } //------------------------------------------------ Result ok = ok("Hello world!"); Result notFound = notFound(); Result pageNotFound = notFound("<h1>Page not found</h1>").as("text/html"); Result badRequest = badRequest(views.html.form.render(formWithErrors)); Result oops = internalServerError("Oops"); Result anyStatus = status(488, "Strange response type"); //------------------------------------------------------
// 重定向同样是简单的Results,把浏览器重定向到一个新的URL也不过是一种简单的result,不同的是这些result 类型没有响应体 public static Result index() { return redirect("/user/home"); } //-------------303----------------------------------- public static Result index() { return temporaryRedirect("/user/home"); }
//-------------TODO Result----------------------------------- return TODO;
(Status code: ok -- 200, error, redirect, 404)
如何使用Result
如在controller中
public static Result list() { ... return ok(list.render(products)); }
在模板中
<a href="@routes.Products.newProduct()" class="btn">
二、HTTP routing(wire URLs to action methods)——绑定HTTP参数到JAVA方法(action)里的参数
- 路由route是将每一个进来的Http请求转换成一个Action调用(action是contoller类里的静态的、公共的方法)的组件。
- 一个Http请求在MVC框架中被视为一个事件,这一事件包含了两个主要的信息块: 请求路径和Http方法(get,post...)
- 路由在conf/routes文件中被定义,并且会被编译,也就是说你能直接在浏览器中看到路由错误
路由route用于传送HTTP请求到JAVA的controller的action代码中,route、controller和action的关系如下图
route的语法书写规定如下:
其中:
1、The HTTP method——Http方法指任何被Http所支持的方法
GET
, POST
, PUT
, DELETE
, HEAD
2、URI pattern
- 静态路径: GET /clients/all controllers.Clients.list()
- 动态路径: GET /product/:ean controllers.Products.details(ean: String)
- 带默认值的动态路径: GET /clients controllers.Clients.list(page: Integer ?= 1)
- 带固定值的路径: GET / controllers.Application.show(page = "home")
- 带可选项的动态路径:
GET /api/list-all controllers.Api.list(version ?=null)
——/api/list-all?version=3.0(???)
- 带正则表达式的动态路径: GET /product/$ean<[0-9]{13}> controllers.Products.details(ean: Long) ——使用$id<regex>
- 带可捕捉多个用/分隔的URI路径的动态路径:
GET /files/*name controllers.Application.download(name) ————如:
GET /files/images/logo.png
,name
部分代表了images/logo.png
3、反向路径:
说明: 没搞明白,以后补充
4、路由优先级
多个路由匹配同一个请求的时候就会产生冲突,这时候第一个路由会被使用。
三、处理HTTP响应
1、Play的Result的默认content type和字符集
- 默认的Content-Type是text/plain, 如: Result textResult = ok("Hello World!"); 返回 text/plain
- 默认的字符集为 utf-8
- 修改为application/json, 如: Result jsonResult = ok(jerksonObject);
2. 设置content type
Result htmlResult = ok("<h1>Hello World!</h1>").as("text/html");
或
public static Result index() { response().setContentType(text/html; charset=iso-8859-1"
);
return ok("<h1>Hello World!</h1>");
}
3. 设置HTTP response头信息
public static Result index() { response().setContentType("text/html"); response().setHeader(CACHE_CONTROL, "max-age=3600"); response().setHeader(ETAG, "xxx"); return ok("<h1>Hello World!</h1>"); }
4.将content type设置为返回JSON格式
public static Result index(){ Map<String, String> itworks = new HashMap<String, String>(); itworks.put("message","It works"); return ok(plays.libs.Json.toJson(itworks)); }
返回结果为:
四、Scope
有了controller和route,我们可以从接受用户的输入数据,并将处理结果返回给用户。现在我们开始讨论储存数据的有效范围和时间——scope。
- play不保存任何服务器段的数据,数据要么保存在服务器,要么保存在客户端
- 和J2EE有所不同,play有四种scope,如下图所示
(Session scope,Flash scope,Request scope,Response scope)
- 如果要保持跨多个Http请求的数据,可以把他们保存在Session或者Flash作用域中。保存在Session中的数据在整个用户会话中可用,保存在Flash作用域中的数据仅在下个请求中可用。
- Session和Flash数据没有在服务器上储存,而是使用cookies在后续的Http请求之间传递(而是使用cookies被添加到后续的每个Http请求中),理解这一点很重要。也就是说数据大小受限制了(最大到4KB),并且只能存储字符。
1. Context对象
- Play中所有scope均存放在Context对象中,Context是一个final static类,其子对象有Request, Response, Session, 和Flash。
- 我们可以使用current()方法获取当前的scope,然后进入Play的各个scope
2. scope类型
1) request scope
该scope不能用于存储数据对象,只是用来进入当前请求的数据,如用HTML的表单的数据就是通过request scope提交的。
2)response scope
该scope不能用于存储数据对象,只是用来设置响应的content类型的,如设置响应的类型为XML:
Context.current().response().setContentType("application/xml");
另外response对象还可以用来设置和删除Cookies
response().setCookie("theme","blue");
response().discardCookies("theme");
如根据Cookie设置主题
public static Result index() { if ("blue".equals(cookies("theme").value())){ // Do something } .... }
3) session scope
- 只要客户端浏览器没有关闭,存储在session中是数据均不会消失。
- 需要说明的是sessions不是存储在服务器端的,在每一次HTTP请求的时候添加上的。
- sessions使用Cookie机制,使用数据大小是4kB,而且只能存储字符串
3.1) 存储数据到session
3.2) 在controller中使用session
4)flash scope
- flash scope的生命周期位于两个请求之间, 这就是说当进行重定向的时候存储在flash scope的数据的有效的。
- flash作用域的工作方式和Session很相似,但是有两点是不同的:
- 数据仅保持在一个请求中
- 数据未被签名,因此用户可以修改Flash cookie的数据
注意:Flash作用域仅应该被用于在简单的非Ajax应用中传递成功/失败消息。因为其数据仅仅传递给下一个请求,但是在复杂的web应用中它无法确保请求的顺序。总之,Flash作用域受到竞争条件限制。
有和没有flash的对比如下所示:
五、数据的安全性
针对存储在客户端的数据的安全性问题,play提供了一个Cookie key来确保用户无法修改Cookie数据。这个Cookie key存放在conf/application.conf文件中,如
application.secret="FuqsIcSJlLppP8s?UpVYb5CvX1v55PVgHQ1Pk"
说明: 如果你的应用程序有多个实例,程序之间需要共享secret key(如两个服务器必须有相同的secret key),否则另一个程序无法验证数据
- Use flash scope—— Flash scope is ideal for passing messages to the user (when a redirect is involved).
- Use action methods——This is the entry point for your business logic. Keep them short and delegate all business logic to your business models.
- Simple data binding is URL-centric data mapping to your action methods