发布 axum 0.7.0

2023年11月27日

今天,我们很高兴地宣布 axum 0.7 版本。axum 是一个符合人体工程学且模块化的 Web 框架,它基于 tokiotowerhyper 构建。

这也包括 axum-coreaxum-extraaxum-macros 的新主要版本。

hyper 1.0 支持

axum 0.7 的主要特性是支持 hyper 1.0。hyper 是 Rust 中许多网络生态系统的基础库,最终拥有稳定的 API 是一个重要的里程碑。

hyper 保证在未来三年内不会再进行任何重大更改,这意味着周围的生态系统也将变得更加稳定。

hyper 1.0 带来了 API 的重大调整。之前的低级 API(在 hyper::server::conn 中找到)已稳定,而高级 API(例如 hyper::Server)已被移除。

计划是将高级 API 添加到一个名为 hyper-util 的新 crate 中。在那里,我们可以构建 API,而无需过多担心稳定性保证和向后兼容性。当某些东西准备好稳定时,它可以被移入 hyper

hyper-util 仍处于开发的早期阶段,并且某些东西(例如之前的 Server 类型)仍然缺失。

由于 hyper-util 不稳定,我们不希望它成为 axum 公共 API 的一部分。如果您想使用 hyper-util 中的某些内容,则必须直接依赖它。

如果您将 axumtower-http 一起使用,请注意,由于两者都公开依赖于 http crate(它也发布了 1.0 版本),因此您需要同时升级 tower-http(到 v0.5+)。

一个新的 axum::serve 函数

axum 0.6 提供了 axum::Server,这是一种快速入门的简便方法。然而,axum::Server 只是 hyper::Server 的重新导出,它已从 hyper 1.0 中移除。

hyper-util 中还没有完整的替代品,因此 axum 现在提供了自己的 serve 函数

use axum::{
    routing::get,
    Router,
};
use tokio::net::TcpListener;

let app = Router::new().route("/", get(|| async { "Hello, World!" }));

let listener = TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();

axum::serve 的目的是提供一种快速开始使用 axum 的方法,因此它不支持任何配置选项。如果您需要配置,则必须直接使用 hyperhyper-util。我们提供了一个示例,展示了如何 在此处 进行操作。

我们自己的 Body 类型

http-body crate 现在也达到了 1.0 版本,并且它也像 hyperhyper-util 一样进行了类似的 API 拆分。http-body 现在只提供核心 API,而高级实用程序已移至 http-body-util。这包括 FullEmptyUnsyncBoxBody 等,它们以前由 axum 重新导出。

出于与 hyper-util 不应成为 axum 公共 API 一部分相同的原因,http-body-util 也不应该成为。

作为替代方案,axum 现在提供了自己的 body 类型,位于 axum::body::Body

它的 API 类似于 hyper::Body 的 API

use axum::body::Body;
use futures::TryStreamExt;
use http_body_util::BodyExt;

// Create an empty body (Body::default() does the same)
Body::empty();

// Create bodies from strings or buffers
Body::from("foobar");
Body::from(Vec::<u8>::from([1, 3, 3, 7]));

// Wrap another type that implements `http_body::Body`
Body::new(another_http_body);

// Convert a `Body` into a `Stream` of data frames
let mut stream = body.into_data_stream();
while let Some(chunk) = stream.try_next().await? {
    // ...
}

// Collect the body into a `Bytes`. Uses `BodyExt::collect`
// This replaces the previous `hyper::body::to_bytes` function
let bytes = body.collect().await?.to_bytes();

更少的泛型

axum::Router 过去是泛型化的请求 body 类型。这意味着应用更改请求 body 类型的中间件会对您的路由产生连锁反应

// This would work just fine
Router::new()
    .route(
        "/",
        get(|body: Request<Body>| async { ... })
    );

// But adding `tower_http::limit::RequestBodyLimitLayer` would make
// things no longer compile
Router::new()
    .route(
        "/",
        get(|body: Request<Body>| async { ... })
    )
    .layer(tower_http::limit::RequestBodyLimitLayer::new(1024));

它不起作用的原因是 RequestBodyLimitLayer 更改了请求 body 类型,因此您必须提取 Request<http_body::Limited<Body>> 而不是 Request<Body>。这非常微妙,并且是造成一些困惑的根源。

在 axum 0.7 中,一切都像以前一样继续工作,无论您添加哪个中间件

Router::new()
    .route(
        "/",
        // You always extract `Request<Body>` no matter
        // which middleware you add
        //
        // This works because `Router` internally converts
        // the body into an `axum::body::Body`, which internally
        // holds a trait object
        get(|body: Request<Body>| async { ... })
    )
    .layer(tower_http::limit::RequestBodyLimitLayer::new(1024));

还有一个方便的 http::Request 类型别名,它使用 axum 的 Body 类型

use axum::extract::Request;

Router::new().route("/", get(|body: Request| async { ... }));

请求 body 类型参数已从 axum 的所有类型和 trait 中移除,包括 FromRequestMethodRouterNext 等。

有关更多信息,请参阅更新日志

我鼓励您阅读更新日志,以查看所有更改以及有关如何从 0.6 升级到 0.7 的提示。

此外,如果您在更新时遇到问题,请发起 GitHub 讨论。也欢迎您在 Discord 中提问。

— David Pedersen (@davidpdrsn)