4

I would like to know how can I process multipart content using the http4s library.

Imagine a service with the following snippet (the complete gist is here):

  case GET -> Root / "form" =>
   Ok(
   """|<html>
      |<body>
      |<form method="post" action="/post" enctype="multipart/form-data">
      | <input type="date" name="birthDate" placeholder="birthDate">
      | <input type="file" name="dataFile">
      | <input type="submit">
      |</form></body></html>""".stripMargin).
      withContentType(Some(`Content-Type`(`text/html`)))

case req @ POST -> Root / "post" => {
 req.decode[Multipart[IO]] { m =>
  Ok(
    s"""Multipart Data\nParts:${m.parts.length}
       |${m.parts.map { case f: Part[IO] => { f.name + ", headers: " + f.headers.mkString(",")} }.mkString("\n")}""".stripMargin)
}
  }

If I execute the service and fill the corresponding fields, I obtain an output like the following:

Multipart Data
Parts:2
Some(birthDate), headers: Content-Disposition: form-data; name="birthDate"
Some(dataFile), headers: Content-Disposition: form-data; name="dataFile"; 
 filename="file.pdf",Content-Type: application/pdf

So I know how to obtain information about the parts, which are elements of type Part[IO] and contain headers and body.

What I would like is to know how to process those parts. In this case, for example, I would like to open the file and inform about its length. What is the idiomatic way to do that?

Labra
  • 1,412
  • 1
  • 13
  • 33

1 Answers1

3

The body of Part[IO] is a Stream[F[_],Byte] which can be processed using methods from the fs2 library.

There are several possibilities, one possibility is to write the stream contents to a file using the io.file.writeAll and io.file.writeAllASync methods.

Another possibility that works for string-based files is to process the stream's contents using the utf8Decode method.

The result could be:

  case req @ POST -> Root / "post" => {
    req.decode[Multipart[IO]] { m => {
      m.parts.find(_.name == Some("dataFile")) match {
        case None => BadRequest(s"Not file")
        case Some(part) => for {
          contents <- part.body.through(utf8Decode).runFoldMonoid
          response <- Ok(s"""Multipart Data\nParts:${m.parts.length}
                            |File contents: ${contents}""".stripMargin)
        } yield response
      }
    }
   }
  }
 }

The previous snippet would return the contents of the file.

Labra
  • 1,412
  • 1
  • 13
  • 33