Rust in Everything
基于 tower-http 0.7.1

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> 造值的」装箱闭包。

📝NOTE
冷知识:为什么 0.7.0 和 0.7.1 各修了一次 Clone

M 既然是 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。

💬 关联讨论

发起讨论
加载中...