Skip to content
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

Add support of constructing multipart/form-data requests #126

Open
nsavch opened this issue May 12, 2020 · 3 comments · May be fixed by #175
Open

Add support of constructing multipart/form-data requests #126

nsavch opened this issue May 12, 2020 · 3 comments · May be fixed by #175

Comments

@nsavch
Copy link

nsavch commented May 12, 2020

This request type is quite common for file upload APIs, so it would be nice to have some ergonomic helpers to construct them. Support of streaming uploads is essential.

Examples of multipart APIs:

@yoshuawuyts
Copy link
Member

yoshuawuyts commented May 16, 2020

@nsavch this would be great, and http-types is indeed the right place to add this. Another example of multipart is in warp:

I think wrapping the multipart crate and exposing our own types is probably the right way to go about it. With shorthands such as impl From<Multipart> for Body and Response::body_multipart.


Example

I haven't properly researched this yet, but one way to think about multipart is as a "response containing multiple bodies". I wonder if we could reuse http_types::Body for this. I'd imagine this could be used along these lines:

Request

let mut req = Request::new(Method::Get, "http://example.website");

let mut multi = Multipart::new();
multi.push("hello world");
multi.push(Body::from_file("./cats.jpeg").await?);

req.set_body(multi);

Response

let mut res = Response::new(200); // get this from somewhere

for body.await? in res.body_multipart() { // pretending async iteration works
    println!("file: {}", body.file_name()); // new method; Option<&str>
    println!("content: {}", body.into_string()?);
}

@kyrias
Copy link
Contributor

kyrias commented May 31, 2020

Another crate that could be looked at for inspiration is multer, which is an async multipart parser. It's what I currently use for a project.


Somewhat annoyingly it currently requires either an futures Stream or a tokio AsyncRead, but the tide body just implements the futures AsyncRead, so I had to write a short wrapper that impls Stream for things that impl the futures AsyncRead. I'm definitely not sure that this is completely correct, but at least it works for me.

use std::pin::Pin;

use async_std::stream::Stream;
use async_std::task::{Context, Poll};
use futures_io::AsyncRead;

struct ByteStream<R>(R);

impl<R: Unpin + AsyncRead> Stream for ByteStream<R> {
    type Item = Result<Vec<u8>, futures_io::Error>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
        let mut buf = [0u8; 1024];
        match Pin::new(&mut self.0).poll_read(cx, &mut buf[..]) {
            Poll::Pending => Poll::Pending,
            Poll::Ready(Ok(0)) => Poll::Ready(None),
            Poll::Ready(Ok(n)) => Poll::Ready(Some(Ok(buf[..n].to_vec()))),
            Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
        }
    }
}

@Fishrock123
Copy link
Member

It seems to me that we may be best off with our own implementation here, since it would be ideal to tailor it very closely to the rest of the API.

@yoshuawuyts yoshuawuyts linked a pull request Jun 6, 2020 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants