Movatterモバイル変換


[0]ホーム

URL:


Docs.rs

Crateaxum[][src]

Expand description

axum is a web application framework that focuses on ergonomics and modularity.

Table of contents

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 thetower andtower-http ecosystem ofmiddleware, services, and utilities.

In particular the last point is what setsaxum apart from other frameworks.axum doesn’t have its own middleware system but instead usestower::Service. This meansaxum gets timeouts, tracing, compression,authorization, and more, for free. It also enables you to share middleware withapplications written usinghyper ortonic.

Compatibility

axum is designed to work withtokio andhyper. Runtime andtransport layer independence is not a goal, at least for the time being.

Example

The “Hello, World!” of axum is:

useaxum::{handler::get,Router,};#[tokio::main]asyncfnmain() {// build our application with a single routeletapp=Router::new().route("/",get(||async {"Hello, World!" }));// run it with hyper on localhost:3000axum::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 thatcan be convertedinto a response.

Handlers is where your custom domain logic lives and axum applications arebuilt by routing between handlers.

Some examples of handlers:

usebytes::Bytes;usehttp::StatusCode;// Handler that immediately returns an empty `200 OK` response.asyncfnunit_handler() {}// Handler that immediately returns an empty `200 OK` response with a plain// text body.asyncfnstring_handler()->String {"Hello, World!".to_string()}// Handler that buffers the request body and returns it.asyncfnecho(body:Bytes)->Result<String,StatusCode> {ifletOk(string)=String::from_utf8(body.to_vec()) {Ok(string)    }else {Err(StatusCode::BAD_REQUEST)    }}

Routing

Routing between handlers looks like this:

useaxum::{handler::get,Router,};letapp=Router::new()    .route("/",get(get_slash).post(post_slash))    .route("/foo",get(get_foo));asyncfnget_slash() {// `GET /` called}asyncfnpost_slash() {// `POST /` called}asyncfnget_foo() {// `GET /foo` called}

Routes can also be dynamic like/users/:id. Seeextractorsfor more details.

You can also define routes separately and merge them withRouter::or.

Precedence

Note that routes are matchedbottom to top so routes that should havehigher precedence should be addedafter routes with lower precedence:

useaxum::{body::{Body,BoxBody},handler::get,http::Request,Router,};usetower::{Service,ServiceExt};usehttp::{Method,Response,StatusCode};usestd::convert::Infallible;// `/foo` also matches `/:key` so adding the routes in this order means `/foo`// will be inaccessible.letmutapp=Router::new()    .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(&mutapp,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.letmutnew_app=Router::new()    .route("/:key",get(||async {"/:key called" }))    .route("/foo",get(||async {"/foo called" }));// Now it workslet (status,body)=call_service(&mutnew_app,Method::GET,"/foo").await;assert_eq!(status,StatusCode::OK);assert_eq!(body,"/foo called");// And the other route works as welllet (status,body)=call_service(&mutnew_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.asyncfncall_service<S>(svc:&mutS,method:Method,uri:&str,)-> (StatusCode,String)whereS:Service<Request<Body>,Response=Response<BoxBody>,Error=Infallible>{letreq=Request::builder().method(method).uri(uri).body(Body::empty()).unwrap();letres=svc.ready().await.unwrap().call(req).await.unwrap();letstatus=res.status();letbody=res.into_body();letbody=hyper::body::to_bytes(body).await.unwrap();letbody=String::from_utf8(body.to_vec()).unwrap();    (status,body)}

Routing to anyService

axum also supports routing to generalServices:

useaxum::{body::Body,http::Request,Router,service};usetower_http::services::ServeFile;usehttp::Response;usestd::convert::Infallible;usetower::service_fn;letapp=Router::new()    .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 mappedservice::any(service_fn(|_:Request<Body>|async {letres=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>|asyncmove {letbody=Body::from(format!("Hi from `{} /foo`",req.method()));letbody=axum::body::box_body(body);letres=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 theservice module for more details.

Routing to fallible services

Note that routing to general services has a small gotcha when it comes toerrors. axum currently does not support mixing routes to fallible serviceswith infallible handlers. For example this doesnot compile:

useaxum::{Router,service,handler::get,http::{Request,Response},body::Body,};usestd::io;usetower::service_fn;letapp=Router::new()// this route cannot fail    .route("/foo",get(||async {}))// this route can fail with io::Error    .route("/",service::get(service_fn(|_req:Request<Body>|async {letcontents=tokio::fs::read_to_string("some_file").await?;Ok::<_,io::Error>(Response::new(Body::from(contents)))        })),    );

The solution is to usehandle_error and handle the error from theservice:

useaxum::{Router,service,handler::get,http::{Request,Response},response::IntoResponse,body::Body,};usestd::{io,convert::Infallible};usetower::service_fn;letapp=Router::new()// this route cannot fail    .route("/foo",get(||async {}))// this route can fail with io::Error    .route("/",service::get(service_fn(|_req:Request<Body>|async {letcontents=tokio::fs::read_to_string("some_file").await?;Ok::<_,io::Error>(Response::new(Body::from(contents)))        }))        .handle_error(handle_io_error),    );fnhandle_io_error(error:io::Error)->Result<implIntoResponse,Infallible> {// ...}

In this particular case you can also handle the error directly inservice_fn but that is not possible, if you’re routing to a service whichyou don’t control.

See“Error handling” for more details onhandle_errorand error handling in general.

Nesting routes

Routes can be nested by callingRouter::nest:

useaxum::{body::{Body,BoxBody},http::Request,handler::get,Router,routing::BoxRoute};usetower_http::services::ServeFile;usehttp::Response;fnapi_routes()->Router<BoxRoute> {Router::new()        .route("/users",get(|_:Request<Body>|async {/* ... */ }))        .boxed()}letapp=Router::new()    .route("/",get(|_:Request<Body>|async {/* ... */ }))    .nest("/api",api_routes());

Note that nested routes will not see the orignal request URI but insteadhave the matched prefix stripped. This is necessary for services like staticfile serving to work. UseOriginalUri if you need the original requestURI.

Extractors

An extractor is a type that implementsFromRequest. Extractors is howyou pick apart the incoming request to get the parts your handler needs.

For example,extract::Json is an extractor that consumes the requestbody and deserializes it as JSON into some target type:

useaxum::{extract,handler::post,Router,};useserde::Deserialize;letapp=Router::new().route("/users",post(create_user));#[derive(Deserialize)]structCreateUser {email:String,password:String,}asyncfncreate_user(payload:extract::Json<CreateUser>) {letpayload:CreateUser=payload.0;// ...}

extract::Path can be used to extract params from a dynamic URL. Itis compatible with any type that implementsserde::Deserialize, such asUuid:

useaxum::{extract,handler::post,Router,};useuuid::Uuid;letapp=Router::new().route("/users/:id",post(create_user));asyncfncreate_user(extract::Path(user_id):extract::Path<Uuid>) {// ...}

You can also apply multiple extractors:

useaxum::{extract,handler::get,Router,};useuuid::Uuid;useserde::Deserialize;letapp=Router::new().route("/users/:id/things",get(get_user_things));#[derive(Deserialize)]structPagination {page:usize,per_page:usize,}implDefaultforPagination {fndefault()->Self {Self {page:1,per_page:30 }    }}asyncfnget_user_things(extract::Path(user_id):extract::Path<Uuid>,pagination:Option<extract::Query<Pagination>>,) {letpagination:Pagination=pagination.unwrap_or_default().0;// ...}

AdditionallyRequest<Body> is itself an extractor:

useaxum::{body::Body,handler::post,http::Request,Router,};letapp=Router::new().route("/users/:id",post(handler));asyncfnhandler(req:Request<Body>) {// ...}

However it cannot be combined with other extractors since it consumes theentire request.

See theextract module for more details.

Building responses

Anything that implementsIntoResponse can bereturned from a handler:

useaxum::{body::Body,handler::{get,Handler},http::{Request,header::{HeaderMap,HeaderName,HeaderValue}},response::{IntoResponse,Html,Json,Headers},Router,};usehttp::{StatusCode,Response,Uri};useserde_json::{Value,json};// We've already seen returning &'static strasyncfnplain_text()->&'staticstr {"foo"}// String works too and will get a `text/plain` content-typeasyncfnplain_text_string(uri:Uri)->String {format!("Hi from {}",uri.path())}// Bytes will get a `application/octet-stream` content-typeasyncfnbytes()->Vec<u8> {vec![1,2,3,4]}// `()` gives an empty responseasyncfnempty() {}// `StatusCode` gives an empty response with that status codeasyncfnempty_with_status()->StatusCode {StatusCode::NOT_FOUND}// A tuple of `StatusCode` and something that implements `IntoResponse` can// be used to override the status codeasyncfnwith_status()-> (StatusCode,&'staticstr) {    (StatusCode::INTERNAL_SERVER_ERROR,"Something went wrong")}// A tuple of `HeaderMap` and something that implements `IntoResponse` can// be used to override the headersasyncfnwith_headers()-> (HeaderMap,&'staticstr) {letmutheaders=HeaderMap::new();headers.insert(HeaderName::from_static("x-foo"),HeaderValue::from_static("foo"),    );    (headers,"foo")}// You can also override both status and headers at the same timeasyncfnwith_headers_and_status()-> (StatusCode,HeaderMap,&'staticstr) {letmutheaders=HeaderMap::new();headers.insert(HeaderName::from_static("x-foo"),HeaderValue::from_static("foo"),    );    (StatusCode::INTERNAL_SERVER_ERROR,headers,"foo")}// `Headers` makes building the header map easier and `impl Trait` is easier// so you don't have to write the whole typeasyncfnwith_easy_headers()->implIntoResponse {Headers(vec![("x-foo","foo")])}// `Html` gives a content-type of `text/html`asyncfnhtml()->Html<&'staticstr> {Html("<h1>Hello, World!</h1>")}// `Json` gives a content-type of `application/json` and works with any type// that implements `serde::Serialize`asyncfnjson()->Json<Value> {Json(json!({"data":42 }))}// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for// returning errorsasyncfnresult()->Result<&'staticstr,StatusCode> {Ok("all good")}// `Response` gives full controlasyncfnresponse()->Response<Body> {Response::builder().body(Body::empty()).unwrap()}letapp=Router::new()    .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("/with_headers",get(with_headers))    .route("/with_headers_and_status",get(with_headers_and_status))    .route("/with_easy_headers",get(with_easy_headers))    .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-httpecosystem of middleware.

If you’re new to tower we recommend you read itsguides fora general introduction to tower and its concepts.

To individual handlers

A middleware can be applied to a single handler like so:

useaxum::{handler::{get,Handler},Router,};usetower::limit::ConcurrencyLimitLayer;letapp=Router::new()    .route("/",get(handler.layer(ConcurrencyLimitLayer::new(100))),    );asyncfnhandler() {}

To groups of routes

Middleware can also be applied to a group of routes like so:

useaxum::{handler::{get,post},Router,};usetower::limit::ConcurrencyLimitLayer;letapp=Router::new()    .route("/",get(get_slash))    .route("/foo",post(post_foo))    .layer(ConcurrencyLimitLayer::new(100));asyncfnget_slash() {}asyncfnpost_foo() {}

Error handling

Handlers created from async functions must always produce a response, evenwhen returning aResult<T, E> the error type must implementIntoResponse. In practice this makes error handling very predictable andeasier to reason about.

However when applying middleware, or embedding other tower services, errorsmight happen. For exampleTimeout will return an error if the timeoutelapses. By default these errors will be propagated all the way up to hyperwhere the connection will be closed. If that isn’t desirable you can callhandle_error to handle errors fromadding a middleware to a handler:

useaxum::{handler::{get,Handler},Router,};usetower::{BoxError,timeout::{TimeoutLayer,error::Elapsed},};usestd::{borrow::Cow,time::Duration,convert::Infallible};usehttp::StatusCode;letapp=Router::new()    .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` returnsiferror.is::<Elapsed>() {returnOk::<_,Infallible>((StatusCode::REQUEST_TIMEOUT,"Request took too long".into(),                    ));                }// If we encounter some error we don't handle return a generic// errorreturnOk::<_,Infallible>((StatusCode::INTERNAL_SERVER_ERROR,// `Cow` lets us return either `&str` or `String`Cow::from(format!("Unhandled internal error: {}",error)),                ));            })),    );asyncfnhandle() {}

The closure passed tohandle_error mustreturnResult<T, E> whereT implementsIntoResponse.

Seerouting::Router::handle_error for more details.

Applying multiple middleware

tower::ServiceBuilder can be used to combine multiple middleware:

useaxum::{body::Body,handler::get,http::Request,Router,};usetower::ServiceBuilder;usetower_http::compression::CompressionLayer;usestd::{borrow::Cow,time::Duration};letmiddleware_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();letapp=Router::new()    .route("/",get(|_:Request<Body>|async {/* ... */ }))    .layer(middleware_stack);

Commonly used middleware

tower::util andtower_http have a large collection of middleware that are compatiblewith axum. Some commonly used are:

useaxum::{body::{Body,BoxBody},handler::get,http::{Request,Response},Router,};usetower::{filter::AsyncFilterLayer,util::AndThenLayer,ServiceBuilder,};usestd::convert::Infallible;usetower_http::trace::TraceLayer;letmiddleware_stack=ServiceBuilder::new()// `TraceLayer` adds high level tracing and logging    .layer(TraceLayer::new_for_http())// `AsyncFilterLayer` lets you asynchronously transform the request    .layer(AsyncFilterLayer::new(map_request))// `AndThenLayer` lets you asynchronously transform the response    .layer(AndThenLayer::new(map_response))    .into_inner();asyncfnmap_request(req:Request<Body>)->Result<Request<Body>,Infallible> {Ok(req)}asyncfnmap_response(res:Response<BoxBody>)->Result<Response<BoxBody>,Infallible> {Ok(res)}letapp=Router::new()    .route("/",get(||async {/* ... */ }))    .layer(middleware_stack);

Additionally axum providesextract::extractor_middleware() for converting any extractor intoa middleware. Among other things, this can be useful for doing authorization. Seeextract::extractor_middleware() for more details.

Writing your own middleware

You can also write you own middleware by implementingtower::Service:

useaxum::{body::{Body,BoxBody},handler::get,http::{Request,Response},Router,};usefutures::future::BoxFuture;usetower::{Service,layer::layer_fn};usestd::task::{Context,Poll};#[derive(Clone)]structMyMiddleware<S> {inner:S,}impl<S,ReqBody,ResBody>Service<Request<ReqBody>>forMyMiddleware<S>whereS:Service<Request<ReqBody>,Response=Response<ResBody>>+Clone+Send+'static,S::Future:Send+'static,ReqBody:Send+'static,ResBody:Send+'static,{typeResponse=S::Response;typeError=S::Error;typeFuture=BoxFuture<'static,Result<Self::Response,Self::Error>>;fnpoll_ready(&mutself,cx:&mutContext<'_>)->Poll<Result<(),Self::Error>> {self.inner.poll_ready(cx)    }fncall(&mutself,mutreq:Request<ReqBody>)->Self::Future {println!("`MyMiddleware` called!");// best practice is to clone the inner service like this// see https://github.com/tower-rs/tower/issues/547 for detailsletclone=self.inner.clone();letmutinner=std::mem::replace(&mutself.inner,clone);Box::pin(asyncmove {letres:Response<ResBody>=inner.call(req).await?;println!("`MyMiddleware` received the response");Ok(res)        })    }}letapp=Router::new()    .route("/",get(||async {/* ... */ }))    .layer(layer_fn(|inner|MyMiddleware {inner }));

Sharing state with handlers

It is common to share some state between handlers for example to share apool of database connections or clients to other services. That can be doneusing theAddExtension middleware (applied withAddExtensionLayer)and theextract::Extension extractor:

useaxum::{AddExtensionLayer,extract,handler::get,Router,};usestd::sync::Arc;structState {// ...}letshared_state=Arc::new(State {/* ... */ });letapp=Router::new()    .route("/",get(handler))    .layer(AddExtensionLayer::new(shared_state));asyncfnhandler(state:extract::Extension<Arc<State>>,) {letstate:Arc<State>=state.0;// ...}

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 itsthe easiest way to get started.

Note thataxum::Server is re-exported by axum so if thats all you needthen you don’t have to explicitly depend on hyper.

Tower isn’t strictly necessary either but helpful for testing. See thetesting example in the repo to learn more about testing axum apps.

Examples

The axum repo containsa number of examples that show how to put all thepieces together.

Feature flags

axum uses a set offeature flags to reduce the amount of compiled andoptional 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.
  • tower-log: Enablestower’slog feature. Enabled by default.

Re-exports

pub use async_trait::async_trait;
pub usehttp;
pub use hyper::Server;
pub use tower_http::add_extension::AddExtension;
pub use tower_http::add_extension::AddExtensionLayer;

Modules

body

HTTP body utilities.

extract

Types and traits for extracting data from requests.

handler

Async functions that can be used to handle requests.

response

Types and traits for generating responses.

routing

Routing betweenServices.

service

Use TowerServices to handle requests.

Structs

Error

Errors that can happen when using axum.

Json

JSON Extractor/Response

Router

The router type for composing handlers and services.

Type Definitions

BoxError

Alias for a type-erased error type.


[8]ページ先頭

©2009-2025 Movatter.jp