set_header 一族
主线位置:作用点主线的前两个时刻。请求侧(
SetRequestHeader)在call时改请求,type Future = S::Future,没有自己的 future;响应侧(SetResponseHeader)在响应 future 完成时改响应头。两侧都不碰 body。
flowchart TB
cfg["Layer:头名 + MakeHeaderValue + 模式"] --> reqside["请求侧 call:mode.apply(&mut req, &mut self.make)"]
cfg --> resside["响应侧 call:make.clone() 放进 future"]
reqside --> inner[内层服务]
resside --> inner
inner --> poll["响应侧 future 完成:mode.apply(&mut res, &mut make)"]
apply{"InsertHeaderMode"} --> o["Override:insert"]
apply --> a["Append:append"]
apply --> n["IfNotPresent:没有才 insert"]
set_header 是 tower-http 里最「薄」的模块之一,四个类型的核心逻辑加起来不到二十行。值得看的是三处:值从哪里来(MakeHeaderValue)、三种模式怎么共用一个函数、以及请求侧和响应侧对同一个闭包的处理为什么不一样。
值从哪里来:MakeHeaderValue
MakeHeaderValue 只有一个方法,看着请求或响应造一个头值,返回 None 表示这次不设:
pub trait MakeHeaderValue<T> {
fn make_header_value(&mut self, message: &T) -> Option<HeaderValue>;
}
impl<F, T> MakeHeaderValue<T> for F
where
F: FnMut(&T) -> Option<HeaderValue>,
// ...
impl<T> MakeHeaderValue<T> for HeaderValue {
fn make_header_value(&mut self, _message: &T) -> Option<HeaderValue> {
Some(self.clone())
}
}
三个实现:闭包、固定的 HeaderValue、Option<HeaderValue>(None 等于整层什么都不做,方便按配置开关)。T 是整个 Request<B> 或 Response<B>,所以闭包能看方法、URI、状态码、别的头,甚至 body 的 size_hint。
方法签名是 &mut self,闭包是 FnMut,可以带状态。这一点在下面会变得重要。
三种模式,一个 apply
overriding、appending、if_not_present 三个构造函数只是给私有枚举 InsertHeaderMode 选一个值。请求和响应两侧共用同一个 apply,靠一个私有的 Headers trait 把 Request 和 Response 抹平:
InsertHeaderMode::Override => {
if let Some(value) = make.make_header_value(target) {
target.headers_mut().insert(header_name.clone(), value);
}
}
InsertHeaderMode::IfNotPresent => {
if !target.headers().contains_key(header_name) {
if let Some(value) = make.make_header_value(target) {
target.headers_mut().insert(header_name.clone(), value);
}
}
}
InsertHeaderMode::Append => {
if let Some(value) = make.make_header_value(target) {
target.headers_mut().append(header_name.clone(), value);
}
}
两个细节:
Override用HeaderMap::insert,会删掉这个名字下的所有旧值,不只是第一个。IfNotPresent先查再造:头已经在了,闭包根本不被调用。带状态的闭包在这种模式下,调用次数少于请求数。
请求侧与响应侧:闭包的状态归谁
SetRequestHeader::call 直接拿 &mut self.make 用:
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
self.mode.apply(&self.header_name, &mut req, &mut self.make);
self.inner.call(req)
}
SetResponseHeader::call 做不到:头要等响应 future 完成才能设,那时 &mut self 早就还回去了,只能把一份 make 克隆进 future:
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
ResponseFuture {
future: self.inner.call(req),
header_name: self.header_name.clone(),
make: self.make.clone(),
mode: self.mode,
}
}
所以响应侧的 Service 实现多一条约束 M: MakeHeaderValue<Response<ResBody>> + Clone,请求侧没有。后果是:带状态的闭包放在请求侧,状态跟着服务实例累积;放在响应侧,每个请求都从「layer 时的那份」重新开始,改动随 future 一起被丢掉。这不是 set_header 独有的,凡是要在第二个时刻动手、又要用可变配置的层都是这样,小型改写层里的 MapRequestBody / MapResponseBody 也是同一对。
下面两侧各放一个计数闭包,同一个服务连调三次,再用 svc.clone().oneshot() 调三次(axum 处理每个请求时都会克隆一份服务,是后一种情形):
tower-http = { version = "=0.7.1", features = ["set-header"] }
tower = { version = "=0.5.3", features = ["util"] }
tokio = { version = "=1.54.1", features = ["macros", "rt"] }
http = "1"
http-body-util = "0.1"
bytes = "1"
use bytes::Bytes;
use http::{header, HeaderName, HeaderValue, Request, Response};
use http_body_util::Full;
use std::convert::Infallible;
use tower::{Service, ServiceBuilder, ServiceExt};
use tower_http::set_header::{
response::SetMultipleResponseHeadersLayer, SetRequestHeaderLayer, SetResponseHeaderLayer,
};
const SEQ: HeaderName = HeaderName::from_static("x-seq");
// 内层:把收到的 x-seq 抄进响应,再自己带一个 cache-control
async fn handler(req: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Infallible> {
let seen = req.headers().get(&SEQ).cloned().unwrap_or(HeaderValue::from_static("-"));
Ok(
Response::builder()
.header("x-seen-seq", seen)
.header(header::CACHE_CONTROL, "no-store")
.body(Full::default())
.unwrap(),
)
}
// 一个带状态的闭包:每调一次加一
fn counter() -> impl FnMut(&Request<Full<Bytes>>) -> Option<HeaderValue> + Clone {
let mut n = 0;
move |_| {
n += 1;
Some(HeaderValue::from(n))
}
}
fn counter_res() -> impl FnMut(&Response<Full<Bytes>>) -> Option<HeaderValue> + Clone {
let mut n = 0;
move |_| {
n += 1;
Some(HeaderValue::from(n))
}
}
#[tokio::main(flavor = "current_thread")]
async fn main() {
println!("stateful closure, same service, 3 calls");
let mut svc = ServiceBuilder::new()
.layer(SetRequestHeaderLayer::overriding(SEQ, counter()))
.layer(SetResponseHeaderLayer::overriding(HeaderName::from_static("x-res-seq"), counter_res()))
.service_fn(handler);
for _ in 0..3 {
let res = svc.ready().await.unwrap().call(Request::new(Full::default())).await.unwrap();
println!(" request side x-seq={:?} response side x-res-seq={:?}", res.headers()["x-seen-seq"], res.headers()["x-res-seq"]);
}
println!("same layers, svc.clone().oneshot() 3 times");
for _ in 0..3 {
let res = svc.clone().oneshot(Request::new(Full::default())).await.unwrap();
println!(" request side x-seq={:?} response side x-res-seq={:?}", res.headers()["x-seen-seq"], res.headers()["x-res-seq"]);
}
println!("three modes on a header the handler already set");
for (name, layer) in [
("overriding", SetResponseHeaderLayer::overriding(header::CACHE_CONTROL, HeaderValue::from_static("max-age=60"))),
("appending", SetResponseHeaderLayer::appending(header::CACHE_CONTROL, HeaderValue::from_static("max-age=60"))),
("if_not_present", SetResponseHeaderLayer::if_not_present(header::CACHE_CONTROL, HeaderValue::from_static("max-age=60"))),
] {
let res = ServiceBuilder::new().layer(layer).service_fn(handler).oneshot(Request::new(Full::default())).await.unwrap();
let all: Vec<_> = res.headers().get_all(header::CACHE_CONTROL).iter().collect();
println!(" {name:<15} {all:?}");
}
println!("closure returning None, and multiple headers");
let layer = SetMultipleResponseHeadersLayer::overriding(vec![
(header::X_FRAME_OPTIONS, HeaderValue::from_static("DENY")).into(),
(header::CACHE_CONTROL, |res: &Response<Full<Bytes>>| {
// 只在 handler 没说 no-store 时改
(res.headers()[header::CACHE_CONTROL] != "no-store").then(|| HeaderValue::from_static("max-age=60"))
})
.into(),
]);
let res = ServiceBuilder::new().layer(layer.clone()).service_fn(handler).oneshot(Request::new(Full::default())).await.unwrap();
println!(" x-frame-options={:?} cache-control={:?}", res.headers()[header::X_FRAME_OPTIONS], res.headers()[header::CACHE_CONTROL]);
println!(" layer debug: {:?}", layer);
}
输出(M2 Max、rustc 1.99.0,跑 3 次相同):
stateful closure, same service, 3 calls
request side x-seq="1" response side x-res-seq="1"
request side x-seq="2" response side x-res-seq="1"
request side x-seq="3" response side x-res-seq="1"
same layers, svc.clone().oneshot() 3 times
request side x-seq="4" response side x-res-seq="1"
request side x-seq="4" response side x-res-seq="1"
request side x-seq="4" response side x-res-seq="1"
three modes on a header the handler already set
overriding ["max-age=60"]
appending ["no-store", "max-age=60"]
if_not_present ["no-store"]
closure returning None, and multiple headers
x-frame-options="DENY" cache-control="no-store"
layer debug: SetMultipleResponseHeadersLayer { headers: [HeaderInsertionConfig { header_name: "x-frame-options", mode: Override, make: "BoxedMakeHeaderValue" }, HeaderInsertionConfig { header_name: "cache-control", mode: Override, make: "BoxedMakeHeaderValue" }] }
- 同一个服务实例:请求侧 1、2、3,状态在
self.make里累积;响应侧永远是 1,每个 future 拿到的都是 layer 时那份闭包的克隆,n从 0 起步。 clone().oneshot():克隆发生在前三次之后,闭包被复制时n已经是 3,于是三个副本各自加到 4,彼此看不见。在 axum 里挂请求侧计数闭包,得到的就是这种「每个请求一份副本」的效果。要跨请求共享状态,闭包里放Arc<AtomicUsize>之类的共享计数,请求标识一页的MakeRequestId就是这么做的。- 三种模式和上面的
apply一一对应;闭包返回None时Override也不会删掉原值。
多头版本:装箱换一张表
0.6.11 加了 SetMultipleResponseHeadersLayer(#672)和请求侧的 SetMultipleRequestHeadersLayer(#677)。一个 Vec 里既有固定值又有闭包,类型各不相同,只能装箱:HeaderMetadata 里放一个 BoxedMakeHeaderValue<T>,也就是 Box<dyn CloneableMakeHeaderValue<T>>,一个自带 clone_box 的 trait 对象。(HeaderName, M) 实现了 Into<HeaderMetadata<T>>,所以上面能写 (name, value).into();装箱要求 M: Clone + Send + Sync + 'static,比单头版本严。
响应侧的 call 照样要把配置克隆进 future,这一次克隆的是整张表:
fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
ResponseFuture {
future: self.inner.call(req),
headers: self.headers.clone(),
}
}
每个请求一次 Vec 分配,加上每个头一次 clone_box 的堆分配。请求侧的 call 用 &mut self.headers 原地遍历,不克隆。模块文档自己也写了取舍:「this layer uses boxing (allocation + dynamic dispatch) to support mixed producer types in a single vec. Stacking multiple [SetRequestHeaderLayer] instances avoids this at the cost of a more complex composed service type.」
还有一处和单头版本不同:类型参数 M 不是值的类型,而是消息的类型。Service 实现写在 SetMultipleResponseHeader<S, Response<ResBody>> 上,Vec<HeaderInsertionConfig<Response<ResBody>>> 里存的是「为 Response<ResBody> 造值的」装箱闭包。
CloneM 既然是 Response<ResBody>,一开始的 #[derive(Clone)] 就会生成 impl<M: Clone> Clone,要求 Response<ResBody>: Clone,而响应 body 几乎都不是 Clone,结果整个 layer 不能克隆,套不进要求 Clone 的地方(axum 的 Router::layer 就要求)。表里存的其实是装箱闭包,克隆它根本不需要 body 能克隆。修法是把派生换成手写的 impl<M> Clone(如第 25 行),分了两次:0.7.0 的 #703 在 CHANGELOG 里写的是「make SetMultipleRequestHeaders and SetMultipleResponseHeaders Clone for non-Clone HTTP bodies」,但对比两个 tag 的源码,0.7.0 只改了请求侧的两个类型和 HeaderMetadata,响应侧还是 #[derive(Clone)];0.7.1 的 #714 才补上:「SetMultipleResponseHeadersLayer and SetMultipleResponseHeader are now Clone regardless of the response body type, matching the fix applied to the request-side types in 0.7.0」。上面实验里 layer.clone() 能编译,靠的是 0.7.1 这一次。
和别的模块的关系
ServiceBuilderExt的override_request_header、append_response_header等六个方法就是这两个单头 layer 的快捷写法(ServiceBuilderExt 与 ServiceExt)。- 客户端栈里常用
SetRequestHeaderLayer::if_not_present(USER_AGENT, ...);它放在FollowRedirect里面还是外面,决定每一跳是否重新设置(一个请求穿过三个时刻末尾,详见拼一个客户端栈)。 - 响应侧在响应 future 完成时设头,套在
CompressionLayer外面能看到压缩加的Content-Encoding,套在里面则被压缩层看到。想用闭包按size_hint写Content-Length的(CHANGELOG 里 #672 的示例),要放在会改 body 的层外面。
小结
MakeHeaderValue有三个实现:FnMut(&T) -> Option<HeaderValue>、固定HeaderValue、Option<HeaderValue>;返回None就不设。- 三种模式共用一个
apply:Override删掉所有旧值,Append追加,IfNotPresent已有就连闭包都不调。 - 请求侧在
call里用&mut self.make,闭包状态跟着服务实例走;响应侧把make克隆进 future,每个请求从 layer 时的副本起步。服务被克隆时状态也被复制。 - 多头版本(0.6.11)装箱成
BoxedMakeHeaderValue,响应侧每个请求克隆整张表;M是消息类型,0.7.0(请求侧,#703,CHANGELOG 说两侧都修了,源码只改了请求侧)、0.7.1(响应侧,#714)才让它们不依赖 body 是否Clone。