§Handling file upload
§Uploading files in a form using multipart/form-data
The standard way to upload files in a web application is to use a form with a special multipart/form-data
encoding, which lets you mix standard form data with file attachment data.
Note: The HTTP method used to submit the form must be
POST
(notGET
).
Start by writing an HTML form:
@helper.form(action = routes.Application.upload, 'enctype -> "multipart/form-data") {
<input type="file" name="picture">
<p>
<input type="submit">
</p>
}
Now define the upload
action using a multipartFormData
body parser:
def upload = Action(parse.multipartFormData) { request =>
request.body.file("picture").map { picture =>
import java.io.File
val filename = picture.filename
val contentType = picture.contentType
picture.ref.moveTo(new File(s"/tmp/picture/$filename"))
Ok("File uploaded")
}.getOrElse {
Redirect(routes.Application.index).flashing(
"error" -> "Missing file")
}
}
The ref
attribute give you a reference to a TemporaryFile
. This is the default way the multipartFormData
parser handles file upload.
Note: As always, you can also use the
anyContent
body parser and retrieve it asrequest.body.asMultipartFormData
.
At last, add a POST
router
POST / controllers.Application.upload()
§Direct file upload
Another way to send files to the server is to use Ajax to upload the file asynchronously in a form. In this case the request body will not have been encoded as multipart/form-data
, but will just contain the plain file content.
In this case we can just use a body parser to store the request body content in a file. For this example, let’s use the temporaryFile
body parser:
def upload = Action(parse.temporaryFile) { request =>
request.body.moveTo(new File("/tmp/picture/uploaded"))
Ok("File uploaded")
}
§Writing your own body parser
If you want to handle the file upload directly without buffering it in a temporary file, you can just write your own BodyParser
. In this case, you will receive chunks of data that you are free to push anywhere you want.
If you want to use multipart/form-data
encoding, you can still use the default multipartFormData
parser by providing a FilePartHandler[A]
and using a different Sink to accumulate data. For example, you can use a FilePartHandler[File]
rather than a TemporaryFile by specifying an Accumulator(fileSink)
:
type FilePartHandler[A] = FileInfo => Accumulator[ByteString, FilePart[A]]
def handleFilePartAsFile: FilePartHandler[File] = {
case FileInfo(partName, filename, contentType) =>
val perms = java.util.EnumSet.of(OWNER_READ, OWNER_WRITE)
val attr = PosixFilePermissions.asFileAttribute(perms)
val path = Files.createTempFile("multipartBody", "tempFile", attr)
val file = path.toFile
val fileSink = FileIO.toFile(file)
val accumulator = Accumulator(fileSink)
accumulator.map { case IOResult(count, status) =>
FilePart(partName, filename, contentType, file)
}(play.api.libs.concurrent.Execution.defaultContext)
}
def uploadCustom = Action(parse.multipartFormData(handleFilePartAsFile)) { request =>
val fileOption = request.body.file("name").map {
case FilePart(key, filename, contentType, file) =>
file.toPath
}
Ok(s"File uploaded: $fileOption")
}