actix-files 0.7.0 变更详解:多根目录、预压缩文件与静态文件服务的 HTTP 语义修复
2026/9/20 23:32:26 网站建设 项目流程
  • 后端
  • Web框架

【免费下载链接】actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

项目地址:https://gitcode.com/gh_mirrors/ac/actix-web
点击查看免费下载

导读

actix-files是 Actix Web 生态中专用于静态文件服务的非阻塞组件(crate 主页),提供Files目录挂载服务与NamedFile单文件响应器。本文以官方变更日志 actix-files/CHANGES.md 为骨架,逐条解析 0.7.0 及近几个版本引入的功能(多根目录、try_compressed预压缩文件服务、read_mode_threshold同步/异步读切换、永久重定向等)与安全修复(空 Range 头防 panic、路径穿越防护、pre-epoch mtime 修复),并结合 files.rs、named.rs、service.rs、path_buf.rs 等源码给出实现层面的佐证。读完本文,你将能准确理解这些变更背后的 HTTP 语义,并能在自己的 Actix Web 项目中正确配置静态文件服务。


一、0.7.0 核心变更总览

0.7.0 是actix-files一次重要的功能与修复并行的版本,主要包含 5 项变更(对应 CHANGES.md):

变更类别内容
移除实验特性删除experimental-io-uringcrate feature 及NamedFile::open_async()
新功能Files::new支持传入多个根目录
新功能新增Files::try_compressed()支持服务预压缩静态文件
Bug 修复修复Range: bytes=0-的处理
Bug 修复修复use_hidden_files()开启时请求路径含.段的 panic
Bug 修复修复NamedFile服务 pre-UNIX epoch(早于 1970)修改时间的 panic
Bug 修复修复NamedFile范围响应中非法的Content-Encoding: identity

下面分节详细展开。


二、移除实验性 io-uring 特性:open_async成为历史

0.7.0 首先移除了自 0.6.0-beta.9 引入的experimental-io-uringcrate feature 及其实现,包括NamedFile::open_async()方法(CHANGES.md)。

该特性曾在 Linux 较新内核上尝试利用异步文件 I/O(见 0.6.0-beta.9 条目,CHANGES.md),但由于其平台限制与 semver-exempt 的实验性质,最终在 0.7.0 被整体移除。当前 Cargo.toml 的依赖中已无tokio-uringNamedFile统一通过同步std::fs::File配合阻塞线程池完成文件读取(详见下文"同步/异步读模式"小节)。

迁移提示:如果你曾在代码中调用NamedFile::open_async(),0.7.0 下应改回NamedFile::open(),该方法是目前唯一入口(named.rs)。


三、Files::new多根目录支持

3.1 参数与顺序语义

Files::new(mount_path, serve_from)的第一个参数是挂载的 URL 前缀,第二个参数是磁盘上的服务根目录。0.7.0 起第二个参数可以是单个路径,也可以是有序路径集合(files.rs):

use actix_web::App; use actix_files::Files; // 单个根目录 App::new().service(Files::new("/static", "./static")); // 多个根目录:按顺序查找,先命中者优先 App::new().service(Files::new("/static", ["./static", "./fallback"]));

多个目录的行为由源码明确定义:

  • 按顺序检查:第一个能服务请求路径的目录生效。测试 test_static_files_multiple_directories 验证了shared.txt优先返回第一个目录的内容,而仅存在于第二个目录的fallback.txt也能被命中;
  • 目录列表不跨根合并:目录清单只从第一个匹配目录生成;
  • index 文件可跨根回退:配置Files::index_file()后,若较早目录中没有 index 文件,会继续在后缀目录中查找(见 test_static_files_multiple_directories_index_file,第一个根无 index 时返回第二个根的index.html);
  • 空根集合永不匹配:请求直接落到default_handler或返回 404(test_static_files_empty_directories)。

3.2 根目录的规范化处理

类型FilesDirs负责承载根目录并执行canonicalize()(files.rs):启动时若路径无法规范化,会记录"Specified path is not a directory"错误日志并保留原路径,防止请求意外回落到当前工作目录(CWD)——这正是 0.6.10 安全通告中 "Avoid serving CWD on invalidFiles::newinputs" 的延续。测试 test_static_files_bad_directory_does_not_serve_cwd_files 验证了挂载不存在的目录时,/Cargo.toml返回 404 而非误服务当前目录文件。

FilesDirs通过From实现支持&strString&PathPathBuf&OsStrOsStringBox<Path>Cow<Path>以及数组/切片/Vec等多种入参(files.rs)。

实践注意:若挂载路径为根/,其后注册的服务将不可达,应优先注册更具体的 handler(files.rs)。


四、Files::try_compressed():服务预压缩静态文件

0.7.0 新增 Files::try_compressed(),用于在磁盘上寻找合适的预压缩文件副本,支持.gz(gzip)、.br(brotli)、.zst(zstd)三种后缀,未找到时回退到未压缩原文件。

4.1 实现原理

查找逻辑位于 service.rs 的find_compressed

  1. 读取请求的Accept-Encoding头;
  2. 按客户端偏好(尊重q=0排除)依次协商brgzipzstdidentity(SUPPORTED_PRECOMPRESSION_ENCODINGS);
  3. 尝试打开原文件名 + 对应扩展名(如index.html.gz)的文件;
  4. 命中后保留基于原文件名推导出的Content-TypeContent-Disposition(避免把Content-Type错误地推断成 gzip 二进制),并设置Content-Encoding: gzip(或br/zstd)头,同时追加Vary: accept-encoding,因为响应表示随协商编码变化(service.rs)。

仓库测试夹具直接验证了该特性:actix-web/tests/fixtures/目录下同时存在 lorem.txt、lorem.txt.gz、lorem.txt.br、lorem.txt.xz、lorem.txt.zst 多份预压缩副本,供 compression.rs 集成测试使用。

4.2 使用示例

use actix_web::App; use actix_files::Files; App::new().service( Files::new("/static", "./static") .try_compressed() // 自动协商 .gz / .br / .zst );

注意:.xz(Flate 之外的扩展名)不在支持列表内(service.rs 注释明确说明 "Flate doesn't have an accepted file extension")。如果客户端不接受任何预压缩编码,会回退到原文件(identity分支直接返回None)。


五、HTTP 语义修复:Range、条件请求与 pre-epoch 时间

5.1Range: bytes=0-修复

0.7.0 修复了Range: bytes=0-的处理。此前该"从 0 到结尾"的范围请求存在语义缺陷;修复后行为由测试 test_named_file_range_header_from_zero_to_end_returns_partial_content 验证:

  • 状态码为206 Partial Content
  • Content-Range: bytes 0-99/100
  • Content-Length: 100,且不带Transfer-Encoding(避免同时使用 chunked 与长度)。

范围解析底层依赖 range.rs 的HttpRange::parse(其自身基于http-rangecrate),支持bytes=10-20bytes=-5(末尾 N 字节)、bytes=0-等多种形式;无效或不可满足的范围返回416 Range Not Satisfiable,空字符串与bytes=同样被安全处理(见 test_named_file_empty_range_headers,返回Content-Range: bytes */100,这也是 0.6.10 安全修复"不因空 Range 头 panic"的回归保障)。

5.2 pre-UNIX epoch 修改时间的 panic 修复

NamedFile生成的 ETag 格式仿照 Apache,包含 inode、文件长度与修改时间(named.rs)。当文件 mtime 早于 UNIX epoch(1970-01-01)时,duration_since(UNIX_EPOCH)会失败;0.7.0 改为编码为负秒数偏移 + 正纳秒(类似 POSIX timespec),不再 panic。

集成测试 pre_epoch_mtime.rs 使用filetime把 mtime 设为-60秒,验证:

  • 响应仍是200 OK
  • ETag头照常生成;
  • Last-Modified头被省略——因为httpdatecrate 只支持 [1970, 9999) 区间的日期格式化(见 named.rs 与last_modified()253_402_300_800秒上限的检查),pre-epoch 时间无法格式化,安全返回None

5.3 范围响应中的Content-Encoding: identity修复

此前NamedFile的范围(206)响应可能带出非法的Content-Encoding: identity头;0.7.0 修复了该问题。结合源码可见 service.rs 中Identity编码映射为None,即不写入Content-Encoding头,从而与Compress中间件协同时避免歧义。仓库中 test_named_file_content_encoding 验证了设置ContentEncoding::Identity时响应仍包含Content-Encoding头且可正常读取正文——但服务端路径上已不再产生非法的 identity 值。

5.4 条件请求与缓存头(背景知识)

NamedFile::into_response完整实现了条件请求语义(named.rs):

  • 默认启用ETagLast-ModifiedFlags位标志默认值0b0000_1111,named.rs),可用use_etag(false)/use_last_modified(false)关闭;
  • 处理If-Match/If-None-Match/If-Modified-Since/If-Unmodified-Since,命中时返回412 Precondition Failed304 Not Modified(304 不携带Content-Length,符合规范,见 0.6.0-beta.9 条目 CHANGES.md);
  • 比较时间时只比较整秒(as_secs()),这是 0.6.0-beta.2 中 "If-Modified-Since/If-Unmodified-Since 不使用亚秒时间戳比较"修复的延续(named.rs)。

六、use_hidden_files().路径段的 panic 修复

0.7.0 修复了启用use_hidden_files()后请求路径包含.段时的 panic。路径解析的核心是 path_buf.rs 的PathBufWrap::parse_path,它充当"安全路径穿越守卫":

  • 拒绝解码出/%2F(0.6.0-beta.13 引入,CHANGES.md),并正确解码%25
  • 逐个 segment 校验:.段、..段、隐藏文件段(未启用use_hidden_files时)、以*开头、以:/</>结尾等均被拒绝或规范处理;
  • Windows 上额外拒绝\:(防盘符穿越,path_buf.rs)。

关于 0.7.0 修复的 panic:启用隐藏文件后.段此前可能引发越界;现在即使允许隐藏文件,.段也会被明确拒绝——测试 test_hidden_files_reject_cur_dir_segment 验证请求/./Cargo.toml返回400 Bad RequestUriSegmentError::BadStart('.')映射 400,error.rs)。

目录穿越的端到端防护由 traversal.rs 验证:无论是裸../..路径、%2e%2e编码路径还是%00空字节注入,全部返回 404。测试 path_traversal 还说明..段会被"折叠"(/../../etc/passwd规约为etc/passwd),因此始终停留在挂载根内。


七、其他值得关注的近版本能力

7.1 读取模式阈值:read_mode_threshold

0.6.7 起FilesNamedFile都提供read_mode_threshold()(files.rs、named.rs):小于阈值的文件使用同步(阻塞)读,大于等于阈值的走异步读。实现见 chunked.rs 的new_chunked_read——ReadMode::Sync直接同步读块,ReadMode::Async通过actix_web::web::block丢到阻塞线程池,每块最大 64 KiB(chunked.rs)。默认阈值0,即全部异步读;调高阈值可减少小文件的调度开销,但阈值过大可能阻塞工作线程。

7.2 重定向行为:307 → 308

0.6.8 起目录重定向默认使用307 Temporary Redirect,并新增with_permanent_redirect()切换为308 Permanent Redirect(files.rs)。重定向条件为:请求的是目录、路径末尾无/、且配置了 index 文件或开启了目录列表(service.rs)。测试 test_redirect_to_slash_directory 覆盖了 307/308 及各种组合。

7.3 Guard 体系:路由级与请求级

  • Files::guard():路由级 guard,用于按请求属性分流多个文件服务(示例见 guarded-listing.rs,同一/assets挂载两套Files,分别按show-listing头决定是否展示目录列表);
  • Files::method_guard()(旧名use_guards,0.6.0 更名):请求级 guard,默认仅放行GET/HEAD,可用guard::Post()等扩展(service.rs),其余方法返回405 Method Not Allowed(见 test_files_not_allowed)。

7.4 其他实用配置

  • show_files_listing():启用目录列表,默认渲染为 HTML(text/html; charset=utf-8),隐藏文件不显示(directory.rs),可用files_listing_renderer()自定义渲染器(files.rs);
  • index_file():目录请求时的默认文档(相对serve_from的路径),找不到且开启列表时回退到目录清单(0.6.0-beta.6 引入,CHANGES.md);
  • path_filter():文件存在性检查之前的路径过滤闭包,可阻止子目录搜索或符号链接跟随(files.rs);
  • mime_override():按 MIME 类型重写Content-Disposition的 inline/attachment 行为(files.rs);
  • prefer_utf8():为text/*等类型附加 UTF-8 charset(0.4.0 引入,0.6.0-beta.14 起默认开启,CHANGES.md);支持类型包括application/javascripttext/htmltext/csstext/plaintext/csvtext/tab-separated-values(encoding.rs);
  • disable_content_disposition():关闭Content-Disposition头(0.1.4 引入)。

7.5 公开 API 与 MIME 细节

  • 0.6.10 将PathBufWrapUriSegmentError公开为 crate 级 API(lib.rs),PathBufWrap也可作为FromRequestextractor 使用(path_buf.rs);
  • Content-Disposition的默认策略:text/*image/*audio/*video/*application/{javascript, json, wasm, xhtml}inline,其余为attachment(named.rs)——XHTML 改 inline 见 0.6.3,音频改 inline 见 0.6.1;
  • 非 ASCII 文件名会额外输出filename*=UTF-8''...参数以兼容旧客户端(0.1.7 引入,named.rs),测试 test_named_file_non_ascii_file_name 验证了中文文件名的头输出;
  • 文件名中的换行/垂直制表/换页/回车等特殊字符会被转义为%0A/%0B/%0C/%0D,防止头注入(0.6.4、0.6.5 修复,named.rs)。

7.6 版本与 MSRV

actix-files当前的 MSRV(最低支持 Rust 版本)为1.88(自 0.6.10 起,见 CHANGES.md),工作区 Cargo 版本为 0.7.0(Cargo.toml),许可证为 MIT OR Apache-2.0。


八、安全实践清单

结合 0.6.10 与 0.7.0 的安全修复,生产环境建议:

  1. 升级版本:0.6.10 修复了"空 Range 头 panic"与"无效Files::new输入误服务 CWD"两个漏洞(CHANGES.md),0.7.0 修复了 pre-epoch 时间与.段的 panic——务必升级到 0.7.0 或更高;
  2. 保持默认路径守卫:不要轻易绕过PathBufWrap的段校验;use_hidden_files()只放宽隐藏文件,不会放宽../.等穿越路径;
  3. 根目录必须真实存在Files::new启动时会 canonicalize 根目录,失败仅记录日志并保留原路径,此时所有请求 404,若目录稍后创建则自动恢复可用;
  4. 不要用根/挂载:会遮蔽后续注册的所有服务(files.rs);
  5. 预压缩文件命名try_compressed()只识别原文件名 + .gz/.br/.zst的后缀模式(如app.js.gz),预生成时请遵守该约定,并注意它不会识别.xz

九、相关源码速查

  • actix-files/CHANGES.md:本文依据的完整变更日志
  • actix-files/README.md:crate 简介与最小示例(Files::new("/static", ".").prefer_utf8(true)
  • actix-files/src/files.rs:Files构建器与全部配置方法
  • actix-files/src/service.rs:请求处理主流程与预压缩协商
  • actix-files/src/named.rs:NamedFile响应器、ETag/Last-Modified/条件请求
  • actix-files/src/path_buf.rs:路径解析与穿越防护
  • actix-files/src/range.rs:Range 头解析
  • actix-files/src/chunked.rs:分块流式读取与同步/异步读切换
  • actix-files/src/directory.rs:目录列表渲染
  • actix-files/src/encoding.rs:UTF-8 MIME 等价类型映射
  • actix-files/src/error.rs:FilesErrorUriSegmentError的 HTTP 状态映射
  • actix-files/examples/guarded-listing.rs:guard 分流双Files服务的完整示例
  • actix-files/tests/pre_epoch_mtime.rs:pre-epoch 修改时间回归测试
  • actix-files/tests/traversal.rs:目录穿越防护回归测试
  • 后端
  • Web框架

【免费下载链接】actix-web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.

项目地址:https://gitcode.com/gh_mirrors/ac/actix-web
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询