Play 2.0 フレームワークのリアクティブ I/O の概念を理解しようとしています。最初から理解を深めるために、フレームワークのヘルパーをスキップしてさまざまな種類の iteratee を構築し、リクエスト本文を解析するためにIteratee
a によって使用されるカスタムをゼロから作成することにしました。BodyParser
IterateesとScalaBodyParser のドキュメントで入手可能な情報と、リアクティブ I/O の再生に関する 2 つのプレゼンテーションから始めて、次のように思いつきました。
import play.api.mvc._
import play.api.mvc.Results._
import play.api.libs.iteratee.{Iteratee, Input}
import play.api.libs.concurrent.Promise
import play.api.libs.iteratee.Input.{El, EOF, Empty}
01 object Upload extends Controller {
02 def send = Action(BodyParser(rh => new SomeIteratee)) { request =>
03 Ok("Done")
04 }
05 }
06
07 case class SomeIteratee(state: Symbol = 'Cont, input: Input[Array[Byte]] = Empty, received: Int = 0) extends Iteratee[Array[Byte], Either[Result, Int]] {
08 println(state + " " + input + " " + received)
09
10 def fold[B](
11 done: (Either[Result, Int], Input[Array[Byte]]) => Promise[B],
12 cont: (Input[Array[Byte]] => Iteratee[Array[Byte], Either[Result, Int]]) => Promise[B],
13 error: (String, Input[Array[Byte]]) => Promise[B]
14 ): Promise[B] = state match {
15 case 'Done => { println("Done"); done(Right(received), Input.Empty) }
16 case 'Cont => cont(in => in match {
17 case in: El[Array[Byte]] => copy(input = in, received = received + in.e.length)
18 case Empty => copy(input = in)
19 case EOF => copy(state = 'Done, input = in)
20 case _ => copy(state = 'Error, input = in)
21 })
22 case _ => { println("Error"); error("Some error.", input) }
23 }
24 }
(注:これらはすべて私にとって初めてのことなので、これについての何かが完全にがらくたである場合はご容赦ください。) Iteratee はかなりばかげており、すべてのチャンクを読み取り、受信したバイト数を合計し、いくつかのメッセージを出力するだけです。一部のデータを使用してコントローラー アクションを呼び出すと、すべてが期待どおりに機能します。すべてのチャンクが Iteratee によって受信され、すべてのデータが読み取られると完了状態に切り替わり、要求が終了することを確認できます。
次の 2 つのケースの動作を確認したかったので、コードをいじり始めました。
- すべての入力が読み取られる前にエラー状態に切り替わります。
- すべての入力が読み込まれる前に完了状態に切り替え、 の
Result
代わりに を返しInt
ます。
上記のドキュメントの私の理解では、両方が可能であるはずですが、実際には観察された動作を理解できません。最初のケースをテストするために、上記のコードの 17 行目を次のように変更しました。
17 case in: El[Array[Byte]] => copy(state = if(received + in.e.length > 10000) 'Error else 'Cont, input = in, received = received + in.e.length)
そのため、10000 バイトを超える受信があった場合にエラー状態に切り替える条件を追加しました。私が得る出力はこれです:
'Cont Empty 0
'Cont El([B@38ecece6) 8192
'Error El([B@4ab50d3c) 16384
Error
Error
Error
Error
Error
Error
Error
Error
Error
Error
Error
その後、リクエストは永遠にハングアップし、決して終了しません。上記のドキュメントからの私の期待は、Iterateeerror
内で関数を呼び出すとfold
、処理を停止する必要があるということでした。ここで起こっていることは、Iteratee の fold メソッドが呼び出された後error
に数回呼び出されるということです。そうすると、リクエストがハングします。
すべての入力を読み取る前に完了状態に切り替えると、動作は非常に似ています。15 行目を次のように変更します。
15 case 'Done => { println("Done with " + input); done(if (input == EOF) Right(received) else Left(BadRequest), Input.Empty) }
そして17行目から:
17 case in: El[Array[Byte]] => copy(state = if(received + in.e.length > 10000) 'Done else 'Cont, input = in, received = received + in.e.length)
次の出力が生成されます。
'Cont Empty 0
'Cont El([B@16ce00a8) 8192
'Done El([B@2e8d214a) 16384
Done with El([B@2e8d214a)
Done with El([B@2e8d214a)
Done with El([B@2e8d214a)
Done with El([B@2e8d214a)
また、リクエストは永遠にハングします。
私の主な質問は、上記のケースでリクエストがハングしている理由です。誰かがこれに光を当てることができれば、私はそれを大いに感謝します!