=doc 18471 Document file uploads
This commit is contained in:
parent
644555a93f
commit
cd3a9ccf85
2 changed files with 118 additions and 0 deletions
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.http.scaladsl.server
|
||||
|
||||
import java.io.File
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import akka.http.scaladsl.model.Multipart.FormData.BodyPart
|
||||
import akka.stream.io.{ Framing, SynchronousFileSink }
|
||||
import akka.stream.scaladsl._
|
||||
import akka.http.scaladsl.model.Multipart
|
||||
import akka.util.ByteString
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Future
|
||||
|
||||
class FileUploadExamplesSpec extends RoutingSpec {
|
||||
|
||||
case class Video(file: File, title: String, author: String)
|
||||
object db {
|
||||
def create(video: Video): Future[Unit] = Future.successful(Unit)
|
||||
}
|
||||
|
||||
"simple-upload" in {
|
||||
val uploadVideo =
|
||||
path("video") {
|
||||
entity(as[Multipart.FormData]) { formData =>
|
||||
|
||||
// collect all parts of the multipart as it arrives into a map
|
||||
val allPartsF: Future[Map[String, Any]] = formData.parts.mapAsync[(String, Any)](1) {
|
||||
|
||||
case b: BodyPart if b.name == "file" =>
|
||||
// stream into a file as the chunks of it arrives and return a future
|
||||
// file to where it got stored
|
||||
val file = File.createTempFile("upload", "tmp")
|
||||
b.entity.dataBytes.runWith(SynchronousFileSink(file)).map(_ =>
|
||||
(b.name -> file))
|
||||
|
||||
case b: BodyPart =>
|
||||
// collect form field values
|
||||
b.toStrict(2.seconds).map(strict =>
|
||||
(b.name -> strict.entity.data.utf8String))
|
||||
|
||||
}.runFold(Map.empty[String, Any])((map, tuple) => map + tuple)
|
||||
|
||||
val done = allPartsF.map { allParts =>
|
||||
// You would have some better validation/unmarshalling here
|
||||
db.create(Video(
|
||||
file = allParts("file").asInstanceOf[File],
|
||||
title = allParts("title").asInstanceOf[String],
|
||||
author = allParts("author").asInstanceOf[String]))
|
||||
}
|
||||
|
||||
// when processing have finished create a response for the user
|
||||
onSuccess(allPartsF) { allParts =>
|
||||
complete {
|
||||
"ok!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MetadataActor {
|
||||
case class Entry(id: Long, values: Seq[String])
|
||||
}
|
||||
val metadataActor: ActorRef = system.deadLetters
|
||||
|
||||
"stream-csv-upload" in {
|
||||
val splitLines = Framing.delimiter(ByteString("\n"), 256)
|
||||
|
||||
val csvUploads =
|
||||
path("metadata" / LongNumber) { id =>
|
||||
entity(as[Multipart.FormData]) { formData =>
|
||||
val done = formData.parts.mapAsync(1) {
|
||||
case b: BodyPart if b.filename.exists(_.endsWith(".csv")) =>
|
||||
b.entity.dataBytes
|
||||
.via(splitLines)
|
||||
.map(_.utf8String.split(",").toVector)
|
||||
.runForeach(csv =>
|
||||
metadataActor ! MetadataActor.Entry(id, csv))
|
||||
case _ => Future.successful(Unit)
|
||||
}.runWith(Sink.ignore)
|
||||
|
||||
// when processing have finished create a response for the user
|
||||
onSuccess(done) {
|
||||
complete {
|
||||
"ok!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -74,3 +74,25 @@ Failures and exceptions inside the Routing DSL
|
|||
Exception handling within the Routing DSL is done by providing :class:`ExceptionHandler` s which are documented in-depth
|
||||
in the :ref:`exception-handling-scala` section of the documtnation. You can use them to transform exceptions into
|
||||
:class:`HttpResponse` s with apropriate error codes and human-readable failure descriptions.
|
||||
|
||||
File uploads
|
||||
^^^^^^^^^^^^
|
||||
|
||||
Handling a simple file upload from for example a browser form with a `file` input can be done
|
||||
by accepting a `Multipart.FormData` entity, note that the body parts are `Source` rather than
|
||||
all available right away, and so is the individual body part payload so you will need to consume
|
||||
those streams both for the file and for the form fields.
|
||||
|
||||
Here is a simple example which just dumps the uploaded file into a temporary file on disk, collects
|
||||
some form fields and saves an entry to a fictive database:
|
||||
|
||||
.. includecode2:: ../../code/docs/http/scaladsl/server/FileUploadExamplesSpec.scala
|
||||
:snippet: simple-upload
|
||||
|
||||
You can transform the uploaded files as they arrive rather than storing then in a temporary file as
|
||||
in the previous example. In this example we accept any number of ``.csv`` files, parse those into lines
|
||||
and split each line before we send it to an actor for further processing:
|
||||
|
||||
.. includecode2:: ../../code/docs/http/scaladsl/server/FileUploadExamplesSpec.scala
|
||||
:snippet: stream-csv-upload
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue