What is the best way to convert an AsyncRead to a TryStream of bytes?

廉价感情. 提交于 2020-08-02 04:18:26

问题


I have an AsyncRead and want to convert it to a Stream<Item = tokio::io::Result<Bytes>> with tokio 0.2 and futures 0.3.

The best I've been able to do is something like:

use bytes::Bytes; // 0.4.12
use futures::stream::{Stream, TryStreamExt};; // 0.3.1
use tokio::{fs::File, io::Result}; // 0.2.4
use tokio_util::{BytesCodec, FramedRead}; // 0.2.0

#[tokio::main]
async fn main() -> Result<()> {
    let file = File::open("some_file.txt").await?;
    let stream = FramedRead::new(file, BytesCodec::new()).map_ok(|b| b.freeze());
    fn_that_takes_stream(stream)
}

fn fn_that_takes_stream<S, O>(s: S) -> Result<()>
where
    S: Stream<Item = Result<Bytes>>,
{
    //...
    Ok(())
}

It seems like there should be a simpler way; I'm surprised Tokio doesn't include a codec to get a stream of Bytes instead of BytesMut or that there isn't just an extension trait that provides a method to convert an AsyncRead into a Stream. Am I missing something?


回答1:


Regarding AsyncRead and stream::* as defined in futures-0.3 crate, there is

fn stream::TryStreamExt::into_async_read(self) -> IntoAsyncRead<Self>

but not the other way around. This discrepancy is annoying and hopefully can be addressed before important crates of async/await ecosystem hit 1.0. For now, I've seen several ways to do it oneself:

  • implement Stream trait for a struct that hosts a AsyncRead

  • make use of futures utility functions such as future::poll_fn

  • OP's approach

IMO the third is the most straightforward. Here is some working code:

//# bytes = "0.5.3"
//# futures = "0.3.1"
//# tokio = { version = "0.2.4", features = ["full"] }
//# tokio-util = { version = "0.2.0", features = ["codec"] }
use bytes::Bytes;
use futures::stream::{self, Stream, StreamExt, TryStreamExt};
use tokio::io::{AsyncRead, Result};
use tokio_util::codec;

fn into_byte_stream<R>(r: R) -> impl Stream<Item=Result<u8>>
where
    R: AsyncRead,
{
    codec::FramedRead::new(r, codec::BytesCodec::new())
        .map_ok(|bytes| stream::iter(bytes).map(Ok))
        .try_flatten()
}

fn into_bytes_stream<R>(r: R) -> impl Stream<Item=Result<Bytes>>
where
    R: AsyncRead,
{
    codec::FramedRead::new(r, codec::BytesCodec::new())
        .map_ok(|bytes| bytes.freeze())
}

#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
    let reader = std::io::Cursor::new([114, 117, 115, 116]);
    let res = into_byte_stream(reader)
        .try_collect::<Vec<_>>()
        .await?;
    dbg!(res);

    let reader = std::io::Cursor::new([114, 117, 115, 116]);
    let res = into_bytes_stream(reader)
        .try_collect::<Vec<_>>()
        .await?;
    dbg!(res);

    Ok(())
}

(OP asked for TryStream. But futures-0.3 has impl<S, T, E> TryStream for S where S: Stream<Item = Result<T, E>> + ?Sized, we get it for free.)


I filed a ticket for futures-rs project to ask why. Turns out it is much more complicated than I initially thought. TL;DR is that only after generic associated types (GATs) is shipped, which hopefully will be next year, we can satisfyingly address this problem. Niko's async interview #2 goes into considerable depth to discuss this.



来源:https://stackoverflow.com/questions/59318460/what-is-the-best-way-to-convert-an-asyncread-to-a-trystream-of-bytes

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!