Rust in Everything
基于 tower-http 0.7.1

use of deprecated ...: too basic to be useful in real applications

主线位置:不在主线上,在编译期。两处弃用都发生在 0.6.7,到 0.7.1 还在,代码照常能用,只是每次编译都报警告;CI 里开了 -D warnings 就是编译失败。bearer / basic 的历史和比较方式在 校验与鉴权,TimeoutLayer 一族在 超时一族。

flowchart TB
  w1["ValidateRequestHeaderLayer::bearer / basic"] --> m1["定义在 #![deprecated] 的 require_authorization 模块里"]
  m1 --> f1["ValidateRequestHeaderLayer::custom,或 AsyncRequireAuthorization"]
  w2["TimeoutLayer::new"] --> m2["#[deprecated]:默认 408 不再隐含"]
  m2 --> f2["TimeoutLayer::with_status_code"]

复现

tower-http = { version = "=0.7.1", features = ["timeout", "validate-request", "auth"] }
axum = { version = "=0.8.9", default-features = false }
tokio = { version = "=1.54.1", features = ["macros", "rt", "time"] }
use axum::routing::get;
use axum::Router;
use std::time::Duration;
use tower_http::timeout::TimeoutLayer;
use tower_http::validate_request::ValidateRequestHeaderLayer;

fn main() {
  let _app: Router = Router::new()
    .route("/", get(|| async { "hi" }))
    .layer(ValidateRequestHeaderLayer::bearer("s3cr3t"))
    .layer(TimeoutLayer::new(Duration::from_secs(10)));
}

rustc 1.99.0:

warning: use of deprecated associated function `tower_http::auth::require_authorization::<impl tower_http::validate_request::ValidateRequestHeaderLayer<tower_http::auth::require_authorization::Bearer<ResBody>>>::bearer`: too basic to be useful in real applications
  --> src/main.rs:10:40
   |
10 |     .layer(ValidateRequestHeaderLayer::bearer("s3cr3t"))
   |                                        ^^^^^^
   |
   = note: `#[warn(deprecated)]` on by default

warning: use of deprecated associated function `tower_http::timeout::TimeoutLayer::new`: Use `TimeoutLayer::with_status_code` instead
  --> src/main.rs:11:26
   |
11 |     .layer(TimeoutLayer::new(Duration::from_secs(10)));
   |                          ^^^

出处

bearer / basic:CHANGELOG 记作「auth::require_authorization is too basic for real-world」(#591)。auth/require_authorization.rs 第 1 行 是模块级的 #![deprecated(since = "0.6.7", note = "too basic to be useful in real applications")]。bearer 是给 ValidateRequestHeaderLayer 加的 inherent 方法,定义在这个模块里,所以调用它也算用了弃用项。警告里那串很长的路径 require_authorization::<impl ...>::bearer 就是这个原因:方法挂在 validate_request 的类型上,身份却属于 auth::require_authorization。这也解释了为什么只开 validate-request 时根本找不到 bearer,要再开 auth。

CHANGELOG 没有逐条列出「太简陋」在哪,从代码看至少有这几处:令牌拼成 Bearer {token} 后和请求头按字节比较,scheme 大小写敏感,比较不是常数时间,也只能配一个固定令牌。

TimeoutLayer::new:timeout/service.rs 第 29 行 的 #[deprecated(since = "0.6.7", note = "Use TimeoutLayer::with_status_code instead")]。同一个版本加了 with_status_code(#599),CHANGELOG 只写了替代方法和旧的默认值「Previously was StatusCode::REQUEST_TIMEOUT」,没有给理由。可以补一句的是:RFC 9110 里 408 指服务器等客户端把请求发完等得太久,拿它表示「handler 处理超时」本来就不太贴切,现在由调用方自己选。同一个文件里 Timeout::new、Timeout::layer 也一起弃用了。

替代

tower-http = { version = "=0.7.1", features = ["timeout", "validate-request"] }
axum = { version = "=0.8.9", default-features = false }
tower = { version = "=0.5.3", features = ["util"] }
tokio = { version = "=1.54.1", features = ["macros", "rt", "time"] }
use axum::body::Body;
use axum::http::{header, Request, Response, StatusCode};
use axum::routing::get;
use axum::Router;
use std::time::Duration;
use tower::ServiceExt;
use tower_http::timeout::TimeoutLayer;
use tower_http::validate_request::ValidateRequestHeaderLayer;

#[tokio::main(flavor = "current_thread")]
async fn main() {
  let expected = "Bearer s3cr3t";
  let app: Router = Router::new()
    .route("/", get(|| async { "hi" }))
    .route("/slow", get(|| async { tokio::time::sleep(Duration::from_secs(1)).await; "late" }))
    // 闭包本身就是一个 ValidateRequest:返回 Err(响应) 表示拒绝
    .layer(ValidateRequestHeaderLayer::custom(move |req: &mut Request<Body>| {
      match req.headers().get(header::AUTHORIZATION) {
        Some(v) if v.as_bytes() == expected.as_bytes() => Ok(()),
        _ => {
          let mut res = Response::new(Body::empty());
          *res.status_mut() = StatusCode::UNAUTHORIZED;
          Err(res)
        }
      }
    }))
    .layer(TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, Duration::from_millis(100)));

  for (path, auth) in [("/", None), ("/", Some(expected)), ("/slow", Some(expected))] {
    let mut req = Request::get(path);
    if let Some(a) = auth {
      req = req.header(header::AUTHORIZATION, a);
    }
    let res = app.clone().oneshot(req.body(Body::empty()).unwrap()).await.unwrap();
    println!("GET {path:<6} auth={:<5} -> {}", auth.is_some(), res.status());
  }
}

编译没有警告,跑 3 次输出相同:

GET /      auth=false -> 401 Unauthorized
GET /      auth=true  -> 200 OK
GET /slow  auth=true  -> 408 Request Timeout
  • 这里的闭包和被弃用的 bearer 做的是同一件事,只为说明形状。真要用固定令牌,至少换成常数时间比较;按用户、按数据库查令牌的,用异步的 AsyncRequireAuthorization,或者框架自己的提取器(axum 里通常是一个自定义提取器)。
  • with_status_code 想保持旧行为就传 StatusCode::REQUEST_TIMEOUT,上面就是;网关后面的服务常用 503 或 504。

小结

  • bearer / basic 定义在整体弃用的 require_authorization 模块里,调用就报 too basic to be useful in real applications。
  • TimeoutLayer::new 弃用,换成 with_status_code(状态码, 时长),传 408 等于旧行为。
  • 两条都是 0.6.7 起的警告,0.7.1 没有删掉这些 API。

💬 关联讨论

发起讨论
加载中...