Documentation

You are viewing the documentation for the 2.2.0 release in the 2.2.x series of releases. The latest stable release series is 2.4.x.

§非同期レスポンスの処理

§非同期レスポンスはなぜ必要か?

これまでは、web クライアントに送信するレスポンスをすぐに生成できることとしていました。しかし、常にこのような場合だけではありません。レスポンスは、高価な計算や長い web サービスの呼び出しに依存するかもしれません。

Play の仕組み上、アクションの実行は可能な限り早く (言い換えると、ノンブロッキングに) 完了しなければなりません。では、レスポンスがまだ生成可能でないときに、一体何を返すべきでしょうか? それは、 レスポンスの Future です!

Future[Result] は、最終的に Result 型の値を補償します。通常の Result のかわりに Future[Result] を返すことで、何もブロックせずに即座に結果を返すことができます。Play は後に約束が果たされたときに、内包された結果を自動的にクライアントへ送信します。

web クライアントはレスポンスを待っている間ずっとブロックされますが、その間でもサーバ側の処理は全くブロックされないため、計算リソースを他のクライアントのために使うことができます。

§Future[Result] の生成

Future[Result] を生成するためには、元となる Future 、つまり結果を計算するために必要な値についての Future が先に必要になります。

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

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

Play の全ての非同期処理に関する API 呼び出しは Future を返します。例えば、play.api.libs.WS API を使って外部の Web サービスを呼び出す場合や、play.api.libs.Akka API 経由で Akka を使った非同期タスクを実行したり、アクターと通信したりする場合がそうです。

以下はコードブロックを非同期で実行して Future を得る簡単な方法です。

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

val futureInt: Future[Int] = scala.concurrent.Future {
  intensiveComputation()
}

Future がどのスレッド上で動作するか理解することが重要です。上記ふたつのコードブロックでは、Play のデフォルト実行コンテキストを import しています。これは、コールバックを受け取る Future API のすべてのメソッドに渡される implicit パラメータです。この実行コンテキストは、必ずしもそうではありませんが、多くの場合においてスレッドプールと等価です。

§Future を返す

これまでは、アクションをビルドするために Action.apply ビルダーメソッドを使ってきましたが、非同期に Result を返すためには 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))
}

§タイムアウト処理

何らかの問題が発生したとき web ブラウザが延々とブロックしてしまうことを避けるために、タイムアウトが役立つことが多々あります。そのような場合、 Promise と Promise のタイムアウトを簡単に組み合わせることができます。

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)
  }
}

次ページ: HTTP レスポンスのストリーミング