A basic application to handle multipart form data using akka-http with test cases in Scala


In my previous blogs, I have talked about file upload and its test cases using akka-http.

Now, in this blog I am going to explain the handling of multi part form data in akka-http with the help of a basic application which contains the code and its test cases.

So Let’s start with the dependencies for the application.
You have to add the following dependencies in you build.sbt :

 val akkaV = "2.4.10"
 val scalaTestV = "3.0.0"
 Seq(
 "com.typesafe.akka" %% "akka-actor" % akkaV,
 "com.typesafe.akka" %% "akka-http-experimental" % akkaV,
 "com.typesafe.akka" %% "akka-http-testkit" % akkaV,
 "org.scalatest" %% "scalatest" % scalaTestV % "test"
 )

Now, we have to create a handler to handle multipart form data :

package com.knoldus

import java.io.File

import akka.actor.ActorSystem
import akka.http.scaladsl.model.Multipart.BodyPart
import akka.http.scaladsl.model.{HttpResponse, Multipart, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.Materializer
import akka.stream.scaladsl.FileIO

import scala.concurrent.duration._
import scala.concurrent.{ExecutionContextExecutor, Future}

trait MultipartFormDataHandler {

implicit val system: ActorSystem

implicit def executor: ExecutionContextExecutor

implicit val materializer: Materializer

val routes = processMultiPartData

def processMultiPartData: Route = path("process" / "multipart" / "data") {
(post & entity(as[Multipart.FormData])) { formData =>
complete {
val extractedData: Future[Map[String, Any]] = formData.parts.mapAsync[(String, Any)](1) {

case file: BodyPart if file.name == "file" => val tempFile = File.createTempFile("process", "file")
file.entity.dataBytes.runWith(FileIO.toPath(tempFile.toPath)).map { ioResult=>
s"file ${file.filename.fold("Unknown")(identity)}" -> s"${ioResult.count} bytes"}

case data: BodyPart => data.toStrict(2.seconds).map(strict =>data.name -> strict.entity.data.utf8String)
}.runFold(Map.empty[String, Any])((map, tuple) => map + tuple)

extractedData.map { data => HttpResponse(StatusCodes.OK, entity = s"Data : ${data.mkString(", ")} has been successfully saved.")}
.recover {
case ex: Exception =>HttpResponse(StatusCodes.InternalServerError,entity = s"Error in processing multipart form data due to ${ex.getMessage}")
}
}
}
}
}

Whenever a file will come in request, a new file will be created in temp directory of your system.
Below, you can find the test cases for the handler :

package com.knoldus

import java.io.File

import akka.http.scaladsl.model.{ContentTypes, HttpEntity, Multipart, StatusCodes}
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.{FlatSpec, Matchers}

class MultipartFormDataHandlerSpec extends FlatSpec with Matchers with ScalatestRouteTest with MultipartFormDataHandler {
override def testConfigSource = "akka.loglevel = WARNING"

val firstName = Multipart.FormData.BodyPart.Strict("FirstName", "Rishi")
val lastName = Multipart.FormData.BodyPart.Strict("LastName", "Khandelwal")

"MultiPart Data Handler" should "be able to save multipart data when file has invalid key" in {
val fileData = Multipart.FormData.BodyPart.Strict("invalid", HttpEntity(ContentTypes.`text/plain(UTF-8)`, "This is test file"), Map("fileName" -> "rishi.txt"))
val expectedOutput = Map("FirstName" -> "Rishi", "LastName" -> "Khandelwal", "invalid" -> "This is test file")
val formData = Multipart.FormData(firstName, lastName, fileData)
Post(s"/process/multipart/data", formData) ~> routes ~> check {
status shouldBe StatusCodes.OK
responseAs[String] shouldBe s"""Data : ${expectedOutput.mkString(", ")} has been successfully saved."""
}
}

it should "be able to save multipart data when filename is not given" in {
val fileData = Multipart.FormData.BodyPart.Strict("file", HttpEntity(ContentTypes.`text/plain(UTF-8)`, "This is test file"), Map())
val expectedOutput = Map("FirstName" -> "Rishi", "LastName" -> "Khandelwal", "file Unknown" -> "17 bytes")
val formData = Multipart.FormData(firstName, lastName, fileData)
Post(s"/process/multipart/data", formData) ~> routes ~> check {
status shouldBe StatusCodes.OK
responseAs[String] shouldBe s"""Data : ${expectedOutput.mkString(", ")} has been successfully saved."""
}
}

it should "be able to save multipart data when file content is provided" in {
val fileData = Multipart.FormData.BodyPart.Strict("file", HttpEntity(ContentTypes.`text/plain(UTF-8)`, "This is test file"), Map("fileName" -> "rishi.txt"))
val expectedOutput = Map("FirstName" -> "Rishi", "LastName" -> "Khandelwal", "file rishi.txt" -> "17 bytes")
val formData = Multipart.FormData(firstName, lastName, fileData)
Post(s"/process/multipart/data", formData) ~> routes ~> check {
status shouldBe StatusCodes.OK
responseAs[String] shouldBe s"""Data : ${expectedOutput.mkString(", ")} has been successfully saved."""
}
}

it should "be able to save multipart data when file is read from given path" in {
val fileData = Multipart.FormData.BodyPart.fromPath("file", ContentTypes.`text/plain(UTF-8)`, new File("./README.md").toPath)
val expectedOutput = Map("FirstName" -> "Rishi", "LastName" -> "Khandelwal", "file README.md" -> "414 bytes")
val formData = Multipart.FormData(firstName, lastName, fileData)
Post(s"/process/multipart/data", formData) ~> routes ~> check {
status shouldBe StatusCodes.OK
responseAs[String] shouldBe s"""Data : ${expectedOutput.mkString(", ")} has been successfully saved."""
}
}
}

I hope, you enjoyed it and it will be helpful for you.

You can get full code here

Happy Blogging !!!

Advertisements

About Rishi Khandelwal

Sr. Software Consultant having more than 6 years industry experience. He has working experience in various technologies such as Scala, Java, Play, Akka, Spark, Hive, Cassandra, Akka-http, ElasticSearch, Backbone.js, html5, javascript, Less, Amazon EC2, WebRTC, SBT
This entry was posted in Akka, Scala. Bookmark the permalink.

2 Responses to A basic application to handle multipart form data using akka-http with test cases in Scala

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s