Как распаковать ответ Reqwest/Hyper с использованием потоков?

Мне нужно скачать 60 МБ 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'))
    }
  })
})

3 ответа

С reqwest вы можете получить .zip файл:

reqwest::get("myfile.zip")

поскольку reqwest может использоваться только для извлечения файла, ZipArchiveот zip Ящик можно использовать для его распаковки. Это не возможно для потоковой передачи .zip подать в ZipArchive, поскольку ZipArchive::new(reader: R) требует R реализовать Read (который выполняется Response из reqwest) а также Seek, который не реализован Response,

В качестве обходного пути вы можете использовать временный файл:

copy_to(&mut tmpfile)

Как File реализует оба Seek а также Read, 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 это удобный ящик, который позволяет вам создать временный файл, поэтому вам не нужно придумывать имя.

Вот так я бы прочитал файл hello.txt с контентом hello world из архива hello.zip, расположенного на локальном сервере:

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

asyncрешение с использованием Токио

Это немного запутанно, но вы можете сделать это, используя tokio, futures,tokio_util::compatа такжеasync_compression. Ключ в том, чтобы создать futures::io::AsyncReadпоток с использованием .into_async_read()а затем преобразовать его в 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(())
}

Кредит Бенджамин Кей .

Другие вопросы по тегам