#zip-archive #tokio #zip #non-blocking #async #file-format

无std archflow

创建可流式传输的ZIP存档

8个版本

0.1.4 2024年1月18日
0.1.3 2024年1月18日
0.1.1 2023年4月21日
0.0.2 2023年3月20日

#82 in 压缩

MIT 许可证

165KB
3K SLoC

Archflow

一个用于一次性创建ZIP存档的库。当输出(如stdout或数据流)无法进行seek操作时,这非常有用。

ZIP是一种支持无损数据压缩的存档文件格式。ZIP文件可以包含一个或多个可能已压缩的文件或目录。ZIP文件格式允许使用多种压缩算法,尽管DEFLATE是最常用的。该格式最初于1989年创建,并在PKWARE,Inc.的PKZIP实用程序中首次实现。

当前实现基于

PKWARE的APPNOTE.TXT v6.3.10

功能

  • 一次性流式传输存档(即无seek)。
  • tokio AsyncRead / AsyncWriteAsyncSeek兼容。
  • std::io Read / WriteSeek兼容
  • 文件类型检测(文本或二进制)
  • Zip64格式(存档大小超过4Gb)
  • Unix时间

支持以下压缩格式

  • 存储(即无压缩)
  • deflate
  • bzip2
  • zstd
  • xz

Cargo功能

在项目中包含时,您可以选择选择tokio(异步)或标准版本。

功能 描述
tokio 使用tokio非阻塞API,即: tokio::io::AsyncReadtokio::io::AsyncWritetokio::io::AsyncSeek
std 使用标准API,即:std::io::Readstd::io::Writestd::io::Seek

示例

文件系统

使用tokio::fs::File创建存档文件的简单示例

 use archflow::{
 compress::FileOptions, compress::tokio::archive::ZipArchive, compression::CompressionMethod,
 error::ArchiveError,
 };

 use tokio::fs::File;

 #[tokio::main]
 async fn main() -> Result<(), ArchiveError> {
 let file = File::create("archive.zip").await?;

 let options = FileOptions::default().compression_method(CompressionMethod::Deflate());

 let mut archive = ZipArchive::new_streamable(file);
 archive.append("file1.txt", &options, &mut b"hello\n".as_ref()).await?;

 let options = options.compression_method(CompressionMethod::Store());
 archive.append("file2.txt", &options, &mut b"world\n".as_ref()).await?;

 archive.finalize().await?;

 Ok(())
 }

Actix

将zip存档作为actix响应流式传输

 use std::path::Path;

 use actix_web::http::header::ContentDisposition;
 use actix_web::{get, App, HttpResponse, HttpServer};
 use archflow::compress::tokio::archive::ZipArchive;
 use archflow::compress::FileOptions;
 use archflow::compression::CompressionMethod;
 use archflow::types::FileDateTime;
 use tokio::fs::File;
 use tokio::io::duplex;

 use tokio_util::io::ReaderStream;

 #[get("/zip")]
 async fn zip_archive() -> HttpResponse {
     let (w, r) = duplex(4096);
     let options = FileOptions::default()
         .last_modified_time(FileDateTime::Now)
         .compression_method(CompressionMethod::Deflate());

     tokio::spawn(async move {
         let mut archive = ZipArchive::new_streamable(w);

         let file_path = Path::new("./tests/resources/lorem_ipsum.txt");

         let mut file = File::open(file_path).await.unwrap();
         archive
             .append("ipsum_deflate.txt", &options, &mut file)
             .await
             .unwrap();

         archive
             .append("file1.txt", &options, &mut b"world\n".as_ref())
             .await
             .unwrap();

         archive
             .append("file2.txt", &options, &mut b"world\n".as_ref())
             .await
             .unwrap();

         let mut f = File::open(file_path).await.unwrap();
         let options = options.compression_method(CompressionMethod::BZip2());
         archive
             .append("ipsum_bz.txt", &options, &mut f)
             .await
             .unwrap();

         let mut f = File::open(file_path).await.unwrap();
         let options = options.compression_method(CompressionMethod::Xz());
         archive
             .append("ipsum_xz.txt", &options, &mut f)
             .await
             .unwrap();

         archive.finalize().await.unwrap();
     });

     HttpResponse::Ok()
         .insert_header(("Content-Type", "application/zip"))
         .insert_header(ContentDisposition::attachment("myzip.zip"))
         .streaming(ReaderStream::new(r))
 }

 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
     let address = "127.0.0.1";
     let port = 8081;

     println!("Test url is http://{}:{}/zip", address, port);

     HttpServer::new(|| App::new().service(zip_archive))
         .bind((address, port))?
         .run()
         .await
 }

Hyper

将zip存档作为hyper响应流式传输


 use archflow::{
 compress::tokio::archive::ZipArchive, compress::FileOptions, compression::CompressionMethod,
 types::FileDateTime,
 };
 use hyper::service::{make_service_fn, service_fn};
 use hyper::{header, Body, Request, Response, Server, StatusCode};
 use tokio::io::duplex;
 use tokio_util::io::ReaderStream;

 async fn zip_archive(_req: Request<Body>) -> Result<Response<Body>, hyper::http::Error> {
     let (w, r) = duplex(4096);
     let options = FileOptions::default()
         .compression_method(CompressionMethod::Deflate())
         .last_modified_time(FileDateTime::Now);
     tokio::spawn(async move {
         let mut archive = ZipArchive::new_streamable(w);
         archive
             .append("file1.txt", &options, &mut b"world\n".as_ref())
             .await
             .unwrap();
         archive
             .append("file2.txt", &options, &mut b"world\n".as_ref())
             .await
             .unwrap();
         archive.finalize().await.unwrap();
     });

     Response::builder()
         .status(StatusCode::OK)
         .header(header::CONTENT_TYPE, "application/zip")
         .body(Body::wrap_stream(ReaderStream::new(r)))
 }

 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
     let address = ([127, 0, 0, 1], 8081).into();
     let service =
         make_service_fn(|_| async { Ok::<_, hyper::http::Error>(service_fn(zip_archive)) });
     let server = Server::bind(&address).serve(service);

     println!("Listening on http://{}", address);
     server.await?;

     Ok(())
 }

免责声明

该库正在建设中,且不稳定。需要做更多测试。

此实现受到以下启发:

Hyper

依赖项

~3.5–7MB
~142K SLoC