Crate axum[−][src]
Expand description
axum is a web application framework that focuses on ergonomics and modularity.
Table of contents
- High level features
- Compatibility
- Handlers
- Routing
- Extractors
- Building responses
- Applying middleware
- Sharing state with handlers
- Routing to any
Service
- Nesting applications
- Required dependencies
- Examples
- Feature flags
High level features
- Route requests to handlers with a macro free API.
- Declaratively parse requests using extractors.
- Simple and predictable error handling model.
- Generate responses with minimal boilerplate.
- Take full advantage of the
tower
andtower-http
ecosystem of middleware, services, and utilities.
In particular the last point is what sets axum
apart from other frameworks.
axum
doesn’t have its own middleware system but instead uses
tower::Service
. This means axum
gets timeouts, tracing, compression,
authorization, and more, for free. It also enables you to share middleware with
applications written using hyper
or tonic
.
Compatibility
axum is designed to work with tokio and hyper. Runtime and transport layer independence is not a goal, at least for the time being.
Example
The “Hello, World!” of axum is:
use axum::prelude::*; #[tokio::main] async fn main() { // build our application with a single route let app = route("/", get(|| async { "Hello, World!" })); // run it with hyper on localhost:3000 axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap(); }
Handlers
In axum a “handler” is an async function that accepts zero or more “extractors” as arguments and returns something that can be converted into a response.
Handlers is where your custom domain logic lives and axum applications are built by routing between handlers.
Some examples of handlers:
use axum::prelude::*; use bytes::Bytes; use http::StatusCode; // Handler that immediately returns an empty `200 OK` response. async fn unit_handler() {} // Handler that immediately returns an empty `200 OK` response with a plain // text body. async fn string_handler() -> String { "Hello, World!".to_string() } // Handler that buffers the request body and returns it. async fn echo(body: Bytes) -> Result<String, StatusCode> { if let Ok(string) = String::from_utf8(body.to_vec()) { Ok(string) } else { Err(StatusCode::BAD_REQUEST) } }
Routing
Routing between handlers looks like this:
use axum::prelude::*; let app = route("/", get(get_slash).post(post_slash)) .route("/foo", get(get_foo)); async fn get_slash() { // `GET /` called } async fn post_slash() { // `POST /` called } async fn get_foo() { // `GET /foo` called }
Routes can also be dynamic like /users/:id
. See extractors
for more details.
Precedence
Note that routes are matched bottom to top so routes that should have higher precedence should be added after routes with lower precedence:
use axum::{prelude::*, body::BoxBody}; use tower::{Service, ServiceExt, BoxError}; use http::{Method, Response, StatusCode}; use std::convert::Infallible; // `/foo` also matches `/:key` so adding the routes in this order means `/foo` // will be inaccessible. let mut app = route("/foo", get(|| async { "/foo called" })) .route("/:key", get(|| async { "/:key called" })); // Even though we use `/foo` as the request URI, `/:key` takes precedence // since its defined last. let (status, body) = call_service(&mut app, Method::GET, "/foo").await; assert_eq!(status, StatusCode::OK); assert_eq!(body, "/:key called"); // We have to add `/foo` after `/:key` since routes are matched bottom to // top. let mut new_app = route("/:key", get(|| async { "/:key called" })) .route("/foo", get(|| async { "/foo called" })); // Now it works let (status, body) = call_service(&mut new_app, Method::GET, "/foo").await; assert_eq!(status, StatusCode::OK); assert_eq!(body, "/foo called"); // And the other route works as well let (status, body) = call_service(&mut new_app, Method::GET, "/bar").await; assert_eq!(status, StatusCode::OK); assert_eq!(body, "/:key called"); // Little helper function to make calling a service easier. Just for // demonstration purposes. async fn call_service<S>( svc: &mut S, method: Method, uri: &str, ) -> (StatusCode, String) where S: Service<Request<Body>, Response = Response<BoxBody>, Error = Infallible> { let req = Request::builder().method(method).uri(uri).body(Body::empty()).unwrap(); let res = svc.ready().await.unwrap().call(req).await.unwrap(); let status = res.status(); let body = res.into_body(); let body = hyper::body::to_bytes(body).await.unwrap(); let body = String::from_utf8(body.to_vec()).unwrap(); (status, body) }
Matching multiple methods
If you want a path to accept multiple HTTP methods you must add them all at once:
use axum::prelude::*; // `GET /` and `POST /` are both accepted let app = route("/", get(handler).post(handler)); // This will _not_ work. Only `POST /` will be accessible. let wont_work = route("/", get(handler)).route("/", post(handler)); async fn handler() {}
Extractors
An extractor is a type that implements FromRequest
. Extractors is how
you pick apart the incoming request to get the parts your handler needs.
For example, extract::Json
is an extractor that consumes the request
body and deserializes it as JSON into some target type:
use axum::prelude::*; use serde::Deserialize; let app = route("/users", post(create_user)); #[derive(Deserialize)] struct CreateUser { email: String, password: String, } async fn create_user(payload: extract::Json<CreateUser>) { let payload: CreateUser = payload.0; // ... }
extract::Path
can be used to extract params from a dynamic URL. It
is compatible with any type that implements serde::Deserialize
, such as
Uuid
:
use axum::prelude::*; use uuid::Uuid; let app = route("/users/:id", post(create_user)); async fn create_user(extract::Path(user_id): extract::Path<Uuid>) { // ... }
You can also apply multiple extractors:
use axum::prelude::*; use uuid::Uuid; use serde::Deserialize; let app = route("/users/:id/things", get(get_user_things)); #[derive(Deserialize)] struct Pagination { page: usize, per_page: usize, } impl Default for Pagination { fn default() -> Self { Self { page: 1, per_page: 30 } } } async fn get_user_things( extract::Path(user_id): extract::Path<Uuid>, pagination: Option<extract::Query<Pagination>>, ) { let pagination: Pagination = pagination.unwrap_or_default().0; // ... }
Additionally Request<Body>
is itself an extractor:
use axum::prelude::*; let app = route("/users/:id", post(handler)); async fn handler(req: Request<Body>) { // ... }
However it cannot be combined with other extractors since it consumes the entire request.
See the extract
module for more details.
Building responses
Anything that implements IntoResponse
can be
returned from a handler:
use axum::{body::Body, response::{Html, Json}, prelude::*}; use http::{StatusCode, Response, Uri}; use serde_json::{Value, json}; // We've already seen returning &'static str async fn plain_text() -> &'static str { "foo" } // String works too and will get a `text/plain` content-type async fn plain_text_string(uri: Uri) -> String { format!("Hi from {}", uri.path()) } // Bytes will get a `application/octet-stream` content-type async fn bytes() -> Vec<u8> { vec![1, 2, 3, 4] } // `()` gives an empty response async fn empty() {} // `StatusCode` gives an empty response with that status code async fn empty_with_status() -> StatusCode { StatusCode::NOT_FOUND } // A tuple of `StatusCode` and something that implements `IntoResponse` can // be used to override the status code async fn with_status() -> (StatusCode, &'static str) { (StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong") } // `Html` gives a content-type of `text/html` async fn html() -> Html<&'static str> { Html("<h1>Hello, World!</h1>") } // `Json` gives a content-type of `application/json` and works with any type // that implements `serde::Serialize` async fn json() -> Json<Value> { Json(json!({ "data": 42 })) } // `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for // returning errors async fn result() -> Result<&'static str, StatusCode> { Ok("all good") } // `Response` gives full control async fn response() -> Response<Body> { Response::builder().body(Body::empty()).unwrap() } let app = route("/plain_text", get(plain_text)) .route("/plain_text_string", get(plain_text_string)) .route("/bytes", get(bytes)) .route("/empty", get(empty)) .route("/empty_with_status", get(empty_with_status)) .route("/with_status", get(with_status)) .route("/html", get(html)) .route("/json", get(json)) .route("/result", get(result)) .route("/response", get(response));
Applying middleware
axum is designed to take full advantage of the tower and tower-http ecosystem of middleware:
To individual handlers
A middleware can be applied to a single handler like so:
use axum::prelude::*; use tower::limit::ConcurrencyLimitLayer; let app = route( "/", get(handler.layer(ConcurrencyLimitLayer::new(100))), ); async fn handler() {}
To groups of routes
Middleware can also be applied to a group of routes like so:
use axum::prelude::*; use tower::limit::ConcurrencyLimitLayer; let app = route("/", get(get_slash)) .route("/foo", post(post_foo)) .layer(ConcurrencyLimitLayer::new(100)); async fn get_slash() {} async fn post_foo() {}
Error handling
Handlers created from async functions must always produce a response, even
when returning a Result<T, E>
the error type must implement
IntoResponse
. In practice this makes error handling very predictable and
easier to reason about.
However when applying middleware, or embedding other tower services, errors
might happen. For example Timeout
will return an error if the timeout
elapses. By default these errors will be propagated all the way up to hyper
where the connection will be closed. If that isn’t desirable you can call
handle_error
to handle errors from
adding a middleware to a handler:
use axum::prelude::*; use tower::{ BoxError, timeout::{TimeoutLayer, error::Elapsed}, }; use std::{borrow::Cow, time::Duration, convert::Infallible}; use http::StatusCode; let app = route( "/", get(handle .layer(TimeoutLayer::new(Duration::from_secs(30))) // `Timeout` uses `BoxError` as the error type .handle_error(|error: BoxError| { // Check if the actual error type is `Elapsed` which // `Timeout` returns if error.is::<Elapsed>() { return Ok::<_, Infallible>(( StatusCode::REQUEST_TIMEOUT, "Request took too long".into(), )); } // If we encounter some error we don't handle return a generic // error return Ok::<_, Infallible>(( StatusCode::INTERNAL_SERVER_ERROR, // `Cow` lets us return either `&str` or `String` Cow::from(format!("Unhandled internal error: {}", error)), )); })), ); async fn handle() {}
The closure passed to handle_error
must
return Result<T, E>
where T
implements
IntoResponse
.
See routing::Layered::handle_error
for more details.
Applying multiple middleware
tower::ServiceBuilder
can be used to combine multiple middleware:
use axum::prelude::*; use tower::ServiceBuilder; use tower_http::compression::CompressionLayer; use std::{borrow::Cow, time::Duration}; let middleware_stack = ServiceBuilder::new() // Return an error after 30 seconds .timeout(Duration::from_secs(30)) // Shed load if we're receiving too many requests .load_shed() // Process at most 100 requests concurrently .concurrency_limit(100) // Compress response bodies .layer(CompressionLayer::new()) .into_inner(); let app = route("/", get(|_: Request<Body>| async { /* ... */ })) .layer(middleware_stack);
Sharing state with handlers
It is common to share some state between handlers for example to share a
pool of database connections or clients to other services. That can be done
using the AddExtension
middleware (applied with AddExtensionLayer
)
and the extract::Extension
extractor:
use axum::{AddExtensionLayer, prelude::*}; use std::sync::Arc; struct State { // ... } let shared_state = Arc::new(State { /* ... */ }); let app = route("/", get(handler)).layer(AddExtensionLayer::new(shared_state)); async fn handler( state: extract::Extension<Arc<State>>, ) { let state: Arc<State> = state.0; // ... }
Routing to any Service
axum also supports routing to general Service
s:
use axum::{service, prelude::*}; use tower_http::services::ServeFile; use http::Response; use std::convert::Infallible; use tower::{service_fn, BoxError}; let app = route( // Any request to `/` goes to a service "/", // Services who's response body is not `axum::body::BoxBody` // can be wrapped in `axum::service::any` (or one of the other routing filters) // to have the response body mapped service::any(service_fn(|_: Request<Body>| async { let res = Response::new(Body::from("Hi from `GET /`")); Ok(res) })) ).route( "/foo", // This service's response body is `axum::body::BoxBody` so // it can be routed to directly. service_fn(|req: Request<Body>| async move { let body = Body::from(format!("Hi from `{} /foo`", req.method())); let body = axum::body::box_body(body); let res = Response::new(body); Ok(res) }) ).route( // GET `/static/Cargo.toml` goes to a service from tower-http "/static/Cargo.toml", service::get(ServeFile::new("Cargo.toml")) );
Routing to arbitrary services in this way has complications for backpressure
(Service::poll_ready
). See the service
module for more details.
Nesting applications
Applications can be nested by calling nest
:
use axum::{prelude::*, routing::BoxRoute, body::{Body, BoxBody}}; use tower_http::services::ServeFile; use http::Response; fn api_routes() -> BoxRoute<Body> { route("/users", get(|_: Request<Body>| async { /* ... */ })).boxed() } let app = route("/", get(|_: Request<Body>| async { /* ... */ })) .nest("/api", api_routes());
Required dependencies
To use axum there are a few dependencies you have pull in as well:
[dependencies] axum = "<latest-version>" hyper = { version = "<latest-version>", features = ["full"] } tokio = { version = "<latest-version>", features = ["full"] } tower = "<latest-version>"
The "full"
feature for hyper and tokio isn’t strictly necessary but its
the easiest way to get started.
Note that axum::Server
is re-exported by axum so if thats all you need
then you don’t have to explicitly depend on hyper.
Tower isn’t strictly necessary either but helpful for testing. See the testing example in the repo to learn more about testing axum apps.
Examples
The axum repo contains a number of examples that show how to put all the pieces together.
Feature flags
axum uses a set of feature flags to reduce the amount of compiled and optional dependencies.
The following optional features are available:
ws
: Enables WebSockets support.headers
: Enables extracting typed headers viaextract::TypedHeader
.multipart
: Enables parsingmultipart/form-data
requests withextract::Multipart
.
Re-exports
Modules
HTTP body utilities.
Types and traits for extracting data from requests.
Async functions that can be used to handle requests.
Re-exports of important traits, types, and functions used with axum. Meant to be glob imported.
Types and traits for generating responses.
Routing between Service
s.
Use Tower Service
s to handle requests.
Server-Sent Events (SSE)
ws
Handle websocket connections.
Structs
Middleware for adding some shareable value to request extensions.
Layer
for adding some shareable value to request extensions.
Functions
Create a route.