-
Notifications
You must be signed in to change notification settings - Fork 593
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document consuming multipart entities #807
Comments
@jrudolph I would like to take this task if other's haven't picked it up. |
👍 @domitian yes, please go ahead. Thanks for taking this on. |
Hi, sorry for raising an old issue. Today at work, we had to deal with a multipart form containing form data part (that are not files) and a file. Our initial approach was to use So I wrote this, and I'm wondering if it would fit in the akka-http code base: type FileNameFn = FileInfo ⇒ File
final case class PartsAndFiles(form: immutable.Map[String, List[String]], files: immutable.Seq[(FileInfo, File)]) {
def addForm(fieldName: String, content: String): PartsAndFiles = this.copy(
form = {
val existingContent: List[String] =this.form.getOrElse(fieldName, List.empty)
val newContents: List[String] = content :: existingContent
this.form + (fieldName -> newContents)
}
)
def addFile(info: FileInfo, file: File): PartsAndFiles = this.copy(
files = this.files :+ ((info, file))
)
}
object PartsAndFiles {
val Empty = PartsAndFiles(immutable.Map.empty, immutable.Seq.empty)
}
def fileUploadAndForm(
fileFields: immutable.Seq[(String, FileNameFn)]
): Directive1[PartsAndFiles] =
entity(as[Multipart.FormData]).flatMap { formData ⇒
extractRequestContext.flatMap { ctx ⇒
implicit val mat = ctx.materializer
implicit val ec = ctx.executionContext
val uploadingSink =
Sink.foldAsync[PartsAndFiles, Multipart.FormData.BodyPart](PartsAndFiles.Empty) {
(acc, part) ⇒
def discard(): Future[PartsAndFiles] = {
part.entity.discardBytes()
Future.successful(acc)
}
part.filename.map { _ ⇒
fileFields.find(_._1 == part.name)
.map {
case (_, destFn) ⇒
val fileInfo = FileInfo(part.name, part.filename.get, part.entity.contentType)
val dest = destFn(fileInfo)
part.entity.dataBytes.runWith(FileIO.toPath(dest.toPath)).map { _ ⇒
acc.addFile(fileInfo, dest)
}
}.getOrElse(discard())
} getOrElse {
part.entity match {
case HttpEntity.Strict(ct, data) if ct.isInstanceOf[ContentType.NonBinary] ⇒
val charsetName = ct.asInstanceOf[ContentType.NonBinary].charset.nioCharset.name
val partContent = data.decodeString(charsetName)
Future.successful(acc.addForm(part.name, partContent))
case _ ⇒
discard()
}
}
}
val uploadedF = formData.parts.runWith(uploadingSink)
onSuccess(uploadedF)
}
} Basically, anything that fits in a If it's valuable, I'll add more documentation and a few tests and submit a PR. For example, this form: POST /submit HTTP/1.1
Host: localhost:8080
User-Agent: curl/7.54.0
Accept: */*
Content-Length: 1101
Content-Type: multipart/form-data; boundary=------------------------5e894a22b602a377
--------------------------5e894a22b602a377
Content-Disposition: form-data; name="name"
Content-Type: text/plain; charset=UTF-8
basic_image
--------------------------5e894a22b602a377
Content-Disposition: form-data; name="version"
Content-Type: text/plain; charset=UTF-8
python27
--------------------------5e894a22b602a377
Content-Disposition: form-data; name="file"; filename="requirements.txt"
Content-Type: text/plain; charset=UTF-8
tornado==4.2.1
simplejson==3.11.1
app-utils>=1.0.258
requests
blob-store==1.0.11
pandas==0.20.3
futures
--------------------------5e894a22b602a377 Would be handled by: val destFn: FileNameFn = ???
fileUploadAndForm(Seq("file" -> destFn)) {
case PartsAndFiles(form, files) =>
//files has one item containing the infor about the file requirements.txt
// form is a map that has 2 entries (name and version)
} |
@daddykotex Please do add the documentation, I was not able to do it. |
it may fail here: |
There is still no easy way to get form processing in scala. It is so much easier in https://commons.apache.org/proper/commons-fileupload/ (it has streaming and etc) All I need is: a POST form of multipart/form-data type is submitted. I want
Basically a Seq of posted files and a Seq of form parameters available simultaneously |
It seems the akka-http/akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileUploadDirectives.scala Lines 82 to 87 in cf152fb
You could try to copy the whole directive and make changes to collect those fields as well. Alternatively, if you can control the format, I'd rather avoid using form fields and file uploads at the same time and use URL parameters instead for the remaining fields to avoid the obligatory issues with streaming. |
The documentation is also proposing another alternative to get the files into temp files and have the form fields into a map: https://doc.akka.io/docs/akka-http/current/routing-dsl/index.html#file-uploads |
Not only for the server-side (i.e. for consuming file-uploads) but also for the client-side where servers may return
multipart/byteranges
or other multipart datastructures.Information it should contain:
Unmarshal(response / entity).to[Multipart.General]
Multipart
Multipart.parts
andMultipart.BodyPart.entity.dataBytes
The text was updated successfully, but these errors were encountered: