如何将超文本响应主体写入文件?

7

我正在尝试使用tokio编写一个测试程序,从网站抓取文件并将流式响应写入文件。Hyper网站提供的示例使用while循环和.data()方法来处理响应主体,但我想使用.map()和其他几种方式来操作流。

我认为下一个合理的尝试是使用TryStreamExt中的.into_async_read()方法将流转换为AsyncRead,但似乎不起作用。我必须使用map将hyper::error::Error转换为std::error::Error以获取TryStream,但现在编译器告诉我已经对转换后的流实现了AsyncRead。这是我的main.rs文件和错误:

use std::error::Error;

use futures::stream::{StreamExt, TryStreamExt};
use http::Request;
use hyper::{Body, Client};
use hyper_tls::HttpsConnector;
use tokio::fs::File;
use tokio::io;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, Body>(https);

    let request = Request::get("some file from the internet").body(Body::empty())?;
    let response = client.request(request).await?;

    let mut stream = response
        .body()
        .map(|result| result.map_err(|error| std::io::Error::new(std::io::ErrorKind::Other, "Error!")))
        .into_async_read();
    let mut file = File::create("output file").await?;

    io::copy(&mut stream, &mut file).await?;

    Ok(())
}

error[E0277]: the trait bound `futures_util::stream::try_stream::into_async_read::IntoAsyncRead<futures_util::stream::stream::map::Map<hyper::body::body::Body, [closure@src/main.rs:20:14: 20:103]>>: tokio::io::async_read::AsyncRead` is not satisfied
  --> src/main.rs:24:5
   |
24 |     io::copy(&mut stream, &mut file).await?;
   |     ^^^^^^^^ the trait `tokio::io::async_read::AsyncRead` is not implemented for `futures_util::stream::try_stream::into_async_read::IntoAsyncRead<futures_util::stream::stream::map::Map<hyper::body::body::Body, [closure@src/main.rs:20:14: 20:103]>>`
   | 
  ::: /Users/jackson/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.13/src/io/util/copy.rs:63:12
   |
63 |         R: AsyncRead + Unpin + ?Sized,
   |            --------- required by this bound in `tokio::io::util::copy::copy`

error[E0277]: the trait bound `futures_util::stream::try_stream::into_async_read::IntoAsyncRead<futures_util::stream::stream::map::Map<hyper::body::body::Body, [closure@src/main.rs:20:14: 20:103]>>: tokio::io::async_read::AsyncRead` is not satisfied
  --> src/main.rs:24:5
   |
24 |     io::copy(&mut stream, &mut file).await?;
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `tokio::io::async_read::AsyncRead` is not implemented for `futures_util::stream::try_stream::into_async_read::IntoAsyncRead<futures_util::stream::stream::map::Map<hyper::body::body::Body, [closure@src/main.rs:20:14: 20:103]>>`
   |
   = note: required because of the requirements on the impl of `core::future::future::Future` for `tokio::io::util::copy::Copy<'_, futures_util::stream::try_stream::into_async_read::IntoAsyncRead<futures_util::stream::stream::map::Map<hyper::body::body::Body, [closure@src/main.rs:20:14: 20:103]>>, tokio::fs::file::File>`
1个回答

4
你已经接近成功了。你调用了 into_async_read,这为你提供了一个实现了 futures::io::AsyncRead 的接口,但你需要的是tokio::io::AsyncRead

使用 tokio-util crate 可以帮助你进行转换。

Cargo.toml中添加:

tokio-util = { version = "0.3.1", features=["compat"] }

假设你添加了以下的转换函数:

fn to_tokio_async_read(r: impl futures::io::AsyncRead) -> impl tokio::io::AsyncRead {
    tokio_util::compat::FuturesAsyncReadCompatExt::compat(r)
}

那么你的代码可能会变成:

let mut futures_io_async_read = response
        .body()
        .map(|result| result.map_err(|error| std::io::Error::new(std::io::ErrorKind::Other, "Error!")))
        .into_async_read();

let tokio_async_read = to_tokio_async_read(futures_io_async_read)

let mut file = File::create("output file").await?;

io::copy(&mut tokio_async_read, &mut file).await?;

添加以下代码: tokio-util = { version = "0.3.1", features=["compat"] } 这样就可以了,谢谢。 - thouger
1
这个在当前版本的hyper中还能用吗?对我来说,我无法调用into_async_read(找不到)。 (我已经使用了TryStreamExtuse。) - robinst
1
我和@robinst遇到了同样的问题。你需要添加futures crate并导入它(use futures::{StreamExt, TryStreamExt};)。 - Felix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接