问题 使用Akka HTTP上传文件


我正在尝试使用Akka HTTP在我的应用程序中实现文件上载功能。我在用 akka-stream 版 2.4.4

这是代码(修改自 阿卡-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")
            }*/
          }
        }
      }
    }
  }

此代码工作正常,并将文件上载到给定路径。我通过附加UUID来生成新的文件名,以确保文件名是唯一的。所以我需要将新文件名返回给调用者。但是,此方法始终不返回文件名。有时,它正在完成 Response has no content

任何人都可以让我知道我在做错了什么吗?


8554
2018-05-25 07:10


起源

这不是你问题的答案,而是研究 doc.akka.io/docs/akka/2.4.6/scala/stream/... 而不是手动写入文件。此外,在路线中使用Await.result是非常糟糕的风格。 - Rüdiger Klaehn
请看那个。我尝试使用onSuccess而不是Await,胸围相同。所以我尝试了Await。感谢您的回复,让我试试这个链接。 - Yadu Krishnan
@RüdigerKlaehn我试过FileIO,仍然存在同样的问题:( - Yadu Krishnan
我认为“响应没有内容”的真正原因是你正在阅读'byteSource'两次,'result'和'result1'。如果'结果'首先到达那里,那么'result1'就没有消耗了。 - akauppi


答案:


当您为此目的使用反应流时,无需使用标准阻塞流:

  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
            }
          }
      }
    }
  }

此代码将上传 fileUpload multipart字段到一个文件里面 /tmp 目录。它只是将输入源的内容转储到相应的文件接收器,在写入操作完成时返回消息。

您可能还想调整用于的调度程序 FileIO 源和汇,如中所述 他们的scaladocs


15
2018-05-25 09:12



即便如此,我有时也没有得到回应:(。它显示'响应是空的'。唯一的区别是我使用的是2.4.4而不是2.4.6。 - Yadu Krishnan
你是如何测试的?卷曲?这是一个非常大的文件吗? - Rüdiger Klaehn
我正在使用Chome Extension,Advanced Rest客户端进行测试。该文件不是很大。试过txt,sql,jpeg,png文件。最小3kb至最大2 mb。 - Yadu Krishnan
将代码替换为@VladimirMatveev建议的代码,看看是否可以用curl重现问题。添加重现问题的步骤。那你就有机会帮助你。 - Rüdiger Klaehn
这个答案对我很有帮助。我只需要将以下内容添加到我的 application.conf 避免 EntityStreamSizeException: parsing.max-content-length = infinite - Matthias Braun


如果您只需要上传文件但在上传文件流完成之前没有做任何事情,那么有更简单的方法:

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)
  }

查看文档: https://doc.akka.io/docs/akka-http/current/routing-dsl/directives/file-upload-directives/storeUploadedFile.html


0
2018-05-17 13:47