Scala fileUpload指令的自定义版本无法实现
当用户将文件上传到我的web服务时,我希望从POST请求中收集非二进制字段。它们包含上传文件的元数据。因此,我将akka http的Scala fileUpload指令的自定义版本无法实现,scala,akka,akka-stream,akka-http,Scala,Akka,Akka Stream,Akka Http,当用户将文件上传到我的web服务时,我希望从POST请求中收集非二进制字段。它们包含上传文件的元数据。因此,我将akka http的fileUpload指令修改为 def fileUpload3(fieldName: String): Directive1[(Map[String, String], FileInfo, Source[ByteString, Any])] = entity(as[Multipart.FormData]).flatMap { formData ⇒ ext
fileUpload
指令修改为
def fileUpload3(fieldName: String): Directive1[(Map[String, String], FileInfo, Source[ByteString, Any])] =
entity(as[Multipart.FormData]).flatMap { formData ⇒
extractRequestContext.flatMap { ctx ⇒
implicit val mat: Materializer = ctx.materializer
val fut =
formData.parts.fold((Map.empty[String, String], Option.empty[(FileInfo, Source[ByteString, Any])])) { case ((fields, pairOpt), part) ⇒
if (part.filename.nonEmpty && part.name == fieldName) {
fields → Some((FileInfo(part.name, part.filename.get, part.entity.contentType), part.entity.dataBytes))
} else if (part.filename.isEmpty && part.entity.contentType.mediaType == MediaTypes.`text/plain` && part.entity.isInstanceOf[HttpEntity.Strict]) {
fields.updated(part.name, part.entity.asInstanceOf[HttpEntity.Strict].data.utf8String) → pairOpt
} else {
fields → pairOpt
}
}
.collect {
case (fields, Some((info, stream))) ⇒
(fields, info, stream)
}
.runWith(Sink.headOption[(Map[String, String], FileInfo, Source[ByteString, Any])])
onSuccess(fut)
}
}.flatMap {
case Some(tuple) ⇒ provide(tuple)
case None ⇒ reject(MissingFormFieldRejection(fieldName))
}
虽然我看不出有什么不同,但当我使用它时,它失败了,除了以下例外:
akka.stream.AbruptIOTerminationException: Stream terminated without completing IO operation.
Caused by: akka.stream.impl.SubscriptionTimeoutException: Substream Source has not been materialized in 5000 milliseconds
at akka.stream.impl.fusing.SubSource.timeout(StreamOfStreams.scala:746)
我遗漏了什么,伙计们?起初我没有意识到这一点,但因为我们从单个连续流中获取所有字段,我们无法通过
Source[t]
提取其中一个字段以供以后的流化使用,即使akka流允许我们这样做
因此,在处理下一个多部分请求之前,必须排空多部分请求的每个部分
还要注意,下面的函数只收集二进制文件前面的文本字段
def fileUploadWithFields(fieldName: String): Directive1[(Map[String, String], FileInfo, Source[ByteString, Any])] =
entity(as[Multipart.FormData]).flatMap { formData ⇒
extractRequestContext.flatMap { ctx ⇒
implicit val mat: Materializer = ctx.materializer
// Because it's continuous stream of fields we MUST consume each field before switching to next one. [https://stackoverflow.com/q/52765993/226895]
val fut = formData.parts
.takeWhile(part ⇒ !(part.filename.isDefined && part.name == fieldName), inclusive = true)
.fold((Map.empty[String, String], Option.empty[(FileInfo, Source[ByteString, Any])])) { case ((fields, pairOpt), part) ⇒
if (part.filename.nonEmpty && part.name == fieldName) {
//println(s"Got file field: $part")
fields → Some((FileInfo(part.name, part.filename.get, part.entity.contentType), part.entity.dataBytes))
} else if (part.filename.isEmpty && part.entity.contentType.mediaType.isText && part.entity.isInstanceOf[HttpEntity.Strict]) {
//println(s"Got text field: $part")
val text = part.entity.asInstanceOf[HttpEntity.Strict].data.utf8String
fields.updated(part.name, text) → pairOpt
} else {
//println(s"IGNORING field: $part")
part.entity.discardBytes()
fields → pairOpt
}
}
.collect {
case (fields, Some((info, stream))) ⇒
//println(s"Completed scanning fields: ${(fields, info, stream)}")
(fields, info, stream)
}
.runWith(Sink.headOption[(Map[String, String], FileInfo, Source[ByteString, Any])])
onSuccess(fut)
}
}.flatMap {
case Some(tuple) ⇒ provide(tuple)
case None ⇒ reject(MissingFormFieldRejection(fieldName))
}
谢谢你@expert