使用流如何解压Reqwest/Hyper响应?

6

我需要下载一个60MB的ZIP文件,并提取其中唯一的文件。我希望使用流来下载和提取它。如何使用Rust实现这个功能?

fn main () {
    let mut res = reqwest::get("myfile.zip").unwrap();
    // extract the response body to myfile.txt
}

在Node.js中,我会这样做:
http.get('myfile.zip', response => {
  response.pipe(unzip.Parse())
  .on('entry', entry => {
    if (entry.path.endsWith('.txt')) {
      entry.pipe(fs.createWriteStream('myfile.txt'))
    }
  })
})

你看过这个库zip吗?https://github.com/mvdnes/zip-rs - mgul
我已经尝试过了,但是我刚开始学习Rust和Reqwest,一个带有Zip示例的教程会非常有用。 - brielov
您是否考虑接受一个答案或为此问题开启悬赏? - Tim Diekmann
3个回答

5
使用reqwest,您可以获取.zip文件:
reqwest::get("myfile.zip")

由于 reqwest 只能用于检索文件,所以可以使用 ZipArchivezip crate 解压它。无法将 .zip 文件流式传输到 ZipArchive 中,因为 ZipArchive::new(reader: R) 需要 R 实现 Read(由 reqwestResponse 实现)和 Seek,而 Response 没有实现该功能。

作为一种解决方法,您可以使用临时文件:
copy_to(&mut tmpfile)

由于File实现了SeekRead,因此可以在此处使用zip

zip::ZipArchive::new(tmpfile)

这是所描述方法的一个有效示例:
extern crate reqwest;
extern crate tempfile;
extern crate zip;

use std::io::Read;

fn main() {
    let mut tmpfile = tempfile::tempfile().unwrap();
    reqwest::get("myfile.zip").unwrap().copy_to(&mut tmpfile);
    let mut zip = zip::ZipArchive::new(tmpfile).unwrap();
    println!("{:#?}", zip);
}

tempfile 是一个很方便的箱子,可以让你创建临时文件,这样你就不用考虑文件名了。


3

这是我从位于本地服务器上的归档文件hello.zip中读取hello.txt文件内容hello world的方式:

extern crate reqwest;
extern crate zip;

use std::io::Read;

fn main() {
    let mut res = reqwest::get("http://localhost:8000/hello.zip").unwrap();

    let mut buf: Vec<u8> = Vec::new();
    let _ = res.read_to_end(&mut buf);

    let reader = std::io::Cursor::new(buf);
    let mut zip = zip::ZipArchive::new(reader).unwrap();

    let mut file_zip = zip.by_name("hello.txt").unwrap();
    let mut file_buf: Vec<u8> = Vec::new();
    let _ = file_zip.read_to_end(&mut file_buf);

    let content = String::from_utf8(file_buf).unwrap();

    println!("{}", content);
}

这将输出hello world

这个示例是否将整个文件读入缓冲区?请记住,这是一个大文件。 - brielov
你可以使用read和固定大小的缓冲区逐块读取。有关Read更多信息,请参见此页面:https://doc.rust-lang.org/std/io/trait.Read.html - mgul

2

async解决方案使用Tokio

虽然有些复杂,但是您可以使用tokiofuturestokio_util::compatasync_compression来实现这个功能。关键是要使用.into_async_read()创建一个futures::io::AsyncRead流,并将其转换为tokio::io::AsyncRead使用.compat()

为了简单起见,它下载一个txt.gz文件并逐行打印。

use async_compression::tokio::bufread::GzipDecoder;
use futures::stream::TryStreamExt;
use tokio::io::AsyncBufReadExt;
use tokio_util::compat::FuturesAsyncReadCompatExt;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let url = "https://f001.backblazeb2.com/file/korteur/hello-world.txt.gz";
    let response = reqwest::get(url).await?;
    let stream = response
        .bytes_stream()
        .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e))
        .into_async_read()
        .compat();
    let gzip_decoder = GzipDecoder::new(stream);

    // Print decompressed txt content
    let buf_reader = tokio::io::BufReader::new(gzip_decoder);
    let mut lines = buf_reader.lines();
    while let Some(line) = lines.next_line().await? {
        println!("{line}");
    }

    Ok(())
}

感谢Benjamin Kay的贡献。


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