Multipart Form Data Example
Multipart Form Data Example
zio-http-example/src/main/scala/example/MultipartFormData.scala
package example
import scala.annotation.nowarn
import zio._
import zio.http._
object MultipartFormData extends ZIOAppDefault {
private val routes: Routes[Any, Response] =
Routes(
Method.POST / "upload" ->
handler { (req: Request) =>
if (req.header(Header.ContentType).exists(_.mediaType == MediaType.multipart.`form-data`))
for {
form <- req.body.asMultipartForm
.mapError(ex =>
Response(
Status.InternalServerError,
body = Body.fromString(s"Failed to decode body as multipart/form-data (${ex.getMessage}"),
),
)
response <- form.get("file") match {
case Some(file) =>
file match {
case FormField.Binary(_, data, contentType, transferEncoding, filename) =>
ZIO.succeed(
Response.text(
s"Received ${data.length} bytes of $contentType filename $filename and transfer encoding $transferEncoding",
),
)
case _ =>
ZIO.fail(
Response(Status.BadRequest, body = Body.fromString("Parameter 'file' must be a binary file")),
)
}
case None =>
ZIO.fail(Response(Status.BadRequest, body = Body.fromString("Missing 'file' from body")))
}
} yield response
else ZIO.succeed(Response(status = Status.NotFound))
},
).sandbox
@nowarn("msg=dead code")
private def program: ZIO[Client & Server, Throwable, Unit] =
for {
port <- Server.install(routes)
_ <- ZIO.logInfo(s"Server started on port $port")
client <- ZIO.service[Client]
response <- client
.host("localhost")
.port(port)
.batched(
Request.post(
"/upload",
Body.fromMultipartForm(
Form(
FormField.binaryField(
"file",
Chunk.fromArray("Hello, world!".getBytes),
MediaType.application.`octet-stream`,
filename = Some("hello.txt"),
),
),
Boundary("AaB03x"),
),
),
)
responseBody <- response.body.asString
_ <- ZIO.logInfo(s"Response: [${response.status}] $responseBody")
_ <- ZIO.never
} yield ()
override def run =
program.provide(Server.default, Client.default)
}
Multipart Form Data Streaming Example
zio-http-example/src/main/scala/example/MultipartFormDataStreaming.scala
package example
import scala.annotation.nowarn
import zio._
import zio.stream.{ZSink, ZStream}
import zio.http._
object MultipartFormDataStreaming extends ZIOAppDefault {
private val routes: Routes[Any, Response] =
Routes(
Method.POST / "upload-simple" -> handler { (req: Request) =>
for {
count <- req.body.asStream.run(ZSink.count)
_ <- ZIO.debug(s"Read $count bytes")
} yield Response.text(count.toString)
},
Method.POST / "upload-nonstream" -> handler { (req: Request) =>
for {
form <- req.body.asMultipartForm
count = form.formData.collect {
case sb: FormField.Binary =>
sb.data.size
case _ => 0
}.sum
_ <- ZIO.debug(s"Read $count bytes")
} yield Response.text(count.toString)
},
Method.POST / "upload-collect" -> handler { (req: Request) =>
for {
sform <- req.body.asMultipartFormStream
form <- sform.collectAll
count = form.formData.collect {
case sb: FormField.Binary =>
sb.data.size
case _ => 0
}.sum
_ <- ZIO.debug(s"Read $count bytes")
} yield Response.text(count.toString)
},
Method.POST / "upload" -> handler { (req: Request) =>
if (req.header(Header.ContentType).exists(_.mediaType == MediaType.multipart.`form-data`))
for {
_ <- ZIO.debug("Starting to read multipart/form stream")
form <- req.body.asMultipartFormStream
.mapError(ex =>
Response(
Status.InternalServerError,
body = Body.fromString(s"Failed to decode body as multipart/form-data (${ex.getMessage}"),
),
)
count <- form.fields.flatMap {
case sb: FormField.StreamingBinary =>
sb.data
case _ =>
ZStream.empty
}.run(ZSink.count)
_ <- ZIO.debug(s"Finished reading multipart/form stream, received $count bytes of data")
} yield Response.text(count.toString)
else ZIO.succeed(Response(status = Status.NotFound))
},
).sandbox @@ Middleware.debug
@nowarn("msg=dead code")
private def program: ZIO[Server, Throwable, Unit] =
for {
port <- Server.install(routes)
_ <- ZIO.logInfo(s"Server started on port $port")
_ <- ZIO.never
} yield ()
override def run =
program
.provide(
ZLayer.succeed(Server.Config.default.enableRequestStreaming),
Server.live,
)
}