本文主要讨论axum的中间件,这也是axum与其他rust web框架极大的不同点,其他框架都定义了自己的接口,而axum直接使用towerServvicelayer的概念, 而tower已经存在一大堆通用的中间件了,所以axum也就自带了一大堆开箱即用的中间件。

<!--more-->

往期文章:

  • https://youerning.top/post/axum/quickstart-1
  • https://youerning.top/post/axum/quickstart-2
  • https://youerning.top/post/axum/quickstart-3
  • https://youerning.top/post/axum/quickstart-4

本节的Cargo.toml文件依赖如下

[dependencies]
axum = { version="0.6.20", features=["default", "headers"] }
axum-extra = { version = "0.8" }
tokio = { version = "1.0", features = ["full"] }
reqwest = { version="0.11.22", features=["json", "multipart"]}
serde = { version = "1.0", features = ["derive"] }
tower = { version = "0.4", features = ["util"] }
tower-http = { version = "0.4.0", features = ["full"] }
serde_json = "1.0.107"
askama = "0.12"
sqlx = { version = "0.7", features = [ "runtime-tokio", "postgres"] }
matchit = "0.7.3"
rand = "0.8.5"
http = "1.0.0"
hyper = "0.14.27"

快速入门

// https://youerning.top/post/axum/quickstart-5
use std::time::Duration;
use axum::http::HeaderName;
use axum::{
    body::Body,
    routing::get,
    Router,
    http::Request,
};
use tokio::time::sleep;
use tower_http::trace::TraceLayer;
use tower_http::compression::CompressionLayer;
use tower_http::timeout::TimeoutLayer;
use tower_http::cors::{CorsLayer, Any};
use tower_http::request_id::{MakeRequestUuid, SetRequestIdLayer};
use tower::ServiceBuilder;



#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(index_handler))
        .layer(
            // 官方推荐在ServiceBuilder上一次性载入
            ServiceBuilder::new()
                .layer(TraceLayer::new_for_http())
                .layer(CompressionLayer::new())
            	// 超时时间是200ms
                .layer(TimeoutLayer::new(Duration::new(0, 200000)))
            	// 需要设置一个请求头的键名,一般叫x-request-id
                .layer(SetRequestIdLayer::new(HeaderName::from_static("x-request-id"), MakeRequestUuid))
                // 默认情况下不放行,所以需要根据自己需求设置必要的允许规则。
               .layer(CorsLayer::new().allow_methods(axum::http::Method::GET).allow_origin(Any))
                
        );

    let addr = "0.0.0.0:8080";
    axum::Server::bind(&addr.parse().unwrap())
      .serve(app.into_make_service())
      .await
      .unwrap();
}

async fn index_handler(req: Request<Body>) -> String {
    if rand::random() {
        sleep(Duration::new(0, 300000)).await;
    }

    if let Some(req_id) = req.headers().get("x-request-id") {
        // CompressionLayer只有当长度大于32时才会压缩
        format!("request[{:?}] {} with method {} ; make the body length longer than 32", req_id ,req.uri(), req.method())
    } else {
        format!("request[none] {} with method {} ; make the body length longer than 32", req.uri(), req.method())
    }
    
}

上面使用了五个比较常用的中间件。

  • TraceLayer trace中间件, 用于追踪各种事件,在不发送给远端的情况的话,跟日志差不多
  • CompressionLayer 压缩中间件,默认情况下,只有内容长度大于32的时候才会进行压缩
  • TimeoutLayer 超时中间件 如果服务端响应超时就返回408状态码
  • SetRequestIdLayer 为服务端设置请求ID,如果还需要为客户端生成对应的请求ID,那么需要PropagateRequestIdLayer
  • CorsLayer 设置CORS请求头,做过前后端分离的项目应该不陌生

除此之外,tower_http还有超级多的中间件可以使用,有兴趣的可以查看: https://docs.rs/tower-http/0.4.3/tower_http/index.html#modules

顺序

tower_http的中间件嵌套其实就像洋葱一层包一层

use axum::{routing::get, Router};

async fn handler() {}

let app = Router::new()
    .route("/", get(handler))
    .layer(layer_one)
    .layer(layer_two)
    .layer(layer_three);

上面的代码的请求顺序如下

        requests
           |
           v
+----- layer_three -----+
| +---- layer_two ----+ |
| | +-- layer_one --+ | |
| | |               | | |
| | |    handler    | | |
| | |               | | |
| | +-- layer_one --+ | |
| +---- layer_two ----+ |
+----- layer_three -----+
           |
           v
        responses

小结

这一节主要了解了一下axum或者说tower的一些常见中间件的使用,如果tower的中间件列表没有自己需要的,可以自己写,虽然会写的不会太难,但是对于没有接触过tower的人来说还是有点难以理解,所以作为快速入门,这里就省略了自己写中间件的内容了, 当然了,也是觉得解释towerService概念需要花很多时间,以后有机会在写相关文章吧。

参考链接

  • https://docs.rs/axum/latest/axum/
  • https://docs.rs/axum/0.6.20/axum/middleware/index.html