技术开发 频道

高效构建Web应用 教你玩转Play框架

  HTTP 路由

  在前面介绍过,Play 框架中的控制器用来接受 HTTP 请求并返回相应的响应。这个过程的重要一环就是 HTTP 请求的 URI 与控制器之间的映射关系。Play 框架提供了灵活的 HTTP 路由功能来完成这个映射。路由信息被保存在 config/routes 文件中,采用简单的方式进行声明。每条路由记录包含 3 个元素,分别是 HTTP 方法的名称、匹配的 URI 模式以及对应的控制器动作方法。路由记录表示的含义是当使用给定的 HTTP 方法来请求对应模式的 URI 的时候,控制器动作方法就会被调用。

  Play 框架支持的 HTTP 方法有 GET、POST、PUT、DELETE 和 HEAD。使用通配符 *可以匹配任何方法。在 URI 模式的声明中可以使用正则表达式来表示复杂的映射规则。URI 模式中还可以使用 {...} 来声明动态的部分。每个动态部分都是有名称的,可以在控制器动作方法中通过 params 对象来获取。比如,/notes/home 这样的 URI 模式会匹配 /notes/home,但是 /notes/{id} 可以匹配 /notes/123和 /notes/abc,而且 URI 模式中 /notes/ 后面的部分可以作为参数 id 的值被获取到。URI 模式 /notes/{<[0-9]+>id} 使用了正则表达式,只会匹配 /notes/后面紧跟的全是数字的情况。在声明控制器的动作方法的时候,需要使用带名称空间的全名,如myapp.Notes.show。有些动作方法是带参数的,可以在声明的时候预先绑定一些参数值,这样可以方便的添加一些 URI 别名。比如动作方法 Notes.show() 有一个参数 id 用来指明要显示的内容的 ID。如果参数 id 的值为 0,则会显示所有内容的一个列表。这样的话,就可以定义一个类似 GET /notes/all Notes.show(id:0) 的路由声明。这样暴露出来的 URI 更加简洁和易于记忆。

  在路由文件中的路由声明是按照从上到下的优先级来进行匹配的。比较具体的 URI 模式应该放在比较通用的模式之前。对于静态文件,可以通过一个特殊的动作方法 staticDir 进行声明。比如 GET /files staticDir:files 就声明了 files 目录中包含的是静态文件。

  在介绍完 HTTP 路由之后,下面介绍 Play 框架独特的无状态的体系结构。

  无状态的体系结构

  HTTP 协议本身就被设计成无状态的,采用请求 / 响应的模式。不同的请求之间并不存在相互关系。但是这种架构模式在开发某些 Web 应用的时候不是很方便。有些应用要求用户进行认证登录之后才能进行某些操作。同样的 URL,认证和未认证用户看到的内容是不同的。而且用户认证成功之后,他应该在一段时间内保持这种认证状态。否则的话,用户每次都需要输入用户名和密码才能访问受限的内容。对于这种情况,很多 Web 开发框架提供了会话的支持,允许应用保存一些与会话相关的数据。Java Servlet 规范中的javax.servlet.http. HttpSession 就是一种会话的接口。应用的服务器会负责维护每个会话相关的数据。这些数据可以通过一个会话 ID 来进行标识。这个标识会利用浏览器的 cookie 机制保存在浏览器端,也可以作为请求 URL 的参数来传递。服务器端通过此标识来识别每个会话。在处理相应的请求的时候,就可以根据会话 ID 来获取保存在服务器端上的会话数据。会话机制的问题是会影响应用的可伸缩性。如果一个应用使用多台服务器的话,就需要额外的机制来保证同一用户在不同机器上面的会话是同步的。而无状态的实现则不存在这个问题,对于某一个请求,由不同机器来处理的结果都是相同的。

  Play 框架的设计架构就是无状态的。它没有提供服务器端的机制用来维护跨多个请求的数据。如果确实需要保存这样的数据的话,可以考虑下面几种方案:

  保存在 Session 或 Flash 作用域中。Play 框架中仍然有会话的机制,但是并没有提供在服务器端保存会话数据的能力。会话数据是保存在浏览器的 cookie 中的,由浏览器在每次请求的时候自动发送。通过这种方式来达到维护会话数据的目的。由于会话数据是保存在 cookie 中,其大小是有限制的,一般不能超过 4K 字节,而且只能保存字符串类型的数据。Flash 作用域和会话一样,也是通过 cookie 来保存的。所不同的是,Flash 作用域中的数据只在下次请求中是有效的。

  保存在持久化的数据存储中,如数据库中。如果需要在多个请求中使用同一个领域对象的话,可以把这个对象的 ID 保存在 Session 或 Flash 作用域中,而在控制器动作方法中使用此 ID 来从数据库中查询相应的对象。

  保存在暂时性数据存储中,如缓存中。Play 框架内置了缓存的支持,通过调用类 play.cache.Cache 就可以对缓存进行操作。与使用持久化存储类似,缓存中的键的值可以保存在 Session 或 Flash 作用域中。

  对于熟悉了 Java Servlet 规范的开发人员来说,需要一些时间来适应 Play 框架的这种无状态的体系结构。不过这种结构对于应用的可伸缩性来说,确实是非常有好处的。

  介绍完无状态的体系结构之后,下面介绍一些其它话题。

  测试

  Play 框架对应用的测试也提供了良好的支持。Play 框架一共支持三种类型的测试,分别是单元测试、功能测试和界面测试。单元测试主要用来测试应用的模型层代码。单元测试用例的 Java 类继承自 play.test.UnitTest ,可以使用 JUnit 4 提供的标注和断言。功能测试主要用来测试应用的控制层代码。功能测试用例的 Java 类继承自 play.test.FunctionalTest。在测试用例中,可以通过GET()、POST()、PUT()、DELETE() 和 makeRequest() 等方法来发出 HTTP 请求,也可以直接调用控制器中的动作方法。除此之外,还可以使用一些与 HTTP 响应相关的断言。如 assertStatus()、assertContentType() 和 assertHeaderEquals() 分别用来验证 HTTP 状态代码、内容类型和 HTTP 头。界面测试使用 Selenium 工具来进行。开发人员可以使用 Selenium 的语法来编写测试用例,也可以使用 Play 框架提供的 #{selenium} 标签。

  在进行测试的时候,需要准备一些测试数据。测试数据可以用 YAML 的格式保存在文本文件中,并通过 play.test.Fixtures.laod()方法来加载这些数据到数据库中。当测试结束之后,可以通过 deleteAll() 方法来删除这些数据。

  在测试的时候,需要用 play test命令以测试模式启动应用,再用浏览器访问 http://localhost:9000/@tests 进行测试。

  任务调度

  在 Web 应用开发中,有时候会需要定期执行一些调度任务,比如数据库备份和数据同步等。这些任务不是通过 HTTP 请求来触发的,而是定时执行的。Play 框架提供了内置的任务调度支持的能力。创建新任务的时候,只需要继承自 play.jobs.Job类,并覆写doJob()方法即可。如果要创建的任务有返回结果的话,覆写 doJobWithResult()方法即可。任务创建完成之后,可以选择不同的调度方式。一种方式是在应用启动的时候执行一次。只需要在任务的 Java 类上添加标注 @OnApplicationStart即可。对于定期执行的任务,Play 框架提供了两个标注:一个是 @Every,用来按照固定的时间间隔调度任务,如 @Every("1h")声明任务每个小时执行一次;另外一个是 @On,用来声明描述调度策略的 CRON 表达式。

  安全

  Play 框架提供了对 Web 应用安全性方面的支持,可以防范一些常见的攻击方式。前面提到过,Play 框架中的会话数据是保存在浏览器的 cookie 中的。这些数据是经过签名的,可以防止被恶意攻击者所篡改。应用中的重要数据也不应该保存在会话中。Play 框架中的模板在输出 HTML 内容的时候,会自动对内容进行转义,可以防范跨站点脚本攻击。对于 SQL 注入攻击,开发人员应该尽量使用提供的 find() 方法来查询领域对象。对于自己创建的查询语句,应该在语句中使用占位符并进行参数绑定,而不是通过字符串相加的方式来创建。为了防范跨站点请求伪造,Play 框架中的控制器的动作方法都可以使用 checkAuthenticity() 方法来声明调用此方法时的请求中必须包含合法的令牌。这个令牌用来确保当前请求是由应用自身发出的,而不是被伪造的。通过session.getAuthenticityToken() 方法可以生成一个只对当前会话有效的令牌,需要在请求的时候附带此令牌。如果是通过页面上的表单来提交请求的话,Play 框架也提供了一个标签 #{authenticityToken /} 用来生成一个包含了令牌的隐藏域,可以直接在模板中使用。

  总结

  Play 框架作为一个优秀的 Java Web 应用开发框架,可以帮助开发人员快速高效的构建 Web 应用。它为开发人员提供了一个良好的基础架构,并屏蔽了很多底层的实现细节。开发人员可以用一个简单的视角来看待 Web 应用开发,而不需要关心过多的细节。Web 开发人员可以熟悉 Play 框架,并在开发中选用这个框架。

0
相关文章