File upload using akka-http in Scala


Lets begin with some introduction of Akka-http first.

Akka HTTP is a layer to expose Actors to the web via HTTP and to enable them to consume HTTP services as a client. It is not an HTTP framework, it is an Actor-based toolkit for interacting with web services and clients. This toolkit is structured into several layers:

  • akka-http-core: A complete implementation of the HTTP standard, both as client and server.
  • akka-http: A convenient and powerful routing DSL for expressing web services.
  • akka-http-testkit: A test harness and set of utilities for verifying your web service implementations.

For more information, see here.

Now its time to go into some code.

To use akka-http, we need to add library in our application.  So to do the same, add following dependencies in build.sbt file.:


"com.typesafe.akka" %% "akka-stream-experimental" % "2.0.2",
"com.typesafe.akka" %% "akka-http-core-experimental" % "2.0.2",
"com.typesafe.akka" %% "akka-http-experimental" % "2.0.2"

Now we will write a route to which we can send our file upload request :


package com.rishi

import java.io.FileOutputStream
import java.util.UUID

import akka.actor.ActorSystem
import akka.http.scaladsl.model.{HttpResponse, Multipart, StatusCodes}
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer
import akka.util.ByteString

trait FileUpload {

  implicit val system = ActorSystem()
  implicit val executor = system.dispatcher
  implicit val materializer = ActorMaterializer()

  /**
   * Route for uploading file
   */
  def uploadFile: Route = {
    path("user" / "upload" / "file") {
      (post & entity(as[Multipart.FormData])) { fileData =>
          complete {
              val fileName = UUID.randomUUID().toString
              val temp = System.getProperty("java.io.tmpdir")
               val filePath = temp + "/" + fileName
              processFile(filePath,fileData).map { fileSize =>
              HttpResponse(StatusCodes.OK, entity = s"File successfully uploaded. Fil size is $fileSize")
              }.recover {
               case ex: Exception => HttpResponse(StatusCodes.InternalServerError, entity = "Error in file uploading")
               }
          }
      }
    }
  }

  private def processFile(filePath: String, fileData: Multipart.FormData) = {
    val fileOutput = new FileOutputStream(filePath)
    fileData.parts.mapAsync(1) { bodyPart ⇒
      def writeFileOnLocal(array: Array[Byte], byteString: ByteString): Array[Byte] = {
        val byteArray: Array[Byte] = byteString.toArray
        fileOutput.write(byteArray)
        array ++ byteArray
      }
      bodyPart.entity.dataBytes.runFold(Array[Byte]())(writeFileOnLocal)
    }.runFold(0)(_ + _.length)
  }

  val routes = uploadFile
}

Here, what we have done is, as route will get file upload request, it will take the file data as multipart and will create a file at our local machine in system’s temp folder.

That’s it. Hope you enjoyed. 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, Future, knoldus, Scala. Bookmark the permalink.

13 Responses to File upload using akka-http in Scala

  1. Pingback: File upload using akka-http in Scala | Arpit Suthar

  2. Hi, I am getting an error in the function processFile() – not enough arguments for method runFold: (implicit materializer: akka.stream.Materializer)scala.concurrent.Future[Array[Byte]]. Unspecified value parameter materializer. I tried adding these:

    import akka.actor.ActorSystem
    import akka.stream.ActorMaterializer
    implicit val system = ActorSystem(“Sys”)
    implicit val materializer = ActorMaterializer()

    Then I started getting error in the uploadFile function in line : processFile(filePath, fileData).map { fileSize =>

    ◾Cannot find an implicit ExecutionContext. You might pass an (implicit ec: ExecutionContext) parameter to your method or import scala.concurrent.ExecutionContext.Implicits.global

    Can you pls help me resolve this, so that the async nature is not lost

    • Hi Anand. Thanks for pointing me the error.

      I have updated the blog. I missed to include some implicit variables.
      Now please give it a try one more time and let me know if you get any error.

  3. Hello Rishi,
    One other thing I noticed is that, when I try to upload a file of size 150 MB, the POST request is failing after around 8-9 seconds. Any idea how to fix it?

  4. A.G. says:

    Hello Anand,
    150MB issue may be related to akka-http config. Default incoming request size is 8MB. Have you tried increasing akka.http.server.parsing.max-content-length ?
    http://doc.akka.io/docs/akka-stream-and-http-experimental/2.0.2/scala/http/configuration.html

  5. avis says:

    Looks good, How do we get form-data inside multipart?

  6. Pingback: Test cases for file upload using akka-http in Scala | Knoldus

  7. Pingback: A basic application to handle multipart form data using akka-http with test cases in Scala | Knoldus

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