Paul Draper
Paul Draper

Reputation: 83245

How to get InputStream from request in Play

Ithink this used to be possible in Play 1.x, but I can't find how to do it in Play 2.x

I know that Play is asynchronous and uses Iteratees. However, there is generally much better support for InputStreams.

(In this case, I will be using a streaming JSON parser like Jackson to process the request body.)

How can I get an InputStream from a chunked request body?

Upvotes: 3

Views: 1295

Answers (2)

Ralph
Ralph

Reputation: 32284

I was able to get this working with the following code:

// I think all of the parens and braces line up -- copied/pasted from code
val pos = new PipedOutputStream()
val pis = new PipedInputStream(pos)
val result = Promise[Either[Errors, String]]()

def outputStreamBodyParser = {
  BodyParser("outputStream") {
    requestHeader =>
      val length = requestHeader.headers(HeaderNames.CONTENT_LENGTH).toLong

      Future {
        result.completeWith(saveFile(pis, length)) // save returns Future[Either[Errors, String]]
      }

      Iteratee.fold[Array[Byte], OutputStream](pos) {
        (os, data) =>
          os.write(data)
          os
      }.map {
        os =>
          os.close()
          Right(os)
      }
  }
}

Action.async(parse.when(
  requestHeaders => {
    val maybeContentLength = requestHeaders.headers.get(HeaderNames.CONTENT_LENGTH)
    maybeContentLength.isDefined && allCatch.opt(maybeContentLength.get.toLong).isDefined
  },
  outputStreamBodyParser,
  requestHeaders => Future.successful(BadRequest("Missing content-length header")))) {
    request =>
    result.future.map {
      case Right(fileRef) => Ok(fileRef)
      case Left(errors) => BadRequest(errors)
  }
}

Upvotes: 2

wingedsubmariner
wingedsubmariner

Reputation: 13667

Play 2 is meant to be fully asynchronous, so this isn't easily possible or desirable. The problem with InputStream is there is no push back, there is no way for the reader of the InputStream to communicate to the input that it wants more data without blocking on read. Technically it is possible to write an Iteratee that could read data and put it into an InputStream, and would wait for a call to read on the InputStream before asking the Enumerator for more data, but it would be dangerous. You would have to make sure that the InputStream was closed properly, or the Enumerator would sit waiting forever (or until it times out) and the call to read must be made from a thread that is not running on the same ExecutionContext as the Enumerator and Iteratee or the application could deadlock.

Upvotes: 1

Related Questions