Question

I wanted to write an app in Play! framework 2.2.x as an exercise in asynchronous approach and the play.api.libs.iterattee package. What I wanted to do was get a POST request with a large file upload in its body and send it, chunk by chunk, as a download to a separate request.

The "Play for Scala" book and this example lead me receiving the file within the request in chunks I can accumulate or iterate over in any Iteratee I define in a custom BodyParser. When it comes to serving a chunked response, I need a provide an Enumerator to the response - like Ok.chunked(enumerator) and it will be served to the client as the Enumerator provides new chunks.

Now that I want to put it all together, I can't seem to find an elegant and reactive way to propagate asynchronously the new data chunks from the uploading client to the downloading. There doesn't seem to be a good way to "forward" data received by an Iteratee (in this case in the BodyParser) to a new Enumerator (in this case the one providing output to the response).

I have come up with two ways of doing that, but neither of them seems good enough for me:

  • use play.api.libs.iteratee.Concurrent.boradcast to generate a Channel and an Enumerator and within my BodyParser push each received chunk to the Channel. The Channels don't have the property that the Enumerators have - the Enumerator doesn't produce a new value until the Iteratee applied over it consumes the old one. If I were to do it this way, if the uploader had a faster internet connection than the downloader, I would have a "swelling" Channel using up more and more memory, instead of handling one chunk at a time.

  • create a proxy akka Actor and send the uploaded file, chunk by chunk to it, only sending the next one after the last one has been consumed by the downloader. But in this case the confirmation of each chunk being sent to the downloader would have to go through the Actor proxy so that the uploader Iteratee would send another chunk to the proxy. This seems like unnecessary overhead for me.

I guess my question is twofold:

  1. Is there a simple way of constructing a "forwarding" Enumerator generating the values received by an Iteratee?
  2. In my use case, what is the correct way of implementing the scenario I have in mind using Play and Scala's asynchronous toolkit?
Was it helpful?

Solution

You can use Concurrent.joined:

val (iteratee, enumerator) = Concurrent.joined[Array[Byte]]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top