Writing incremental computations using iteratees

There is another situation, which I haven't mentioned yet, where the server could be waiting for a remote response: when it reads the body of an incoming request. Indeed, according to the upload speed of the client, the server may be waiting between every chunk of data. In this situation, the job of the server consists of parsing the request body to make it available to the action's code. However, you don't want the server to block for the inputs to arrive, and you probably don't want the server to load the entire request body in memory, just in case it is a big file! Play provides a specific abstraction named Iteratee for the purpose of incrementally consuming a stream of data in a non-blocking way.

Note

The iteratee's API is not intended to be used from the Java code. Nevertheless, Java developers should not skip this section as it gives useful details on the internals of Play.

An Iteratee[E, A] object represents an incremental computation eventually producing a value of type A (for example, a request body) and consuming input of type E (for example, an HTTP chunk). An iteratee is in one of the three following states:

  • Done(a, e): The computation is finished, it produced the value a, and e remains as an unused input
  • Error(msg, e): The computation resulted in an error described by msg, and e remains as an unused input
  • Cont(k): The computation is in progress, and k is a continuation function taking the next input as the parameter and returning the next state of the iteratee (that is either Done, Error, or Cont)

For the sake of illustration, here is how you can define an iteratee consuming a sentence (a stream of letters ending with a point):

import play.api.libs.iteratee.{Iteratee, Input, Cont, Done, Error}
def step(parsed: String): Iteratee[Char, String] = Cont {
  case Input.El('.') => Done(parsed)
  case Input.El(char) => step(parsed :+ char)
  case Input.Empty => step(parsed)
  case Input.EOF => Error("A sentence must end with a point!", Input.EOF)
}
val sentence = step("")

The step function effectively implements one step of the incremental computation: it takes the next input, which can either be a character, an empty input, or the special Input.EOF object representing the end of the input. If we input the character ., the computation ends and yields the text that preceded the point. If the input is any other character, the computation continues with the character appended to the previously parsed content. If the end of the input arrives before a point has been encountered, the computation yields an error message. The sentence value is a step with an empty string as the initial state.

The Iteratee companion object provides various methods making it easier to define iteratees. For instance, Iteratee.consume concatenates all the inputs in one big sequence. Iteratee.fold works in a similar way to the fold method of Scala's collections. Another method worth mentioning is Iteratee.foreach, which produces nothing but executes a side-effecting function each time an input arrives.

Let's reconsider the way Play processes HTTP requests and calls your action's code now that you are familiar with iteratees.

Body parsers were mentioned in Chapter 1, Building a Web Service; they are responsible for reading the body of the requests. Body parsers are able to read the request body in a non-blocking way because they are defined as incremental computations. As a first approximation, a BodyParser[A] object can be thought of as an Iteratee[Array[Byte], A] object: an incremental computation processing each request chunk as an array of bytes and producing a value of type A (where A is, for instance, JsValue in the case of a JSON request).

When a client performs an HTTP request, it first sends the request headers and then the request body, eventually using several chunks of data. When Play receives the request, it first finds the corresponding action to invoke, according to the request URL and the application's routes. Then, the request body is incrementally parsed by the action's body parser. Finally, the result of the body parsing is supplied to the action's code.

In some cases though, it can be useful to short-circuit the parsing process and return an HTTP result without even invoking the action's code. For instance, it can be useful to limit the size of requests to 1 KB and let the body parser directly produce an HTTP response with status code 413 (entity too large) before even parsing the whole body if a client sends a request that is too big. That's why, body parsers are rather represented as Iteratee[Array[Byte], Either[Result, A]]. That way, they have a chance to directly send a result to the client, without invoking the action's code.

At last, the request headers may contain useful information to guide the parsing process. For instance, the default body parser chooses which parsing algorithm to use (JSON, XML, URL-encoded, multipart, and so on) according to the Content-Type request header. That's why the exact type signature of body parsers is as follows:

trait BodyParser[A]
  extends RequestHeader => Iteratee[Array[Byte], Either[Result, A]]

It means that a BodyParser[Foo] object is an incremental computation parameterized by the request headers, processing each request chunk as an array of bytes, and either producing an HTTP result or a Foo value.

Finally, note that a body parser is not forced to load all the content of the request body in memory. For instance, Play defines a parse.file body parser that copies the request body to a file, chunk by chunk, without ever loading the whole request body in memory.

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

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