Streaming and proxying with Play part 2

September 12, 2014

In a last post, we’ve seen some streaming and proxying examples with Play Framework. Let’s see how we can simplify and improve the last example with an implicit class.


Update : As Martin said in the comments, since Play 2.3, WS provides a getStream method returning what we’re trying to achieve, i.e. a Future[(WSResponseHeaders, Enumerator[Array[Byte]])].

But you can still read the following post for pedagogical purpose :)


Define a new function

First, let’s create a new case class, just for readability purpose.

case class WSStream(wsr: WSResponseHeaders, stream: Enumerator[Array[Byte]])

Then we create our new function.

def getAndStream(wsr: WSRequestHolder) = { 
    
  def consumer(promiseToFeed: Promise[WSStream]) = { rs: WSResponseHeaders =>
    val (wsConsumer, stream) = joined[Array[Byte]]
    promiseToFeed.success(WSStream(rs, stream))
    wsConsumer
  }
  
  val resultPromise = Promise[WSStream]
  wsr.get(consumer(resultPromise)).map(_.run)
  resultPromise.future
}

This is quite similar as the code seen in the previous example, except that all the tricky parts are in a separated function.

We can call this function to get a stream enumerator (and the response headers) from the WS response.

  val headersAndStream = getAndStream(WS.url("http://dumps.wikimedia.org/simplewiki/latest/simplewiki-latest-pages-articles.xml.bz2"))
  val streamEnumerator = headersAndStream.map(_.stream)

This is a little easier to read but we can do better!

Define an implicit class

Let’s hide all the complexity in an implicit class.

implicit class RichWSRequestHolder(val self: WSRequestHolder) {
    def getAndStream = {    

        def consumer(promiseToFeed: Promise[WSStream]) = { rs: WSResponseHeaders =>        
          val (wsConsumer, stream) = joined[Array[Byte]]
          promiseToFeed.success(WSStream(rs, stream))
          wsConsumer
        }

        val resultPromise = Promise[WSStream]
        self.get(consumer(resultPromise)).map(_.run)
        resultPromise.future
    }
  }

RichWSRequestHolder will implicitly add a new getAndStream method on the WSRequestHolder class. Don’t worry, everything is resolved at compile time so it’s safe.

Now you can import the RichWSRequestHolder class anywhere, and you will be able to call a Web Service like this to get a WSStream :

  val headersAndStream = WS.url("http://dumps.wikimedia.org/simplewiki/latest/simplewiki-latest-pages-articles.xml.bz2").getAndStream

Really easier isn’t it?

N.B : Of course you can do the same with other HTTP methods (POST, PUT, etc.).

comments powered by Disqus