Skip to content
BarrelKnightPublic

About

流式 tar 归档下载服务器,支持随机访问和 HTTP Range 请求。无需临时文件,内存占用低,完美应对大目录打包和断点续传。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RAAS (Random Access Archive Stream)

流式 tar 归档下载服务器,支持随机访问和 HTTP Range 请求。

项目背景

在工作中遇到了一个业务场景:需要提供一个将整个目录一次性打包下载的 HTTP 下载链接。这个场景面临以下挑战:

  1. 文件体积巨大: 目录下的文件非常大,传统的压缩方式需要占用双倍的存储空间(源目录 + 压缩文件)
  2. 必须保持原始目录结构: 不能简单地打平文件结构,需要完整保留目录层级
  3. 内容频繁更新: 源目录内容会不定期更新,如果采用预先压缩的方式,每次更新后都需要重新压缩,耗时耗力
  4. 临时压缩延迟高: 如果在用户请求时临时压缩,用户需要等待很长时间才能开始下载,体验极差
  5. 大文件下载可靠性: 由于文件非常大,下载过程中很容易因网络中断而失败,需要支持断点续传和多线程下载

基于这些实际需求,开发了这个项目。

项目概述

RAAS (Random Access Archive Stream) 是一个高性能的流式归档下载服务器,支持 tar 与 zip 两种归档格式,其核心功能是将整个目录打包成一个可下载的归档文件,解决了传统 HTTP 下载只能一次性下载单个资源的局限性。

与传统方案不同,RAAS 无需预先创建完整的 tar 文件即可实现流式传输。当客户端请求下载某个目录时,服务器会实时扫描目录结构、计算文件偏移量、构建虚拟索引,然后通过 HTTP 响应流式生成并传输 tar 归档数据。整个过程无需在磁盘上创建临时文件,内存占用极低,即使对于 GB 级别的大型目录也能高效处理。

核心特性:随机访问压缩流

RAAS 最具创新性的特性是支持对 tar 压缩流的随机访问。这意味着:

  • 不只是打包下载: 系统不仅能将整个目录打包成 tar 格式供客户端下载,更重要的是实现了对生成的 tar 流的任意位置访问能力
  • HTTP Range 协议完整支持: 基于随机访问能力,系统完整实现了 HTTP Range 协议,使客户端可以:
    • 多线程下载: 同时发起多个 Range 请求,并行下载归档的不同部分,显著提升下载速度
    • 断点续传: 网络中断后可以从断点位置继续下载,无需重新开始,对大文件下载尤为重要
    • 部分下载: 只下载归档文件中需要的特定字节范围,无需获取完整归档,节省带宽和时间

要点

  1. 随机访问压缩流: 实现了对 tar 流的任意位置访问,这是区别于传统 tar 服务器的核心能力
  2. 完全流式处理: 无需预创建完整归档文件,无需临时文件,边计算边传输,内存占用与目录大小无关
  3. 缓存机制:
    • 文件句柄缓存: 使用 RefCell 缓存当前文件句柄,避免重复打开文件的系统调用开销,请求结束后自动释放
    • 归档索引缓存: 缓存已扫描的目录结构和预计算的偏移量信息,减少重复扫描和计算
    • tar 头部缓存: 预计算并缓存每个文件的 tar 头部(包括 GNU LongLink 扩展),避免重复生成
    • 自动失效: 通过文件系统监听实时感知源目录变更,自动失效受影响的缓存条目,保证缓存与源目录一致
  4. tar 格式规范:
    • 支持 GNU LongPath 扩展,处理超过 100 字符的长路径
    • 严格的 512 字节对齐处理
    • 正确的目录项和文件项区分
    • 保留源文件的权限位与修改时间,并以规范要求的两个 512 字节零块结束归档
  5. 路径安全验证: 防止路径穿越攻击,确保访问路径在 DATA_ROOT 范围内
  6. 不阻塞异步运行时: 归档扫描与文件读取都是同步 I/O,统一放在阻塞线程池执行;流式响应体通过有界通道把数据块交回异步流,客户端断开时任务自动退出
  7. 多归档格式: 通过 format 参数在 tar 与 zip 之间切换,两种格式都完整支持随机访问与 HTTP Range

原理

当客户端请求一个目录的归档下载时,系统执行以下流程:

1. 目录扫描与元数据收集

  • 递归扫描目标目录,收集所有文件和子目录的元数据信息
  • 获取每个文件的相对路径、实际大小、类型(文件/目录)

2. 偏移量精确计算

  • 为每个文件创建 tar 头部,确定是否需要 GNU LongLink 扩展
  • 计算每个文件在 tar 流中的精确位置偏移量,包括:
    • tar 头部大小(标准 512 字节或带 LongLink 的扩展头部)
    • 文件内容大小
    • 内容后的 512 字节对齐填充
  • 所有偏移量存储在 file_index HashMap 中,形成虚拟索引

3. 虚拟归档索引构建

  • 构建包含所有文件元数据和偏移量的索引结构
  • 预计算并缓存每个文件的 tar 头部数据(包括 GNU LongLink 头部)
  • 计算归档文件的总大小(total_size)

4. 基于偏移量的随机访问

  • 当收到 Range 请求(如 Range: bytes=1024-2048)时,系统通过 file_index 快速定位到对应偏移量
  • 使用二分查找算法(find_file_by_position)确定指定位置属于哪个文件的哪个部分(头部/内容/填充)
  • 无需按顺序读取,可直接跳转到任意位置开始传输

5. 流式数据传输

  • RangeStreamWriter 实现了 std::io::Read trait,支持按需读取指定范围的数据
  • 根据当前位置判断需要读取的数据类型:
    • 头部区域: 直接从 header_cache 读取预计算的 tar 头部
    • 内容区域: 通过文件句柄缓存打开源文件,seek 到指定位置读取文件内容
    • 填充区域: 生成零字节填充数据以满足 512 字节对齐
  • 使用异步流(async_stream)将数据分块传输给 HTTP 响应体
  • 整个过程按需进行,内存中只保留当前读取的数据块

Zip 格式支持

Zip 归档复用与 tar 相同的「扫描 → 计算偏移 → 随机访问」流程,差异在于格式本身:

  • STORED 存储: 条目数据不压缩,与 tar 一样避免占用双份存储;因此单个文件的内容区间可直接映射到源文件偏移,天然可随机访问
  • CRC-32 可控: Zip 规范要求每个条目记录 CRC-32。默认(未启用 zip-crc32 feature)下 CRC 字段直接写 0,完全不读取文件内容,与 tar 一样只依赖元数据;启用 zip-crc32 后,CRC 在首次生成该条目头部时按需计算并缓存,且仅访问文件内容区间不会触发
  • 区间划分: 整个归档被切分为「本地头 / 文件数据 / 中央目录项 / Zip64 EOCD / Zip64 定位器 / EOCD」等连续区间,给定偏移量通过二分查找定位所属区间并按需生成或读取
  • Zip64: 当大小、偏移或条目数超出 Zip32 上限时,自动切换为 Zip64 记录,支持超过 4GiB 的归档
  • UTF-8 文件名: 设置通用标志位 bit 11,正确处理中文等多字节路径

兼容性/性能权衡: 默认关闭 CRC 时,生成的是「结构合法但校验值为 0」的 zip,标准解压工具在读取内容时会报告 CRC 校验失败(unzip -t、Python zipfile 等);文件结构与中央目录仍可正常解析与列举。若需要严格校验,请启用 zip-crc32 feature(代价:首次生成头部时会额外读取一次文件内容)。

快速开始

编译

cargo build --release

运行

# 设置数据根目录(Windows PowerShell)
$env:DATA_ROOT="C:\your\data\directory"

# 设置监听地址(可选)
$env:BIND_ADDR="0.0.0.0:8080"

# 启动服务器
cargo run --release

使用示例

下载完整归档

curl -O http://127.0.0.1:8080/api/archive/download?path=my_folder

部分下载(Range 请求)

curl -H "Range: bytes=0-1023" http://127.0.0.1:8080/api/archive/download?path=my_folder

以 Zip 格式下载

# 完整下载
curl -O "http://127.0.0.1:8080/api/archive/download?path=my_folder&format=zip"

# Range 请求同样适用于 zip
curl -H "Range: bytes=0-1023" "http://127.0.0.1:8080/api/archive/download?path=my_folder&format=zip"

配置

环境变量

变量名 说明 默认值
DATA_ROOT 数据根目录 .
BIND_ADDR 监听地址 0.0.0.0:8080
MAX_CONCURRENT_REQUESTS 最大并发请求数 100
THREAD_POOL_SIZE 线程池大小 4
STREAM_READ_BUFFER_SIZE 流式读取块大小(字节,低于 65536 会被提升) 131072
ARCHIVE_CACHE_MAX_CAPACITY 存档缓存最大容量 100
ENABLE_FILE_WATCHER 是否启用文件系统监听以自动失效缓存 true
FILE_WATCHER_DEBOUNCE_MS 文件系统事件抖动合并窗口(毫秒) 200

日志

服务使用 tracing 输出结构化日志,每个请求会记录方法、路径、状态码与耗时。默认级别随构建模式变化:release 构建为 info,debug(cargo run/cargo test)构建为 debug。

可通过环境变量 RUST_LOG 覆盖默认级别:

# 强制输出缓存命中/未命中、归档构建与流式传输统计等
export RUST_LOG=debug

# 含缓存失效明细与 zip CRC 计算等更细粒度信息
export RUST_LOG=raas=trace

# release 下只想看错误
export RUST_LOG=error

Cargo Features

Feature 说明 默认
zip-crc32 启用 Zip 归档的 CRC-32 计算。关闭时 CRC 字段写 0、不额外读取文件;开启后按需计算,标准工具可严格校验内容 关闭

启用方式:

cargo build --release --features zip-crc32

API 文档

GET /health

健康检查,返回 {"status":"ok"}。

GET /api/archive/download

流式下载目录为归档文件(默认 tar,可通过 format 选择 zip)。

查询参数:

  • path (必需): 相对于 DATA_ROOT 的路径
  • format (可选): 归档格式,支持 tar(默认)与 zip

响应头:

  • Content-Type: application/x-tar(tar)或 application/zip(zip)
  • Accept-Ranges: bytes
  • Content-Disposition: attachment; filename="<name>.<ext>"

支持 Range 请求:

  • 请求头: Range: bytes=start-end
  • 支持的区间形式:bytes=start-end、bytes=start-(到结尾)、bytes=-N(最后 N 字节);多段区间仅取第一段
  • 结束位置超出归档大小时自动裁剪到末尾;起始位置超出范围时返回 416 Range Not Satisfiable
  • 语法非法的 Range 头按 HTTP 规范忽略,返回完整内容(200)
  • 响应状态: 206 Partial Content
  • 响应头: Content-Range: bytes start-end/total

开发计划

1. 文件系统监听与缓存自动失效机制

目标: 实现文件系统变更自动检测,确保缓存数据与源目录保持一致

实现:

  • 基于 notify 递归监听 DATA_ROOT,在源目录内容发生变更时自动失效受影响的存档缓存
  • 每个事件只失效「自身及所有祖先路径」的缓存条目,例如 root/a/b.txt 变更时失效 root/a/b.txt、root/a、root 的缓存,避免无关缓存被误伤
  • 通过抖动窗口(debounce)合并连续事件,避免批量写入时反复重扫目录
  • 失效操作与正在进行的请求相互隔离:已获取的存档由 Arc 持有,不受缓存移除影响,新请求则会重新扫描
  • 监听器生命周期与 AppState 绑定,Drop 时自动停止监听并回收线程
  • 可通过 ENABLE_FILE_WATCHER=false 关闭监听;监听启动失败时自动降级为不启用,不影响服务主流程

2. 零拷贝文件传输优化 (sendfile)

目标: 集成操作系统级零拷贝机制,大幅提升大文件传输性能

3. Zip 压缩格式支持

目标: 扩展归档格式选择,支持 Zip 格式同时保持随机访问特性

实现:

  • 新增统一的归档抽象 Archive trait,tar 与 zip 各自实现 format / total_size / stream_range,缓存与 HTTP 处理器不感知具体格式
  • 通过查询参数 format=tar|zip 选择格式(默认 tar);缓存以「源路径 + 格式」为键,两种格式互不影响、可同时缓存,并在源目录变更时一并失效
  • Zip 采用 STORED(不压缩) 存储,索引构建阶段只读取目录元数据、不读取文件内容,与 tar 一样保持低内存、低首包延迟
  • CRC-32 由 zip-crc32 feature 控制(默认关闭):关闭时字段写 0、不读取文件;启用后按需在生成条目头部时计算并缓存,仅做 Range 下载或仅访问文件内容区间不会触发额外读取
  • 随机访问:将归档切分为本地头、文件数据、中央目录项、EOCD 等连续区间,基于偏移量二分查找即可定位任意位置,完整支持 HTTP Range 与多线程下载
  • 支持 Zip64 扩展,自动处理超过 4GiB 的归档与超过 65535 个条目的目录
  • 文件名统一使用 UTF-8,正确处理中文等多字节路径

许可证

MIT

About

流式 tar 归档下载服务器,支持随机访问和 HTTP Range 请求。无需临时文件,内存占用低,完美应对大目录打包和断点续传。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages