13
votes

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?

2
This is not an answer to your question, but look into doc.akka.io/docs/akka/2.4.6/scala/stream/… instead of manually writing to a file. Also, using Await.result inside a route is really bad style.Rüdiger Klaehn
Will look at that. I tried with onSuccess instead of Await, bust same. So I tried with Await. Thanks for the reply, let me try the link.Yadu Krishnan
@RüdigerKlaehn I tried with FileIO, still the same issue exist :(Yadu Krishnan
I think the real reason for "Response has no content" is that you are reading 'byteSource' twice, for 'result' and 'result1'. If 'result' got there first, there's nothing for 'result1' to consume.akauppi

2 Answers

19
votes

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.

2
votes

If you need only uploading a file but not doing anything until upload finishes in the file stream, then there is much simpler way:

def tempDestination(fileInfo: FileInfo): File =
  File.createTempFile(fileInfo.fileName, ".tmp")

val route =
  storeUploadedFile("csv", tempDestination) {
    case (metadata, file) =>
      // do something with the file and file metadata ...
      file.delete()
      complete(StatusCodes.OK)
  }

See docs: https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/file-upload-directives/storeUploadedFile.html