2

The plugin play-reactivemongo offers an easy way to upload a file:

def upload = Action(gridFSBodyParser(gridFS)) { request =>
  val futureFile: Future[ReadFile[BSONValue]] = request.body.files.head.ref
  futureFile.map { file =>
    // do something
    Ok
  }.recover { case e: Throwable => InternalServerError(e.getMessage)  }
}

Unfortunately this solution doesn't suit me because:

  • I would like only my DAO layer to depend on reactive-mongo.
  • I need to save the file only if a user is authenticated (with SecureSocial) and use some user's properties as checks and metadata.
  • If no user is authenticated the request body shouldn't be parsed at all (see also this question).

It would be something along the lines

def upload = SecuredAction { request =>
  val user = request.user
  val enumerator = an enumrator from the body parsing ???
  myDAO.saveFile(user, enumerator)

object myDAO {
  def saveFile(user:User, enumerator:Enumerator[Array[Byte]]) = {
    ...
    val fileToSave = DefaultFileToSave(...)
    gridfs.save(enumerator, fileToSave)
    ...
  } 
}

Unfortunately it seems there is no way to get an enumerator from the parsing of the request body. The only way seems to provide the Action with a parser and an Iteratee that will be fed with the the body being parsed.

I couldn't figure out how to achieve it in a reactive way (without using a temporary file or storing the body in memory). Is it at all possible?

Community
  • 1
  • 1
Franck Valentin
  • 1,115
  • 8
  • 14

1 Answers1

2

Actually, you might consider not using girdFS built-in parser at all:

val gfs = new GridFS(db)

// the controller method, Authenticated here is custom object extending ActionBuilder 
def upload = Authenticated.async(parse.multipartFormData) { request =>
  ...
  request.body.file("photo") match {
    // handle error cases
    ...
    case Some(photo) =>
      val fileToSave = DefaultFileToSave(photo.filename, photo.contentType)
      // here some more operations, basically you don't need the and need only photo.ref.file
      val enumerator = Enumerator(Image(photo.ref.file).fitToWidth(120).write)
      gfs.save(enumerator, fileToSave) map {
        //handle responses and stuff
        ...
      }
    }
  }
}
Andrey Neverov
  • 2,135
  • 1
  • 12
  • 21
  • Thanks for your reply Andrey. I first came to this solution but unfortunately it's not reactive as parse.multipartFormData stores the file in a temp directory first. Good to have this workaround listed though. – Franck Valentin May 21 '14 at 12:39
  • Looking through play sources it seems that if you create your own BodyParser and consume incoming file in a request for example as an array of bytes into an Iteratee, it will solve your problem. Take a look at implementation of 'multipartParser' here: https://github.com/playframework/playframework/blob/92078f9cc751a5c19117dede18c7ca63aca73347/framework/src/play/src/main/scala/play/api/mvc/ContentTypes.scala#L615 – Andrey Neverov May 21 '14 at 14:54