Question

I'm developing a REST API with Play 2 and I'm wondering how to implement file upload functionality.

I've read the official Play documentation but it just provides a multipart/form-data example, while my backend does not provide any form... it just consists of a REST API to be invoked by a JavaScript client or whatever else.

That said, what's the correct way to implement such an API? Should I implement a PartHandler and then still use the mutipartFormData parser? How should I pass the file content to the API? Is there any exhaustive example on this topic?

Any help would be really appreciated.

Was it helpful?

Solution 4

OK, thank you all for your suggestions... here below is how I solved my issue:

object Files extends Controller {

  def upload = SecuredAction[Files.type]("upload").async(parse.multipartFormData(partHandler)) { implicit request =>
    future { request.body.files.head.ref match {
      case Some((data, fileName, contentType)) => Ok(success(Json.obj("fileName" -> fileName)))
      case _ => BadRequest
    }}.recover { case e =>
     InternalServerError(error(errorProcessingRequest(e.getMessage)))
    }
  }

  ...

  private def partHandler = {
    parse.Multipart.handleFilePart {
      case parse.Multipart.FileInfo(partName, fileName, contentType) =>
        Iteratee.fold[Array[Byte], ByteArrayOutputStream](
          new ByteArrayOutputStream
        ) { (outputStream, data) =>
          outputStream.write(data)
          outputStream
        }.map { outputStream =>
          outputStream.close()
          Some(outputStream.toByteArray, fileName, contentType.get)
        }
    }
  }
}

I hope it helps.

OTHER TIPS

You should look into BodyParsers: http://www.playframework.com/documentation/2.2.x/ScalaBodyParsers

What you are trying to do is not especially complicated, especially if you are only handling smaller files that would fit in memory. After all uploading a file is just about sending the file as a body of a POST or something like that. It is not any different from receiving some XML or JSON in a request.

Hope this helps

import org.apache.http.entity.mime._
import java.io.File

import org.apache.http.entity.mime.content._
import java.io.ByteArrayOutputStream
import play.api.libs.ws.WS



val contents ="contents string"
val file = File.createTempFile("sample", ".txt")

val bw = new java.io.BufferedWriter(new java.io.FileWriter(file)
bw.write(new_contents);
bw.close();

builder.addPart("file", new FileBody(file, org.apache.http.entity.ContentType.create("text/plain"), "sample"))
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
val entity = builder.build

        val outputstream = new ByteArrayOutputStream
        entity.writeTo(outputstream)
        val header = (entity.getContentType.getName -> entity.getContentType.getValue)
        val response = WS.url("/post/file").withHeaders(header).post(outputstream.toByteArray())

To pass your contents, depending on your client side, you can encode the contents to Base64 at client side to pass the contents as Json (You can use Json body parser). Then on the server side you can decode the contents using a Base64 decoder (e.g. Apache Commons) to get the byte array. It will be as simple as

Base64.decodeBase64(YourEncodedFileContent)

When you have the byte array you can simply write it on disk or save it into database etc. We are using this approach in production and it works fine however we only handle small file uploads.

while my backend does not provide any form... it just consists of a REST API to be invoked by a JavaScript client

Then your backend is not a REST API. You should follow the HATEOAS principle, so you should respond with links and forms along with data to every GET request. You don't have to send back HTML, you can describe these things with hypermedia json or xml media types, for example with JSON-LD, HAL+JSON, ATOM+XML, etc... So you have to describe your upload form in your preferred hypermedia, and let the REST client to turn that description into a real HTML file upload form (if the client is HTML). After that you can send a multipart/form-data as usual (REST is media type agnostic, so you can send data in any media type you want, not just in a JSON format). Check the AJAX file upload techniques for further detail...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top