第九章 异步HTTP编程

处理异步结果

Play原生支持异步HTTP编程,它以异步无阻塞的方式处理客户端请求。

Play中 controller 默认是异步,在Play运行过程中, action 的代码必须执行速度比较快,否则会阻塞,那么如何处理需要计算很长时间的任务呢?

答案是使用 future 作为返回结果。

Future[Result] 最终将赋值为 Result 类型,通过将 Future[Result] 替换为 Result ,我们就可以快速生成响应而不用等待。

客户端会一直处于阻塞等待响应,但是服务端不会。

为了返回 Future[Result] ,需要先创建一个 Future ,以便在以后给 Result 赋值。

import play.api.libs.concurrent.Execution.Implicits.defaultContext

val futurePIValue: Future[Double] = computePIAsynchronously()
val futureResult: Future[Result] = futurePIValue.map { pi =>
  Ok("PI value computed: " + pi)
}

返回异步结果

之前我们一直使用 apply 来生成 action ,为了返回异步结果,需要使用 Action.async 方法:

import play.api.libs.concurrent.Execution.Implicits.defaultContext

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  futureInt.map(i => Ok("Got result: " + i))
}

Actions 默认也是异步的。

处理超时

为了避免客户端一直处于等待中,可以使用 promise timeout 来处理:

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.duration._

def index = Action.async {
  val futureInt = scala.concurrent.Future { intensiveComputation() }
  val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second)
  Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map {
    case i: Int => Ok("Got result: " + i)
    case t: String => InternalServerError(t)
  }
}

流式响应

在现实中,我们可能需要发送大量的数据,这时可以采用流式响应:

分发文件

在Play中分发文件也很方便:

def index = Action {
 Ok.sendFile(new java.io.File("/tmp/fileToServe.pdf"))
}

Play会自动计算 Conten-TypeContent-Disposition,不过也可以自定义:

def index = Action {
  Ok.sendFile(
    content = new java.io.File("/tmp/fileToServe.pdf"),
    fileName = _ => "termsOfService.pdf"
 )
}

如果你想将文件直接显示在浏览器中,可以这样设置:

def index = Action {
  Ok.sendFile(
    content = new java.io.File("/tmp/fileToServe.pdf"),
    inline = true
  )
}

块响应

如果后台数据是动态生成的,这时没法计算数据大小,只能分块发送。

def index = Action {
  val CHUNK_SIZE = 100
  val data = getDataStream
  val dataContent: Source[ByteString, _] = StreamConverters.fromInputStream(data, CHUNK_SIZE)

  Ok.chunked(dataContent)
}

当然,我们也可以使用任意 Source 的块数据:

def index = Action {
  val source = Source.apply(List("kiki", "foo", "bar"))
  Ok.chunked(source)
}