Announcing axum 0.7.0

November 27, 2023

Today, we're happy to announce axum version 0.7. axum is an ergonomic and modular web framework built with tokio, tower, and hyper.

This also includes new major versions of axum-core, axum-extra, and axum-macros.

hyper 1.0 support

The headline feature of axum 0.7 is support for hyper 1.0. hyper is a foundational library for much of the networking ecosystem in Rust and finally having a stable API is a big milestone.

hyper is guaranteeing to not make any more breaking changes for the next three years which means the surrounding ecosystem can also become more stable.

hyper 1.0 comes with a big shuffling of the APIs. The previous low level APIs (found in hyper::server::conn) were stabilized whereas the high level APIs (such as hyper::Server) have been removed.

The plan is to add the high level APIs to a new crate called hyper-util. There we can build out the APIs without worrying too much about stability guarantees and backwards compatibility. When something is ready for stabilization it can be moved into hyper.

hyper-util is still in early stages of development and some things (like the previous Server type) are still missing.

Because hyper-util is not stable we don't want it to be part of axum's public API. If you want to use something from hyper-util you have to depend it directly.

If you are using axum together with tower-http, please note that since both have a public dependency on the http crate which also had its 1.0 release, you need to upgrade tower-http at the same time (to v0.5+).

A new axum::serve function

axum 0.6 provided axum::Server which was an easy way to get started. axum::Server was however just a re-export of hyper::Server which has been removed from hyper 1.0.

There isn't yet a full replacement in hyper-util so axum now provides its own serve function:

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();

The purpose of axum::serve is to provide a way to get started with axum quickly and as such it does not support any configuration options whatsoever. If you need configuration you have to use hyper and hyper-util directly. We provide an example showing how to do that here.

Our own Body type

The http-body crate is now also at 1.0 and that comes with a similar API split that hyper and hyper-util have. http-body now just provides the core APIs, and high level utilities has been moved to http-body-util. That includes things like Full, Empty, and UnsyncBoxBody, which used to be re-exported by axum.

For the same reason that hyper-util shouldn't be part of axum's public API, http-body-util shouldn't either.

As a replacement, axum now provides its own body type found at axum::body::Body.

Its API is similar to what hyper::Body had:

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();

Fewer generics

axum::Router used to be generic over the request body type. That meant applying middleware that changed the request body type would have knock-on effects throughout your routes:

// 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));

The reason it doesn't work is that RequestBodyLimitLayer changes the request body type so you have to extract Request<http_body::Limited<Body>> instead of Request<Body>. This was very subtle and the source of some confusion.

In axum 0.7, everything continues working as before, regardless of which middleware you add:

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));

There is also a convenient type alias of http::Request that uses axum's Body type:

use axum::extract::Request;

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

The request body type parameter has been removed from all of axum's types and traits including FromRequest, MethodRouter, Next, and more.

See the changelog for more

I encourage you to read the changelog to see all the changes and for tips on how to upgrade from 0.6 to 0.7.

Also, please open a GitHub discussion if you have trouble updating. You're also welcome to ask questions in Discord.

ā€” David Pedersen (@davidpdrsn)