-
Notifications
You must be signed in to change notification settings - Fork 596
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 and provide examples how to do multipart/formdata uploads on the client side #285
Comments
Here's a basic example I hacked together: https://gist.github.com/jrudolph/08d0d28e1eddcd64dbd0 |
Hi @jrudolph. |
@nykolaslima thanks for the comment. We are always looking for suggestions how to simplify things. Can you give an example about how you would expect it to look like (or how other client libraries do this)? The part of creating a file upload request isn't not as short as it could be but the 10+ lines don't seem too bad: def createFileUploadEntityFromFile(file: File): Future[RequestEntity] = {
require(file.exists())
val formData =
Multipart.FormData(
Source.single(
Multipart.FormData.BodyPart(
"test",
HttpEntity(MediaTypes.`application/octet-stream`, file.length(), SynchronousFileSource(file, chunkSize = 100000)), // the chunk size here is currently critical for performance
Map("filename" -> file.getName))))
Marshal(formData).to[RequestEntity]
}
def createRequest(target: Uri, file: File): Future[HttpRequest] =
for {
e ← createFileUploadEntityFromFile(file)
} yield HttpRequest(HttpMethods.POST, uri = target, entity = e) I guess it would be nice if there were
This example could then be written as def createFileUploadEntityFromFile(file: File): Future[RequestEntity] = {
require(file.exists())
Multipart.FormData.fromFile(file, chunkSize = ...).toRequestEntity
}
def createRequest(target: Uri, file: File): Future[HttpRequest] =
for {
e ← createFileUploadEntityFromFile(file)
} yield HttpRequest(HttpMethods.POST, uri = target, entity = e) |
Actually I got a really hard day trying to make file upload to work hahaha I got a solution and I think it's pretty easy and would be a nice documentation example
What do you think? Maybe removing the |
@nykolaslima ah, you are talking about the server side. This issue originally was about the client-side and I implemented the server side just for testing purposes. Still thanks for the suggestions :). There's also #16841 to improve accessing uploaded files on the server side. Regarding the code itself, there are at least these problems:
What you should do instead is passing the source of the part (part.entity.dataBytes) to your backend service and keep it as along as possible and connect it only at the end to something which can consume things in a streaming manner. An obvious choice for file uploads would be a |
@jrudolph thank you for the feedback! I don't understand how to do it. The |
@nykolaslima That is because the photosService.upload() function is not streaming but needs all the data to be drained - which is suboptimal and defeats the whole benefit of streaming. Sometimes you cannot avoid this because of a 3rd party library, but in many cases you can just stream the bytes directly instead of buffering them to the heap. And yes, ++ on the incoming Array chunks will be quadratic. Use the ++ from ByteString instead and get the underlying Array as the last step instead (if you really need the Array). |
To those who may be interested about how to create a DSL directive to do this: usage:
example code: |
I think that a def uploadToDirectory(dir: File, maxFiles: Int = 1): Directive0 and/or def upload(maxFiles: Int = 1): Directive1[Source[(FileInfo, Source[ByteString])]] could be a nice addition to the |
One more java example for the reference: 2m@6562550 |
And for the reference, an another example on how to use // 1- reading a file
val source = new File(
getClass.getResource("/example.pdf").getFile
)
// 2- a method to construct entities
def defaultEntity(content: String) =
HttpEntity.Strict(ContentTypes.`text/plain(UTF-8)`, ByteString(content))
// 3- then the multipartForm
val multipartForm = Multipart.FormData(Source(
Multipart.FormData.BodyPart("someKey", defaultEntity("SomeValue")) ::
Multipart.FormData.BodyPart("AnotherKey", defaultEntity("AnotherValue")) ::
Multipart.FormData.BodyPart.fromFile(
"file", ContentType.Binary(MediaTypes.`application/pdf`), source
):: Nil)) 4- You can then construct the request as @jrudolph mentionned : def createRequest(target: Uri, file: File): Future[HttpRequest] =
for {
e ← createFileUploadEntityFromFile(file)
} yield HttpRequest(HttpMethods.POST, uri = target, entity = e) |
Friday Jun 05, 2015 at 16:17 GMT
Originally opened as akka/akka#17665
Low-level and with marshalling.
/cc @sirthias
The text was updated successfully, but these errors were encountered: