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

Multipart Forms #21

Open
DaneSlattery opened this issue Jul 29, 2024 · 2 comments
Open

Multipart Forms #21

DaneSlattery opened this issue Jul 29, 2024 · 2 comments

Comments

@DaneSlattery
Copy link

Feature request for multi part forms support. This crate comes the closest out of the many that I have tried, but requires stream support to be implemented for a connection/request. I hacked it together with sans_io, but I cannot get the bytes::Bytes to be cast from the buffer type &[u8]

I have also tried:

  1. multipart but this only supports std::io::Read and is no longer in development.
  2. axum multipart, but this requires futures_util::stream::StreamExt.
  3. actix_multipart but this requires `futures_core::stream::Stream

Are there any plans for an embedded-io-async approach for this?

@ivmarkov
Copy link
Owner

ivmarkov commented Aug 23, 2024

Feature request for multi part forms support.

Sorry for the late reply!
If you would like to work on a no_std multiparts form support, you are welcome :) We can also include it in the edge-net suite once we have an initial implementation.

I personally don't have spare cycles for this, and I also don't need it right now, as most HTTP-related code on Embedded seems to get away with other POST payloads (most often than not application/json) rather than multipart/form-data.

This crate comes the closest out of the many that I have tried, but requires stream support to be implemented for a connection/request.

I think it would rather require Stream support NOT for the "connection" / "request" but for the request body.
The request body in edge-http (both client and server) is just something which happens to implement... embedded_io_async::Read (on the server) and embedded_io_async::Write (on the client, I guess less interesting to you, as you are probably more interested in utility code for parsing multipart stuff rather than building it).

Saying that ^^^ because your task is then to map embedded_io_async::Read to a futures(_lite)::Stream which is relatively easy.

I hacked it together with sans_io, but I cannot get the bytes::Bytes to be cast from the buffer type &[u8]

Couldn't find much info about "sans-io" by a quick googling, but regardless, mapping an embedded_io_async::Read to futures::Stream<Output = std::io::Result<Bytes>> might be as simple as (excuse not having this type-checked):

fn as_stream<R: embedded_io_async::Read>(read: R) -> impl futures::Stream<Output = std::io::Result<Bytes>> {
    futures::stream::poll_fn(move |ctx| {
        let mut buf = [0; 64];
        let read = core::pin::pin!(read.read(&mut buf));

        match read.poll(ctx) {
            Ok(0) => Poll::Ready(None), // Reading 0 bytes from the input stream <=> eof
            Ok(n) => Poll::Ready(Some(alloc::boxed::Box::new(&buf[..n]),
            Err(e) => Poll::Ready(Some(convert_to_io_err(e)),
        }
    })
}

With that said, using Bytes on embedded is suboptimal, as the Bytes contract is not life-timed (owns the data) and as such requires allocations (as my Box thing from above).

I have also tried:

  1. multipart but this only supports std::io::Read and is no longer in development.

Yes, if it only wants blocking reads, this is a no-go. If it also supports AsyncRead, you can map embedded_io_async::Read to AsyncRead and the other way around. Look inside the embedded_io_async crate.

  1. axum multipart, but this requires futures_util::stream::StreamExt.

If you have something which implements futures(_util)::stream::Stream, then it also implements StreamExt, as StreamExt is a decorator-only trait of Stream, just like FutureExt is a decorator-only trait of Future.

  1. actix_multipart but this requires `futures_core::stream::Stream

futures, futures-core, futures-lite are in the end - roughly speaking - all the same thing:
futures is re-exporting futures-core plus offers more
futures-lite is re-exporting futures-core plus offers more

Basically, the futures-core is the place where traits like Stream live, which are not yet stable in regular upstream Rust (unlike Future, which is stable in upstream Rust since several years, and hence the futures(-core)'s crate futures::Future is a type-alias for core::future::Future now).

Are there any plans for an embedded-io-async approach for this?

As per above, somebody needs to drive this. :)

@ivmarkov
Copy link
Owner

ivmarkov commented Aug 23, 2024

UPDATE: Unfortunately the above code might not work as embedded_io_async::Read - unlike AsyncRead does not have poll_read :(
This is the general problem of wrapping traits containing async functions to something which looks like a Future or a Stream (i.e. stuff which has poll on the object itself, i.e. it is itself a Future or a Stream...)

Also see this.

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

No branches or pull requests

2 participants