在Play项目中咱们常常须要开发一些自定义Filter完成一些特定任务,在Filter实现中一般须要根据Response的Content-Type作相应的处理。例如实现一个CacheFilter只缓存js/css/img等静态文件,LoggerFilter只打印html响应的请求,GzipFilter忽略image类型响应(由于image自己就是压缩类型)。因此正确的获取Content-Type在开发Filter时显得尤其重要。在Play2.5.x中,Content-Type的获取方式发生了一些变化,下面对比Play2.4.x作一些简单的说明。css
从Play2.5.x开始,Play将逐渐地从Iteratee迁移到Akka Stream,在官方文档“Play 2.5 Migration Guide”第1段中就说明了这一点:html
- Streams Migration Guide – Migrating to Akka Streams, now used in place of iteratees in many Play APIs
对于咱们的平常开发来讲,最大的影响就是Result的类型声明发生了变化,在Play2.4.x中Result的类型声明为:json
case class Result(header: ResponseHeader, body: Enumerator[Array[Byte]], connection: HttpConnection.Connection = HttpConnection.KeepAlive)
而在Play2.5.x中,body的类型从Enumerator变成了HttpEntity:缓存
case class Result(header: ResponseHeader, body: HttpEntity)
下面咱们经过生成一个简单的json响应对比一下2.4.x和2.5.x之间的实现差别,生成json代码以下:app
Ok(Json.obj("success" -> true))
由于传入的是JsValue类型,因此Play会自动添加以下响应头:ide
Content-Type:application/json
Play2.4.x的相应实如今Results.Status.apply方法中,代码以下:ui
class Status(status: Int) extends Result(header = ResponseHeader(status), body = Enumerator.empty, connection = HttpConnection.KeepAlive) { def apply[C](content: C)(implicit writeable: Writeable[C]): Result = { Result( ResponseHeader(status, writeable.contentType.map(ct => Map(CONTENT_TYPE -> ct)).getOrElse(Map.empty)), Enumerator(writeable.transform(content)) ) } ...
注意apply方法的第2行,Play2.4.x根据响应内容将Content-Type设置到ResponseHeader中。scala
再来看Play2.5.x,实现也在Results.Status.apply方法中,代码以下:code
class Status(status: Int) extends Result(header = ResponseHeader(status), body = HttpEntity.NoEntity) { def apply[C](content: C)(implicit writeable: Writeable[C]): Result = { Result( header, writeable.toEntity(content) ) } ...
注意apply方法的第2行,Play2.5.x并无在ResponseHeader设置请求头。继续追踪HttpEntity的实现,发现它有一个contentType方法声明,其值来自隐式的ContentTypeOf[JsValue]参数:orm
/** * The content type of the entity, if known. */ def contentType: Option[String]
好吧,真相浮出水面了:Play2.5.x默认将Content-Type响应头设置在HttpEntity上,而不是像Play2.4.x那样设置在响应头上。
因此Play2.5.x中正确获取Content-Type的方法是使用response.body.contentType,下面是配置GzipFilter的代码示例:
new GzipFilter(shouldGzip = (request, response) => response.body.contentType.exists(_.startsWith("text/html")))
参考: