ivmarkov / edge-net

async + no_std + no-alloc implementations of various network protocols
Apache License 2.0
87 stars 15 forks source link

Multipart Forms #21

Open DaneSlattery opened 1 month ago

DaneSlattery commented 1 month ago

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 commented 3 weeks ago

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 commented 3 weeks ago

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.