Rust in Everything
基于 tower-http 0.7.1

源码地图

主线位置:不在主线上。本页说明两条主线经过的代码在哪个模块、要打开哪个 feature 才能编译到它,以及模块之间谁用了谁。

模块之间的依赖(只画 crate 内部 use 出来的边,测试代码不算):

flowchart TB
  trace --> classify
  oed["on_early_drop(EarlyDropsAsFailures)"] -.->|"开了 trace 才有"| trace
  auth --> validate_request
  compression --> cu[compression_utils]
  compression --> ce[content_encoding]
  decompression --> cu
  decompression --> ce
  decompression --> body
  fs["services::fs"] --> ce
  fs --> set_status
  fs --> body
  limit --> body
  catch_panic --> body

一个 crate,三万行

仓库是一个 workspace,发布的只有 tower-http 一个 crate,旁边的 examples/ 是 axum、tonic、warp 各一个 key-value-store 示例,外加一个 custom-future-multiple-bodies。tag 里 tower-http/src/ 下所有 .rs 一共 31 619 行,包括注释、文档和模块内的 #[cfg(test)] 测试,只用来估计分量:

模块行数管什么feature本卷
services/5 170ServeDir、ServeFile、Redirectfs、redirect08
trace/2 513TraceLayer 与六个回调trace06
cors/2 112CORScors04
follow_redirect/2 107客户端跟随重定向follow-redirect07
compression/2 107响应压缩compression-*05
set_header/1 876设置请求头、响应头set-header03
csrf/1 647跨站请求伪造防护(0.7.0)csrf04
decompression/1 552响应解压、请求解压decompression-*05
on_early_drop/1 503future 或 body 被提前丢掉时回调(0.6.9)on-early-drop06
classify/1 294什么算失败无,总在06
timeout/1 104超时一族timeout05
auth/1 070鉴权auth03
validate_request.rs952校验请求头validate-request03
content_encoding.rs876Accept-Encoding 解析与编码优先级内部,压缩、解压、fs 都拉它05
builder.rs、service_ext.rs1 058ServiceBuilderExt、ServiceExtutil02
request_id.rs602x-request-idrequest-id03
compression_utils.rs488压缩、解压共用的 body 包装内部05
catch_panic.rs407panic 变 500catch-panic06
limit/405请求 body 大小限制limit04
metrics/339InFlightRequestsmetrics06
body.rs142包一层的 Full、Limited、UnsyncBoxBody见下文02
macros.rs137三个内部宏总在02
其余九个文件2 158normalize_path、sensitive_headers、map_*_body、propagate_header、add_extension、set_status,加上 lib.rs、test_helpers.rs各自同名03

lib.rs 从第 210 行起是一串 #[cfg(feature = "...")] pub mod ...;,feature 名用连字符,模块名用下划线。有两个模块前面没有 cfg:

pub mod classify;
pub mod services;

classify 不受 feature 控制,任何配置下都能用(第 320 行);services 本身总在,但里面的 fs、redirect 各自挂着 feature,都不开时它是个空模块。另外两个内部模块按「谁需要」编译:content_encoding 在任何 compression-*、decompression-* 或 fs 下编译,fs 那一行的注释写着「Used for serving precompressed static files as well」;compression_utils 只在压缩、解压下编译。

feature:默认全关

[features] 里 default = []。只写 tower-http = "=0.7.1",依赖树是这样的:

nofeat v0.1.0
└── tower-http v0.7.1
    ├── bitflags v2.13.2
    ├── bytes v1.12.1
    ├── http v1.5.0
    │   ├── bytes v1.12.1
    │   └── itoa v1.0.18
    ├── percent-encoding v2.3.2
    ├── pin-project-lite v0.2.17
    ├── tower-layer v0.3.3
    └── tower-service v0.3.3

连 http-body 都不在:所有和 body 打交道的模块都挂在 feature 后面。默认能用的只有 classify、LatencyUnit 和 BoxError。bitflags、percent-encoding 两个非可选依赖都是给 classify 用的:gRPC 分类器用位掩码记「哪些 code 算成功」,0.7.0 的 #422 让它把 grpc-message 百分号解码后带进失败信息,percent-encoding 也就从 fs 的可选依赖变成了必选。

[features] 里显式写出的 feature:

类数量内容
模块23add-extension、auth、catch-panic、cors、csrf、follow-redirect、fs、limit、map-request-body、map-response-body、metrics、normalize-path、on-early-drop、propagate-header、redirect、request-id、sensitive-headers、set-header、set-status、timeout、trace、util、validate-request
压缩5compression-br、-deflate、-gzip、-zstd,加上全开的 compression-full
解压5decompression-br、-deflate、-gzip、-zstd,加上 decompression-full
其他2default(空)、full

一共 35 个。full 是 23 个模块 feature 加 compression-full、decompression-full。

隐式 feature

Cargo 会给每个可选依赖生成一个同名 feature,除非某个 feature 用 dep:名字 引用过它。tower-http 的可选依赖里,有的写了 dep:(http-body、tokio、url……),有的没写(fs = [..., "mime_guess", "mime", "httpdate", ...]),没写的就留下了隐式 feature。cargo metadata 看到的是 45 个:

0.7.1 features: 45 implicit: 10
implicit: base64 futures-core futures-util httpdate mime mime_guess tokio-util tower tracing uuid

这 10 个都能在 Cargo.toml 里直接写,比如 features = ["tracing"],它的效果是打开可选依赖 tracing,让各处 #[cfg(feature = "tracing")] 的日志代码编译进来。trace 和 catch-panic 两个 feature 会顺带打开它,其余模块不会。

📝NOTE
没有蠢问题

问: 升到 0.6.9 以后,ServeDir 读文件出错时的那条 error 日志不见了,代码没改。

答: 0.6.9 起 fs 不再顺带打开 tracing(#614)。ServeDir 里打日志的地方都包在 #[cfg(feature = "tracing")] 里,没人打开这个隐式 feature,那几行就不编译了。CHANGELOG 的原话是「fs: no longer auto-enables the tracing crate feature; enable tracing explicitly to restore error logging on ServeDir IO failures」。在自己的 Cargo.toml 里加上 tower-http = { ..., features = ["fs", "tracing"] } 就回来了。同一个版本还去掉了两个隐式 feature:async-compression(#642)和 tokio(#628),手写过它们的 Cargo.toml 升级时会报找不到 feature。

feature 之间的依赖

只有三处是 feature 依赖 feature:

feature依赖的 feature为什么
authvalidate-request(外加 base64)被弃用的 require_authorization 是用 ValidateRequestHeader 实现的
fsset-statusServeDir::not_found_service 用 SetStatus 把 fallback 的状态码改成 404
trace无(classify 总在)Trace 的类型参数里就有一个 MakeClassifier

on-early-drop 不依赖 trace,但它的 EarlyDropsAsFailures 包在 #[cfg(feature = "trace")] 里,两个都开才有。util 拉进 tower(ServiceBuilderExt 要给 tower::ServiceBuilder 加方法);follow-redirect 只拉 tower/util,不需要 tower-http 自己的 util。

公开 API 只认这几个外部 crate

Cargo.toml 末尾有一段给 cargo-check-external-types 用的白名单,CI 用它检查公开 API 里出现的外部类型:

[package.metadata.cargo_check_external_types]
allowed_external_types = [
    "bytes::*",
    "http::*",
    "http_body::*",
    "mime::*",
    "tokio::*",
    "tower::*",
    "tower_layer::*",
    "tower_service::*",
    "tracing::*",
    "tracing_core::*",
]

http_body 在,http_body_util 不在。tower-http 内部到处用 http-body-util 的 Full、Limited、UnsyncBoxBody,但只要它们会出现在公开签名里,就得先包一层,这就是 body 模块存在的原因。

MSRV

rust-version = "1.65",0.7.0 从 1.64 提上来(#684)。那个 PR 给 ServeDir 加了 Backend trait,里面用到泛型关联类型(GAT),GAT 是 1.65 稳定的。edition 还是 2018。

📝NOTE
冷知识:这个仓库清空过一次

仓库的第一个提交是 2018 年 1 月的「Add AddOrigin middleware」,之后两年多里先后有过 tower-http-util、tower-request-modifier、HttpMakeConnection 这些东西,其中 tower-http-util 在 2019 年 5 月发过 0.1.0(「Prepare for v0.1.0 release (#27)」)。2021 年 1 月 26 日的 #32 标题就是「Remove all code. We are starting over」,删掉了全部代码。同一天的 #34 重新搭起 crate,之后四天里加进了 AddExtension、Compression、Decompression;2 月 10 日的 #50 给所有中间件加上 get_ref / get_mut / into_inner,就是今天 define_inner_service_accessors! 宏的来历。重新开始后的 tower-http 0.1.0 发布于 2021-05-28。

lib.rs 文档里讲 feature 的那一段,示例写的还是 tower-http = { version = "0.1", features = ["trace"] }。

小结

  • 一个 crate,src/ 3.16 万行;最大的是 services(5.2k,几乎全是 fs)、trace、cors、follow_redirect、compression。
  • 35 个显式 feature:23 个模块、压缩 5 个、解压 5 个、default(空)和 full;cargo metadata 另有 10 个隐式 feature,tracing 是其中之一,0.6.9 起 fs 不再打开它。
  • classify 不受 feature 控制;默认配置下连 http-body 都不依赖。
  • feature 依赖只有 auth → validate-request、fs → set-status;模块依赖里 trace → classify 最重要,EarlyDropsAsFailures 要 trace 和 on-early-drop 都开。
  • 公开 API 的外部类型白名单里没有 http_body_util;MSRV 1.65,来自 0.7.0 的 GAT。

💬 关联讨论

发起讨论
加载中...