源码地图
主线位置:不在主线上。本页说明两条主线经过的代码在哪个模块、要打开哪个 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 170 | ServeDir、ServeFile、Redirect | fs、redirect | 08 |
trace/ | 2 513 | TraceLayer 与六个回调 | trace | 06 |
cors/ | 2 112 | CORS | cors | 04 |
follow_redirect/ | 2 107 | 客户端跟随重定向 | follow-redirect | 07 |
compression/ | 2 107 | 响应压缩 | compression-* | 05 |
set_header/ | 1 876 | 设置请求头、响应头 | set-header | 03 |
csrf/ | 1 647 | 跨站请求伪造防护(0.7.0) | csrf | 04 |
decompression/ | 1 552 | 响应解压、请求解压 | decompression-* | 05 |
on_early_drop/ | 1 503 | future 或 body 被提前丢掉时回调(0.6.9) | on-early-drop | 06 |
classify/ | 1 294 | 什么算失败 | 无,总在 | 06 |
timeout/ | 1 104 | 超时一族 | timeout | 05 |
auth/ | 1 070 | 鉴权 | auth | 03 |
validate_request.rs | 952 | 校验请求头 | validate-request | 03 |
content_encoding.rs | 876 | Accept-Encoding 解析与编码优先级 | 内部,压缩、解压、fs 都拉它 | 05 |
builder.rs、service_ext.rs | 1 058 | ServiceBuilderExt、ServiceExt | util | 02 |
request_id.rs | 602 | x-request-id | request-id | 03 |
compression_utils.rs | 488 | 压缩、解压共用的 body 包装 | 内部 | 05 |
catch_panic.rs | 407 | panic 变 500 | catch-panic | 06 |
limit/ | 405 | 请求 body 大小限制 | limit | 04 |
metrics/ | 339 | InFlightRequests | metrics | 06 |
body.rs | 142 | 包一层的 Full、Limited、UnsyncBoxBody | 见下文 | 02 |
macros.rs | 137 | 三个内部宏 | 总在 | 02 |
| 其余九个文件 | 2 158 | normalize_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:
| 类 | 数量 | 内容 |
|---|---|---|
| 模块 | 23 | add-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 |
| 压缩 | 5 | compression-br、-deflate、-gzip、-zstd,加上全开的 compression-full |
| 解压 | 5 | decompression-br、-deflate、-gzip、-zstd,加上 decompression-full |
| 其他 | 2 | default(空)、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 会顺带打开它,其余模块不会。
问: 升到 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 | 为什么 |
|---|---|---|
auth | validate-request(外加 base64) | 被弃用的 require_authorization 是用 ValidateRequestHeader 实现的 |
fs | set-status | ServeDir::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。
仓库的第一个提交是 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。