Play Framework 2.5 | Web 开发总结

Samsara Aquarius已完成,是时候总结一下Play Framework的一些有用的东西和一些坑了~这里对应的Play版本为 2.5.2

Action, Controller 和 Result

在Play的Web开发中,Action, ControllerResult 这三个东西最为重要了。我们一个一个来解释:

Result

Result代表HTTP请求的结果,它包含header和body两部分。Play内部封装了常见的HTTP response状态,可以在这些Status的基础上生成Result,比如200 OK

1
2
/** Generates a ‘200 OK’ result. */
val Ok = new Status(OK)

Action

每个Action相当于一个Request => Result类型(处理请求并返回结果)的函数,即每个Action都接受一个Request对象(代表HTTP请求),并产生Result对象(代表HTTP回应)。

Play Framework 2底层是基于Netty实现的,因此Action经过处理以后会在Netty通信层执行(Reactor模式)。关于Action的详细实现,后边我还会专门写一篇文章分析。

Controller

在Play中,Controller里的函数用于生成Action。其实Action就相当于处理请求的函数,只不过将普通的函数包装了一层而已(继承关系:Action <:< EssentialAction <:< (RequestHeader => Accumulator[ByteString, Result])),因此调用这些函数其实就是在调用Action中包装的函数。

Session、Cookie与Flash

Play中处理Session、Cookie的语法都比较简洁。比如Cookie的添加与删除:

1
2
val res1 = Ok("Good Scala") withCookies Cookie("fuck", "shit")
val res2 = res1.discardingCookies(DiscardingCookie("fuck"))

Session的使用也类似:

1
2
val res2 = Ok("Good Scala") withSession Session("fuck", "shit")
val res_rm = res2 withNewSession // clear session

但是Play里的session有个坑,它没实现expiration的功能,因此需要我们自己在session中加个时间戳,然后自己实现expiration的逻辑。

另外还有个大坑!如果需要添加session的话,需要这样:

1
Ok("Good Scala") withSession (request.session + Session("fuck", "shit"))

因为withSession(s)方法会将s设为当前的session,原先的session将会被抹掉。
。所以一定要把原先的session加上,要不然原先的session会丢失。

Flash和Session类似,但Flash只在一个request内有效,多用于request之间数据的传递:

1
Ok("Good Scala") flashing "fuck" -> "shit"

自定义Action

比如有一些逻辑中,我们需要验证用户是否登录,如果没有登录就自动跳转到登录界面。如果在每个Action中都写上登录验证逻辑的话会非常不优美,因此我们可以自定义一个AuthenticatedAction,里面包装了权限验证的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
object AuthenticatedAction extends ActionBuilder[Request] {
override def invokeBlock[A](request: Request[A],
block: (Request[A]) => Future[Result]): Future[Result] = {
utils.DateUtils.ensureSession(request)
request.session.get("user_token") match {
case Some(u) =>
block(request)
case None =>
Future.successful(Redirect(routes.UserController.loginIndex()))
}
}
}

我们自定义的Action需要实现ActionBuilder[Request]接口,并重写invokeBlock函数。

拦截器

在Play中使用Filter也非常方便。比如我们想给应用添加安全拦截器,我们可以写一个Filter:

1
2
3
4
5
6
7
8
9
10
11
12
package filters
import javax.inject.Inject
import play.api.http.HttpFilters
import play.filters.headers.SecurityHeadersFilter
class Filters @Inject() (security: SecurityHeadersFilter) extends HttpFilters {
override def filters = Seq(security)
}

其中我们通过依赖注入获取安全拦截器SecurityHeadersFilter实例。然后,我们在application.conf中配置拦截器:

1
play.http.filters=filters.Filters

依赖注入

Play Framework 2.4.0开始又引入了依赖注入(学习Spring大乱炖的节奏)。之前写了一篇总结:Play Framework 2.5 | Dependency Injection总结

Twirl模板引擎

这次开发还顺便玩了玩Play 2的Twirl模板引擎,用起来还比较顺手~不过现在一般都用前后端分离的架构了,后端模板引擎也用的少了。

微服务架构思考

用Play做微服务应用是一个不错的选择,那么如何将单体架构重构成微服务架构呢?这里面就有一个主要的问题:如何将各个组件解耦。一种最简单的思路就是用Play实现一个API Gateway作为网关服务(需要多实例部署+负载均衡+高可用),API Gateway将请求分派到各个组件中,组件之间通过Actor进行通信。可以总结为两点:事件驱动+异步通信。事件驱动可以使整个微服务架构更加灵活,各个组件的耦合度降低,而基于Akka Actor的异步通信可以提高并发性能,提高吞吐量。当然实际生产环境中还要考虑容错、高可用等等的问题,设计起来会更加复杂。


TODO

  • Play 2.5 Streaming(Iteratees/Enumerator/Akka Stream)
文章目录
  1. 1. Action, Controller 和 Result
    1. 1.1. Result
    2. 1.2. Action
    3. 1.3. Controller
  2. 2. Session、Cookie与Flash
  3. 3. 自定义Action
  4. 4. 拦截器
  5. 5. 依赖注入
  6. 6. Twirl模板引擎
  7. 7. 微服务架构思考
  8. 8. TODO