Как распаковать ответ 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(())
}
Кредит Бенджамин Кей .