Iteratees

Iteratee is defined as a trait, Iteratee[E, +A], where E is the input type and A is the result type. The state of an Iteratee is represented by an instance of Step, which is defined as follows:

sealed trait Step[E, +A] {

  def it: Iteratee[E, A] = this match {
    case Step.Done(a, e) => Done(a, e)
    case Step.Cont(k) => Cont(k)
    case Step.Error(msg, e) => Error(msg, e)
  }

}

object Step {

  //done state of an iteratee
  case class Done[+A, E](a: A, remaining: Input[E]) extends Step[E, A]

  //continuing state of an iteratee.
  case class Cont[E, +A](k: Input[E] => Iteratee[E, A]) extends Step[E, A]

  //error state of an iteratee
  case class Error[E](msg: String, input: Input[E]) extends Step[E, Nothing]
}

The input used here represents an element of the data stream, which can be empty, an element, or an end of file indicator. Therefore, Input is defined as follows:

sealed trait Input[+E] {
  def map[U](f: (E => U)): Input[U] = this match {
    case Input.El(e) => Input.El(f(e))
    case Input.Empty => Input.Empty
    case Input.EOF => Input.EOF
  }
}

object Input {

  //An input element
  case class El[+E](e: E) extends Input[E]

  // An empty input
  case object Empty extends Input[Nothing]

  // An end of file input
  case object EOF extends Input[Nothing]

}

An Iteratee is an immutable data type and each result of processing an input is a new Iteratee with a new state.

To handle the possible states of an Iteratee, there is a predefined helper object for each state. They are:

  • Cont
  • Done
  • Error

Let's see the definition of the readLine method, which utilizes these objects:

def readLine(line: List[Array[Byte]] = Nil): Iteratee[Array[Byte], String] = Cont {
      case Input.El(data) => {
        val s = data.takeWhile(_ != '
')
        if (s.length == data.length) {
          readLine(s :: line)
        } else {
          Done(new String(Array.concat((s :: line).reverse: _*), "UTF-8").trim(), elOrEmpty(data.drop(s.length + 1)))
        }
      }
      case Input.EOF => {
        Error("EOF found while reading line", Input.Empty)
      }
      case Input.Empty => readLine(line)
    }

The readLine method is responsible for reading a line and returning an Iteratee. As long as there are more bytes to be read, the readLine method is called recursively. On completing the process, an Iteratee with a completed state (Done) is returned, else an Iteratee with state continuous (Cont) is returned. In case the method encounters EOF, an Iteratee with state Error is returned.

In addition to these, Play Framework exposes a companion Iteratee object, which has helper methods to deal with Iteratees. The API exposed through the Iteratee object is documented at https://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.libs.iteratee.Iteratee$.

The Iteratee object is also used internally within the framework to provide some key features. For example, consider the request body parsers. The apply method of the BodyParser object is defined as follows:

def apply[T](debugName: String)(f: RequestHeader => Iteratee[Array[Byte], Either[Result, T]]): BodyParser[T] = new BodyParser[T] {
    def apply(rh: RequestHeader) = f(rh)
    override def toString = "BodyParser(" + debugName + ")"
  }

So, to define BodyParser[T], we need to define a method that accepts RequestHeader and returns an Iteratee whose input is an Array[Byte] and results in Either[Result,T].

Let's look at some of the existing implementations to understand how this works.

The RawBuffer parser is defined as follows:

def raw(memoryThreshold: Int): BodyParser[RawBuffer] = BodyParser("raw, memoryThreshold=" + memoryThreshold) { request =>
      import play.core.Execution.Implicits.internalContext
      val buffer = RawBuffer(memoryThreshold)
      Iteratee.foreach[Array[Byte]](bytes => buffer.push(bytes)).map { _ =>
        buffer.close()
        Right(buffer)
      }
    }

The RawBuffer parser uses Iteratee.forEach method and pushes the input received into a buffer.

The file parser is defined as follows:

def file(to: File): BodyParser[File] = BodyParser("file, to=" + to) { request =>
      import play.core.Execution.Implicits.internalContext
      Iteratee.fold[Array[Byte], FileOutputStream](new FileOutputStream(to)) {
        (os, data) =>
        os.write(data)
        os
      }.map { os =>
        os.close()
        Right(to)
      }
    }

The file parser uses the Iteratee.fold method to create FileOutputStream of the incoming data.

Now, let's see the implementation of Enumerator and how these two pieces fit together.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.226.240