Search code examples
scalafile-uploadakkaakka-streamakka-http

File upload using Akka HTTP


I am trying to implement file upload functionality in my application using Akka HTTP. I am using akka-stream version 2.4.4.

Here is the code (modified from akka-doc)

path("fileupload") {
    post {
      extractRequestContext {
        ctx => {
          implicit val materializer = ctx.materializer
          implicit val ec = ctx.executionContext
          fileUpload("fileUpload") {
            case (metadata, byteSource) =>
              val location = FileUtil.getUploadPath(metadata)
              val updatedFileName = metadata.fileName.replaceAll(" ", "").replaceAll("\"", "")
              val uniqFileName = uniqueFileId.concat(updatedFileName)
              val fullPath = location + File.separator + uniqFileName
              val writer = new FileOutputStream(fullPath)
              val bufferedWriter = new BufferedOutputStream(writer)

              val result = byteSource.map(s => {
                bufferedWriter.write(s.toArray)
              }).runWith(Sink.ignore)

              val result1 = byteSource.runWith(Sink.foreach(s=>bufferedWriter.write(s.toArray)))
              Await.result(result1, 5.seconds)
              bufferedWriter.flush()
              bufferedWriter.close()
              complete(uniqFileName)
            /*onSuccess(result) { x =>
              bufferedWriter.flush()
              bufferedWriter.close()
              complete("hello world")
            }*/
          }
        }
      }
    }
  }

This code is working fine and is uploading the file to the given path. I am generating new file names by appending UUID to make sure that the file names are unique. So I need to return the new file name to the caller. However, this method is not returning the filename always. Sometimes, it is finishing with Response has no content.

Can anyone let me know what I am doing wrong here?


Solution

  • There is no need to use the standard blocking streams when you have reactive streams for that purpose:

      path("fileUpload") {
        post {
          fileUpload("fileUpload") {
            case (fileInfo, fileStream) =>
              val sink = FileIO.toPath(Paths.get("/tmp") resolve fileInfo.fileName)
              val writeResult = fileStream.runWith(sink)
              onSuccess(writeResult) { result =>
                result.status match {
                  case Success(_) => complete(s"Successfully written ${result.count} bytes")
                  case Failure(e) => throw e
                }
              }
          }
        }
      }
    

    This code will upload fileUpload multipart field to a file inside /tmp directory. It just dumps the content of the input source to the respective file sink, returning a message upon the completion of the write operation.

    You may also want to tweak the dispatcher used for FileIO sources and sinks, as described in their scaladocs.