Я пытаюсь понять концепции реактивного ввода-вывода платформы Play 2.0. Чтобы получить лучшее понимание с самого начала, я решил пропустить хелперы фреймворка для создания итераций разных типов и написать с нуля пользовательский Iteratee
, который будет использоваться BodyParser
для разбора тела запроса.
Начав с информации, доступной в документах Iteratees и ScalaBodyParser и двух презентаций о реактивном вводе-выводе, я пришел к этому :
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, и когда все данные прочитаны, он переключается в состояние выполнено, и запрос завершается.
Теперь я начал экспериментировать с кодом, потому что хотел увидеть поведение для этих двух случаев :
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
Затем запрос висит навсегда и никогда не заканчивается.Я ожидал от вышеупомянутых документов, что когда я вызываю функцию error
внутри fold
Iteratee, обработка должна быть остановлена. Здесь происходит следующее: метод fold объекта Iteratee вызывается несколько раз после того, как 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)
и снова запрос висит навсегда.
Мой главный вопрос, почему запрос зависает в вышеперечисленных случаях. Если бы кто-нибудь мог пролить свет на это, я был бы очень признателен!