Playframework and FileUpload in RESTful
File Upload in RESTful API right now, there are only 3 options till now.
1. Base64 encode the file, at the expense of increasing the data size by around 33%.
2. Send the file first in a multipart/form-data POST, and return and ID to the client. The client then sends the metadata with the ID. The server re-associates the file and the metadata.
3. Send the metadata first, and return an ID to the client. The client then sends the file with the ID, and the server re-associates the file and the metadata.
I may choose #2 or #3. Looking at play framework Uploading Files Chapter.
1. Playframework File Upload
I will use “multipart/form-data” encoding to make the form support mix standard form data with file attachment data.
Logging Level
ch.qos.logback.classic.Level TRACE, DEBUG, INFO, WARN and ERROR
https://www.playframework.com/documentation/2.4.3/SettingsLogger
https://www.playframework.com/documentation/2.4.3/ScalaLogging
def upload = Action(parse.multipartFormData) { request =>
request.body.file("file").map { file =>
import java.io.File
val filename = file.filename
val contentType = file.contentType
Ok("Resume there.")
}.getOrElse {
BadRequest(Json.obj("status" -> "OK", "message" -> "No files in the attachment."))
}
}
I was originally thinking that I may separate it into 2 steps. 1 - uploading file, 2 - parse file. But after think about this again. I may change that to 1 single step.
2. Directly Upload the File and Other Params
route configuration to upload the file content
# Resume Routes
POST /api/v1/resume/parseFile controllers.ResumeController.parseFile()
POST /api/v1/resume/parseContent controllers.ResumeController.parseContent()
The logging configuration
<configuration>
<conversionRule conversionWord="coloredLevel" converterClass="play.api.Logger$ColoredLevel" />
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/application.log</file>
<encoder>
<pattern>%date [%level] from %logger in %thread - %message%n%xException</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%coloredLevel %logger{15} - %message%n%xException{10}</pattern>
</encoder>
</appender>
<appender name="ASYNCFILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
<appender name="ASYNCSTDOUT" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="STDOUT" />
</appender>
<logger name="play" level="INFO" />
<logger name="application" level="DEBUG" />
<!-- Off these ones as they are annoying, and anyway we manage configuration ourself -->
<logger name="com.avaje.ebean.config.PropertyMapLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.core.XmlConfigLoader" level="OFF" />
<logger name="com.avaje.ebeaninternal.server.lib.BackgroundThread" level="OFF" />
<logger name="com.gargoylesoftware.htmlunit.javascript" level="OFF" />
<logger name="controllers.ResumeController" level="DEBUG" />
<root level="WARN">
<appender-ref ref="ASYNCFILE" />
<appender-ref ref="ASYNCSTDOUT" />
</root>
</configuration>
Implementation Class to Deal with File
import java.io.FileInputStream
import com.wordnik.swagger.annotations._
import utils.IncludeLogger
import play.api.libs.json.{JsError, Json}
import play.api.mvc.{BodyParsers, Action, Controller}
…snip...
implicit val resumeWrites = Json.writes[Resume]
implicit val resumeReads = Json.reads[Resume]
@ApiOperation(value = "parseFile",
notes = "Parse the Resume File",
response = classOf[Resume],
httpMethod = "POST",
produces = "json",
position = 1)
@ApiImplicitParams(Array(
new ApiImplicitParam(
name="body",
value = "Resume File",
required = true,
dataType = "file",
paramType = "body")))
@ApiResponses(Array(new ApiResponse(code = 200, message = "Successful Parse Resume.", response = classOf[Resume]) ))
def parseFile = Action(parse.multipartFormData) { request =>
logger.debug("The request is here.")
val resume = request.body.files.map { file =>
val filename = file.filename
logger.debug("file: " + filename + " " + file.contentType)
val inputStream = new FileInputStream(file.ref.file)
ResumeUtil.parseInputStream(inputStream)
}
Ok(Json.toJson(resume))
}
Command to Play with Large Memory
>sbt clean update compile dist
>bin/classifier-play -Dconfig.file=conf/application.conf -Dhttp.port=8003 -Dhttp.address=0.0.0.0 -J-Xms1024M -J-Xmx8g -J-server
References:
http://stackoverflow.com/questions/3938569/how-do-i-upload-a-file-with-metadata-using-a-rest-web-service
http://stackoverflow.com/questions/4083702/posting-a-file-and-data-to-restful-webservice-as-json
File Upload for Scala
https://www.playframework.com/documentation/2.4.3/ScalaFileUpload
http://stackoverflow.com/questions/9452375/how-to-get-the-upload-file-with-other-inputs-in-play2