All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
WebSocketUpgrade::write_buffer_size
and WebSocketUpgrade::max_write_buffer_size
WebSocketUpgrade::max_send_queue
Handler
for T: IntoResponse
(#2140)axum::extract::Query::try_from_uri
(#2058)IntoResponse
for Box<str>
and Box<[u8]>
(#2035).source()
of composite rejections (#2030)#[debug_handler]
(#2014)Sec-WebSocket-Key
header in WebSocketUpgrade
(#1972)CONNECT
requests (#1958)MatchedPath
in fallbacks (#1934)Router
with something nested at /
was used as a fallback (#1934)Router::new().fallback(...)
isn't optimal (#1940)axum::rejection=trace
target (#1890)Router::nest
introduced in 0.6.0. nest
now flattens the routes which performs better (#1711)MatchedPath
in nested handlers now gives the full matched path, including the nested path (#1711)Deref
and DerefMut
for built-in extractors (#1922)IntoResponse
for MultipartError
(#1861)S: Debug
for impl Debug for Router<S>
(#1836)#[must_use]
attributes to types that do nothing unless used (#1809)TypedHeader
extractor (#1810)Router
via a dynamic library (#1806)Allow
missing from routers with middleware (#1773)KeepAlive::event
for customizing the event sent for SSE keep alive (#1729)FormRejection::FailedToDeserializeFormBody
which is returned if the request body couldn't be deserialized into the target type, as opposed to FailedToDeserializeForm
which is only for query parameters (#1683)MockConnectInfo
for setting ConnectInfo
during tests (#1767)MethodRouter
to Router::fallback
(#1730)#[debug_handler]
sometimes giving wrong borrow related suggestions (#1710)impl IntoResponse
as the return type from handler functions (#1736)IntoResponse
for &'static [u8; N]
and [u8; N]
(#1690)Path
support types using serde::Deserializer::deserialize_any
(#1693)RawPathParams
(#1713)Clone
and Service
for axum::middleware::Next
(#1712)body_text
and status
methods to built-in rejections (#1612)runtime
feature of hyper
when using tokio
(#1671)Router::with_state
(#1580)fixed: Nested routers are now allowed to have fallbacks (#1521):
let api_router = Router::new() .route("/users", get(|| { ... })) .fallback(api_fallback); let app = Router::new() // this would panic in 0.5 but in 0.6 it just works // // requests starting with `/api` but not handled by `api_router` // will go to `api_fallback` .nest("/api", api_router);
The outer router‘s fallback will still apply if a nested router doesn’t have its own fallback:
// this time without a fallback let api_router = Router::new().route("/users", get(|| { ... })); let app = Router::new() .nest("/api", api_router) // `api_router` will inherit this fallback .fallback(app_fallback);
breaking: The request /foo/
no longer matches /foo/*rest
. If you want to match /foo/
you have to add a route specifically for that (#1086)
For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new() // this will match `/foo/bar/baz` .route("/foo/*rest", get(handler)) // this will match `/foo/` .route("/foo/", get(handler)) // if you want `/foo` to match you must also add an explicit route for it .route("/foo", get(handler)); async fn handler( // use an `Option` because `/foo/` and `/foo` don't have any path params params: Option<Path<String>>, ) {}
breaking: Path params for wildcard routes no longer include the prefix /
. e.g. /foo.js
will match /*filepath
with a value of foo.js
, not /foo.js
(#1086)
For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new().route("/foo/*rest", get(handler)); async fn handler( Path(params): Path<String>, ) { // for the request `/foo/bar/baz` the value of `params` will be `bar/baz` // // on 0.5 it would be `/bar/baz` }
fixed: Routes like /foo
and /*rest
are no longer considered overlapping. /foo
will take priority (#1086)
For example:
use axum::{Router, routing::get}; let app = Router::new() // this used to not be allowed but now just works .route("/foo/*rest", get(foo)) .route("/foo/bar", get(bar)); async fn foo() {} async fn bar() {}
breaking: Automatic trailing slash redirects have been removed. Previously if you added a route for /foo
, axum would redirect calls to /foo/
to /foo
(or vice versa for /foo/
):
use axum::{Router, routing::get}; let app = Router::new() // a request to `GET /foo/` will now get `404 Not Found` // whereas in 0.5 axum would redirect to `/foo` // // same goes the other way if you had the route `/foo/` // axum will no longer redirect from `/foo` to `/foo/` .route("/foo", get(handler)); async fn handler() {}
Either explicitly add routes for /foo
and /foo/
or use axum_extra::routing::RouterExt::route_with_tsr
if you want the old behavior (#1119)
breaking: Router::fallback
now only accepts Handler
s (similarly to what get
, post
, etc. accept). Use the new Router::fallback_service
for setting any Service
as the fallback (#1155)
This fallback on 0.5:
use axum::{Router, handler::Handler}; let app = Router::new().fallback(fallback.into_service()); async fn fallback() {}
Becomes this in 0.6
use axum::Router; let app = Router::new().fallback(fallback); async fn fallback() {}
breaking: It is no longer supported to nest
twice at the same path, i.e. .nest("/foo", a).nest("/foo", b)
will panic. Instead use .nest("/foo", a.merge(b))
breaking: It is no longer supported to nest
a router and add a route at the same path, such as .nest("/a", _).route("/a", _)
. Instead use .nest("/a/", _).route("/a", _)
.
changed: Router::nest
now only accepts Router
s, the general-purpose Service
nesting method has been renamed to nest_service
(#1368)
breaking: Allow Error: Into<Infallible>
for Route::{layer, route_layer}
(#924)
breaking: MethodRouter
now panics on overlapping routes (#1102)
breaking: Router::route
now only accepts MethodRouter
s created with get
, post
, etc. Use the new Router::route_service
for routing to any Service
s (#1155)
breaking: Adding a .route_layer
onto a Router
or MethodRouter
without any routes will now result in a panic. Previously, this just did nothing. #1327
breaking: RouterService
has been removed since Router
now implements Service
when the state is ()
. Use Router::with_state
to provide the state and get a Router<()>
. Note that RouterService
only existed in the pre-releases, not 0.5 (#1552)
added: Added new type safe State
extractor. This can be used with Router::with_state
and gives compile errors for missing states, whereas Extension
would result in runtime errors (#1155)
We recommend migrating from Extension
to State
for sharing application state since that is more type safe and faster. That is done by using Router::with_state
and State
.
This setup in 0.5
use axum::{routing::get, Extension, Router}; let app = Router::new() .route("/", get(handler)) .layer(Extension(AppState {})); async fn handler(Extension(app_state): Extension<AppState>) {} #[derive(Clone)] struct AppState {}
Becomes this in 0.6 using State
:
use axum::{routing::get, extract::State, Router}; let app = Router::new() .route("/", get(handler)) .with_state(AppState {}); async fn handler(State(app_state): State<AppState>) {} #[derive(Clone)] struct AppState {}
If you have multiple extensions, you can use fields on AppState
and implement FromRef
:
use axum::{extract::{State, FromRef}, routing::get, Router}; let state = AppState { client: HttpClient {}, database: Database {}, }; let app = Router::new().route("/", get(handler)).with_state(state); async fn handler( State(client): State<HttpClient>, State(database): State<Database>, ) {} // the derive requires enabling the "macros" feature #[derive(Clone, FromRef)] struct AppState { client: HttpClient, database: Database, } #[derive(Clone)] struct HttpClient {} #[derive(Clone)] struct Database {}
breaking: It is now only possible for one extractor per handler to consume the request body. In 0.5 doing so would result in runtime errors but in 0.6 it is a compile error (#1272)
axum enforces this by only allowing the last extractor to consume the request.
For example:
use axum::{Json, http::HeaderMap}; // This wont compile on 0.6 because both `Json` and `String` need to consume // the request body. You can use either `Json` or `String`, but not both. async fn handler_1( json: Json<serde_json::Value>, string: String, ) {} // This won't work either since `Json` is not the last extractor. async fn handler_2( json: Json<serde_json::Value>, headers: HeaderMap, ) {} // This works! async fn handler_3( headers: HeaderMap, json: Json<serde_json::Value>, ) {}
This is done by reworking the FromRequest
trait and introducing a new FromRequestParts
trait.
If your extractor needs to consume the request body then you should implement FromRequest
, otherwise implement FromRequestParts
.
This extractor in 0.5:
struct MyExtractor { /* ... */ } #[async_trait] impl<B> FromRequest<B> for MyExtractor where B: Send, { type Rejection = StatusCode; async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { // ... } }
Becomes this in 0.6:
use axum::{ extract::{FromRequest, FromRequestParts}, http::{StatusCode, Request, request::Parts}, async_trait, }; struct MyExtractor { /* ... */ } // implement `FromRequestParts` if you don't need to consume the request body #[async_trait] impl<S> FromRequestParts<S> for MyExtractor where S: Send + Sync, { type Rejection = StatusCode; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { // ... } } // implement `FromRequest` if you do need to consume the request body #[async_trait] impl<S, B> FromRequest<S, B> for MyExtractor where S: Send + Sync, B: Send + 'static, { type Rejection = StatusCode; async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { // ... } }
For an example of how to write an extractor that accepts different Content-Types
see the parse-body-based-on-content-type
example.
added: FromRequest
and FromRequestParts
derive macro re-exports from axum-macros
behind the macros
feature (#1352)
added: Add RequestExt
and RequestPartsExt
which adds convenience methods for running extractors to http::Request
and http::request::Parts
(#1301)
added: JsonRejection
now displays the path at which a deserialization error occurred (#1371)
added: Add extract::RawForm
for accessing raw urlencoded query bytes or request body (#1487)
fixed: Used 400 Bad Request
for FailedToDeserializeQueryString
rejections, instead of 422 Unprocessable Entity
(#1387)
changed: The inner error of a JsonRejection
is now serde_path_to_error::Error<serde_json::Error>
. Previously it was serde_json::Error
(#1371)
changed: The default body limit now applies to the Multipart
extractor (#1420)
breaking: ContentLengthLimit
has been removed. Use DefaultBodyLimit
instead (#1400)
breaking: RequestParts
has been removed as part of the FromRequest
rework (#1272)
breaking: BodyAlreadyExtracted
has been removed (#1272)
breaking: The following types or traits have a new S
type param which represents the state (#1155):
Router
, defaults to ()
MethodRouter
, defaults to ()
FromRequest
, no defaultHandler
, no defaultbreaking: MatchedPath
can now no longer be extracted in middleware for nested routes. In previous versions it returned invalid data when extracted from a middleware applied to a nested router. MatchedPath
can still be extracted from handlers and middleware that aren't on nested routers (#1462)
breaking: Rename FormRejection::FailedToDeserializeQueryString
to FormRejection::FailedToDeserializeForm
(#1496)
middleware::from_fn
functions (#1088)middleware::from_fn_with_state
to enable running extractors that require state (#1342)middleware::from_extractor_with_state
(#1396)map_request
, map_request_with_state
for transforming the request with an async function (#1408)map_response
, map_response_with_state
for transforming the response with an async function (#1414)IntoResponse
(#1152)extractor_middleware
which was previously deprecated. Use axum::middleware::from_extractor
instead (#1077)Handler::layer
to have Infallible
as the error type (#1152)simple-router-wasm
example for more details (#1382)ServiceExt
with methods for turning any Service
into a MakeService
similarly to Router::into_make_service
(#1302)From
impls have been added to extract::ws::Message
to be more inline with tungstenite
(#1421)#[derive(axum::extract::FromRef)]
(#1430)accept_unmasked_frames
setting in WebSocketUpgrade (#1529)WebSocketUpgrade::on_failed_upgrade
to customize what to do when upgrading a connection fails (#1539)#[track_caller]
so the error message points to where the user added the invalid route, rather than somewhere internally in axum (#1248)S: Service
, the bounds have been relaxed so the response type must implement IntoResponse
rather than being a literal Response
tokio
default feature needed for WASM support. If you don't need WASM support but have default_features = false
for other reasons you likely need to re-enable the tokio
feature (#1382)handler::{WithState, IntoService}
are merged into one type, named HandlerService
(#1418)breaking: Router::with_state
is no longer a constructor. It is instead used to convert the router into a RouterService
(#1532)
This nested router on 0.6.0-rc.4
Router::with_state(state).route(...);
Becomes this in 0.6.0-rc.5
Router::new().route(...).with_state(state);
breaking:: Router::inherit_state
has been removed. Use Router::with_state
instead (#1532)
breaking:: Router::nest
and Router::merge
now only supports nesting routers that use the same state type as the router they're being merged into. Use FromRef
for substates (#1532)
added: Add accept_unmasked_frames
setting in WebSocketUpgrade (#1529)
fixed: Nested routers will now inherit fallbacks from outer routers (#1521)
added: Add WebSocketUpgrade::on_failed_upgrade
to customize what to do when upgrading a connection fails (#1539)
JsonRejection
is now serde_path_to_error::Error<serde_json::Error>
. Previously it was serde_json::Error
(#1371)JsonRejection
now displays the path at which a deserialization error occurred (#1371)ContentLengthLimit
(#1389)400 Bad Request
for FailedToDeserializeQueryString
rejections, instead of 422 Unprocessable Entity
(#1387)middleware::from_extractor_with_state
(#1396)DefaultBodyLimit::max
for changing the default body limit (#1397)map_request
, map_request_with_state
for transforming the request with an async function (#1408)map_response
, map_response_with_state
for transforming the response with an async function (#1414)ContentLengthLimit
has been removed. Use DefaultBodyLimit
instead (#1400)Router
no longer implements Service
, call .into_service()
on it to obtain a RouterService
that does (#1368)Router::inherit_state
, which creates a Router
with an arbitrary state type without actually supplying the state; such a Router
can't be turned into a service directly (.into_service()
will panic), but can be nested or merged into a Router
with the same state type (#1368)Router::nest
now only accepts Router
s, the general-purpose Service
nesting method has been renamed to nest_service
(#1368)simple-router-wasm
example for more details (#1382)tokio
default feature needed for WASM support. If you don't need WASM support but have default_features = false
for other reasons you likely need to re-enable the tokio
feature (#1382)handler::{WithState, IntoService}
are merged into one type, named HandlerService
(#1418)Multipart
extractor (#1420)From
impls have been added to extract::ws::Message
to be more inline with tungstenite
(#1421)#[derive(axum::extract::FromRef)]
(#1430)FromRequest
and FromRequestParts
derive macro re-exports from axum-macros
behind the macros
feature (#1352)MatchedPath
can now no longer be extracted in middleware for nested routes (#1462)extract::RawForm
for accessing raw urlencoded query bytes or request body (#1487)FormRejection::FailedToDeserializeQueryString
to FormRejection::FailedToDeserializeForm
(#1496)Yanked, as it didn't compile in release mode.
breaking: Added default limit to how much data Bytes::from_request
will consume. Previously it would attempt to consume the entire request body without checking its length. This meant if a malicious peer sent an large (or infinite) request body your server might run out of memory and crash.
The default limit is at 2 MB and can be disabled by adding the new DefaultBodyLimit::disable()
middleware. See its documentation for more details.
This also applies to these extractors which used Bytes::from_request
internally:
Form
Json
String
(#1346)
.route_layer
onto a Router
or MethodRouter
without any routes will now result in a panic. Previously, this just did nothing. #1327middleware::from_fn_with_state
and middleware::from_fn_with_state_arc
to enable running extractors that require state (#1342)breaking: Nested Router
s will no longer delegate to the outer Router
's fallback. Instead you must explicitly set a fallback on the inner Router
(#1086)
This nested router on 0.5:
use axum::{Router, handler::Handler}; let api_routes = Router::new(); let app = Router::new() .nest("/api", api_routes) .fallback(fallback.into_service()); async fn fallback() {}
Becomes this in 0.6:
use axum::Router; let api_routes = Router::new() // we have to explicitly set the fallback here // since nested routers no longer delegate to the outer // router's fallback .fallback(fallback); let app = Router::new() .nest("/api", api_routes) .fallback(fallback); async fn fallback() {}
breaking: The request /foo/
no longer matches /foo/*rest
. If you want to match /foo/
you have to add a route specifically for that (#1086)
For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new() // this will match `/foo/bar/baz` .route("/foo/*rest", get(handler)) // this will match `/foo/` .route("/foo/", get(handler)) // if you want `/foo` to match you must also add an explicit route for it .route("/foo", get(handler)); async fn handler( // use an `Option` because `/foo/` and `/foo` don't have any path params params: Option<Path<String>>, ) {}
breaking: Path params for wildcard routes no longer include the prefix /
. e.g. /foo.js
will match /*filepath
with a value of foo.js
, not /foo.js
(#1086)
For example:
use axum::{Router, routing::get, extract::Path}; let app = Router::new().route("/foo/*rest", get(handler)); async fn handler( Path(params): Path<String>, ) { // for the request `/foo/bar/baz` the value of `params` will be `bar/baz` // // on 0.5 it would be `/bar/baz` }
fixed: Routes like /foo
and /*rest
are no longer considered overlapping. /foo
will take priority (#1086)
For example:
use axum::{Router, routing::get}; let app = Router::new() // this used to not be allowed but now just works .route("/foo/*rest", get(foo)) .route("/foo/bar", get(bar)); async fn foo() {} async fn bar() {}
breaking: Trailing slash redirects have been removed. Previously if you added a route for /foo
, axum would redirect calls to /foo/
to /foo
(or vice versa for /foo/
). That is no longer supported and such requests will now be sent to the fallback. Consider using axum_extra::routing::RouterExt::route_with_tsr
if you want the old behavior (#1119)
For example:
use axum::{Router, routing::get}; let app = Router::new() // a request to `GET /foo/` will now get `404 Not Found` // whereas in 0.5 axum would redirect to `/foo` // // same goes the other way if you had the route `/foo/` // axum will no longer redirect from `/foo` to `/foo/` .route("/foo", get(handler)); async fn handler() {}
breaking: Router::fallback
now only accepts Handler
s (similarly to what get
, post
, etc accept). Use the new Router::fallback_service
for setting any Service
as the fallback (#1155)
This fallback on 0.5:
use axum::{Router, handler::Handler}; let app = Router::new().fallback(fallback.into_service()); async fn fallback() {}
Becomes this in 0.6
use axum::Router; let app = Router::new().fallback(fallback); async fn fallback() {}
breaking: Allow Error: Into<Infallible>
for Route::{layer, route_layer}
(#924)
breaking: MethodRouter
now panics on overlapping routes (#1102)
breaking: Router::route
now only accepts MethodRouter
s created with get
, post
, etc. Use the new Router::route_service
for routing to any Service
s (#1155)
added: Added new type safe State
extractor. This can be used with Router::with_state
and gives compile errors for missing states, whereas Extension
would result in runtime errors (#1155)
We recommend migrating from Extension
to State
since that is more type safe and faster. That is done by using Router::with_state
and State
.
This setup in 0.5
use axum::{routing::get, Extension, Router}; let app = Router::new() .route("/", get(handler)) .layer(Extension(AppState {})); async fn handler(Extension(app_state): Extension<AppState>) {} #[derive(Clone)] struct AppState {}
Becomes this in 0.6 using State
:
use axum::{routing::get, extract::State, Router}; let app = Router::with_state(AppState {}) .route("/", get(handler)); async fn handler(State(app_state): State<AppState>) {} #[derive(Clone)] struct AppState {}
If you have multiple extensions you can use fields on AppState
and implement FromRef
:
use axum::{extract::{State, FromRef}, routing::get, Router}; let state = AppState { client: HttpClient {}, database: Database {}, }; let app = Router::with_state(state).route("/", get(handler)); async fn handler( State(client): State<HttpClient>, State(database): State<Database>, ) {} #[derive(Clone)] struct AppState { client: HttpClient, database: Database, } #[derive(Clone)] struct HttpClient {} impl FromRef<AppState> for HttpClient { fn from_ref(state: &AppState) -> Self { state.client.clone() } } #[derive(Clone)] struct Database {} impl FromRef<AppState> for Database { fn from_ref(state: &AppState) -> Self { state.database.clone() } }
breaking: It is now only possible for one extractor per handler to consume the request body. In 0.5 doing so would result in runtime errors but in 0.6 it is a compile error (#1272)
axum enforces this by only allowing the last extractor to consume the request.
For example:
use axum::{Json, http::HeaderMap}; // This wont compile on 0.6 because both `Json` and `String` need to consume // the request body. You can use either `Json` or `String`, but not both. async fn handler_1( json: Json<serde_json::Value>, string: String, ) {} // This won't work either since `Json` is not the last extractor. async fn handler_2( json: Json<serde_json::Value>, headers: HeaderMap, ) {} // This works! async fn handler_3( headers: HeaderMap, json: Json<serde_json::Value>, ) {}
This is done by reworking the FromRequest
trait and introducing a new FromRequestParts
trait.
If your extractor needs to consume the request body then you should implement FromRequest
, otherwise implement FromRequestParts
.
This extractor in 0.5:
struct MyExtractor { /* ... */ } #[async_trait] impl<B> FromRequest<B> for MyExtractor where B: Send, { type Rejection = StatusCode; async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> { // ... } }
Becomes this in 0.6:
use axum::{ extract::{FromRequest, FromRequestParts}, http::{StatusCode, Request, request::Parts}, async_trait, }; struct MyExtractor { /* ... */ } // implement `FromRequestParts` if you don't need to consume the request body #[async_trait] impl<S> FromRequestParts<S> for MyExtractor where S: Send + Sync, { type Rejection = StatusCode; async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> { // ... } } // implement `FromRequest` if you do need to consume the request body #[async_trait] impl<S, B> FromRequest<S, B> for MyExtractor where S: Send + Sync, B: Send + 'static, { type Rejection = StatusCode; async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> { // ... } }
breaking: RequestParts
has been removed as part of the FromRequest
rework (#1272)
breaking: BodyAlreadyExtracted
has been removed (#1272)
breaking: The following types or traits have a new S
type param which represents the state (#1155):
Router
, defaults to ()
MethodRouter
, defaults to ()
FromRequest
, no defaultHandler
, no defaultadded: Add RequestExt
and RequestPartsExt
which adds convenience methods for running extractors to http::Request
and http::request::Parts
(#1301)
extractor_middleware
which was previously deprecated. Use axum::middleware::from_extractor
instead (#1077)middleware::from_fn
functions (#1088)IntoResponse
(#1152)Handler::layer
to have Infallible
as the error type (#1152)S: Service
, the bounds have been relaxed so the response type must implement IntoResponse
rather than being a literal Response
#[track_caller]
so the error message points to where the user added the invalid route, rather than somewhere internally in axum (#1248)ServiceExt
with methods for turning any Service
into a MakeService
similarly to Router::into_make_service
(#1302)breaking: Added default limit to how much data Bytes::from_request
will consume. Previously it would attempt to consume the entire request body without checking its length. This meant if a malicious peer sent an large (or infinite) request body your server might run out of memory and crash.
The default limit is at 2 MB and can be disabled by adding the new DefaultBodyLimit::disable()
middleware. See its documentation for more details.
This also applies to these extractors which used Bytes::from_request
internally:
Form
Json
String
(#1346)
QueryRejection
response. (#1171)Yanked, as it contained an accidental breaking change.
WebSocketUpgrade
cannot upgrade the connection it will return a WebSocketUpgradeRejection::ConnectionNotUpgradable
rejection (#1135)WebSocketUpgradeRejection
has a new variant ConnectionNotUpgradable
variant (#1135)debug_handler
which is an attribute macro that improves type errors when applied to handler function. It is re-exported from axum-macros
(#1144)TryFrom<http::Method>
for MethodFilter
and use new NoMatchingMethodFilter
error in case of failure (#1130)Router
cheaper to clone (#1123)headers
is enabled and the form
feature is disabled (#1107)Forwarded
header in Host
extractor (#1078)IntoResponse
for Form
(#1095)Default
for Extension
(#1043)Vec<(String, String)>
in extract::Path<_>
to get vector of key/value pairs (#1059)extract::ws::close_code
which contains constants for close codes (#1067)impl IntoResponse
less in docs (#1049)WebSocket::protocol
to return the selected WebSocket subprotocol, if there is one. (#1022)PathRejection::WrongNumberOfParameters
to hint at using Path<(String, String)>
or Path<SomeStruct>
(#1023)PathRejection::WrongNumberOfParameters
now uses 500 Internal Server Error
since it's a programmer error and not a client error (#1023)InvalidFormContentType
mentioning the wrong content typeGET
, HEAD
, and OPTIONS
requests in ContentLengthLimit
. Request with these methods are now accepted if they do not have a Content-Length
header, and the request body will not be checked. If they do have a Content-Length
header they'll be rejected. This allows ContentLengthLimit
to be used as middleware around several routes, including GET
routes (#989)MethodRouter::{into_make_service, into_make_service_with_connect_info}
(#1010)response::ErrorResponse
and response::Result
for IntoResponse
-based error handling (#921)middleware::from_extractor
and deprecate extract::extractor_middleware
(#957)AppendHeaders
for appending headers to a response rather than overriding them (#927)axum::extract::multipart::Field::chunk
method for streaming a single chunk from the field (#901)Yanked, as it contained an accidental breaking change.
RequestParts::extract
which allows applying an extractor as a method call (#897)added: Document sharing state between handler and middleware (#783)
added: Extension<_>
can now be used in tuples for building responses, and will set an extension on the response (#797)
added: extract::Host
for extracting the hostname of a request (#827)
added: Add IntoResponseParts
trait which allows defining custom response types for adding headers or extensions to responses (#797)
added: TypedHeader
implements the new IntoResponseParts
trait so they can be returned from handlers as parts of a response (#797)
changed: Router::merge
now accepts Into<Router>
(#819)
breaking: sse::Event
now accepts types implementing AsRef<str>
instead of Into<String>
as field values.
breaking: sse::Event
now panics if a setter method is called twice instead of silently overwriting old values.
breaking: Require Output = ()
on WebSocketStream::on_upgrade
(#644)
breaking: Make TypedHeaderRejectionReason
#[non_exhaustive]
(#665)
breaking: Using HeaderMap
as an extractor will no longer remove the headers and thus they'll still be accessible to other extractors, such as axum::extract::Json
. Instead HeaderMap
will clone the headers. You should prefer to use TypedHeader
to extract only the headers you need (#698)
This includes these breaking changes:
RequestParts::take_headers
has been removed.RequestParts::headers
returns &HeaderMap
.RequestParts::headers_mut
returns &mut HeaderMap
.HeadersAlreadyExtracted
has been removed.HeadersAlreadyExtracted
variant has been removed from these rejections:RequestAlreadyExtracted
RequestPartsAlreadyExtracted
JsonRejection
FormRejection
ContentLengthLimitRejection
WebSocketUpgradeRejection
<HeaderMap as FromRequest<_>>::Rejection
has been changed to std::convert::Infallible
.breaking: axum::http::Extensions
is no longer an extractor (ie it doesn't implement FromRequest
). The axum::extract::Extension
extractor is not impacted by this and works the same. This change makes it harder to accidentally remove all extensions which would result in confusing errors elsewhere (#699) This includes these breaking changes:
RequestParts::take_extensions
has been removed.RequestParts::extensions
returns &Extensions
.RequestParts::extensions_mut
returns &mut Extensions
.RequestAlreadyExtracted
has been removed.<Request as FromRequest>::Rejection
is now BodyAlreadyExtracted
.<http::request::Parts as FromRequest>::Rejection
is now Infallible
.ExtensionsAlreadyExtracted
has been removed.ExtensionsAlreadyExtracted
removed variant has been removed from these rejections:ExtensionRejection
PathRejection
MatchedPathRejection
WebSocketUpgradeRejection
breaking: Redirect::found
has been removed (#800)
breaking: AddExtensionLayer
has been removed. Use Extension
instead. It now implements tower::Layer
(#807)
breaking: AddExtension
has been moved from the root module to middleware
breaking: .nest("/foo/", Router::new().route("/bar", _))
now does the right thing and results in a route at /foo/bar
instead of /foo//bar
(#824)
breaking: Routes are now required to start with /
. Previously routes such as :foo
would be accepted but most likely result in bugs (#823)
breaking: Headers
has been removed. Arrays of tuples directly implement IntoResponseParts
so ([("x-foo", "foo")], response)
now works (#797)
breaking: InvalidJsonBody
has been replaced with JsonDataError
to clearly signal that the request body was syntactically valid JSON but couldn't be deserialized into the target type
breaking: Handler
is no longer an #[async_trait]
but instead has an associated Future
type. That allows users to build their own Handler
types without paying the cost of #[async_trait]
(#879)
changed: New JsonSyntaxError
variant added to JsonRejection
. This is returned when the request body contains syntactically invalid JSON
fixed: Correctly set the Content-Length
header for response to HEAD
requests (#734)
fixed: Fix wrong content-length
for HEAD
requests to endpoints that returns chunked responses (#755)
fixed: Fixed several routing bugs related to nested “opaque” tower services (i.e. non-Router
services) (#841 and #842)
changed: Update to tokio-tungstenite 0.17 (#791)
breaking: Redirect::{to, temporary, permanent}
now accept &str
instead of Uri
(#889)
breaking: Remove second type parameter from Router::into_make_service_with_connect_info
and Handler::into_make_service_with_connect_info
to support MakeService
s that accept multiple targets (#892)
AddExtensionLayer
and AddExtension::layer
deprecation notes (#812)tower::Layer
for Extension
(#801)AddExtensionLayer
. Use Extension
instead (#805)middleware::from_fn
for creating middleware from async functions. This previously lived in axum-extra but has been moved to axum (#719)Allow
header when responding with 405 Method Not Allowed
(#733)Router
s at /
(#691)nest("", service)
work and mean the same as nest("/", service)
(#691)301
with 308
for trailing slash redirects. Also deprecates Redirect::found
(302
) in favor of Redirect::temporary
(307
) or Redirect::to
(303
). This is to prevent clients from changing non-GET
requests to GET
requests (#682)axum::AddExtension::layer
(#607)sse::Event
will no longer drop the leading space of data, event ID and name values that have it (#600)sse::Event
is more strict about what field values it supports, disallowing any SSE events that break the specification (such as field values containing carriage returns) (#599)sse::Event
(#601)Path
fail with ExtensionsAlreadyExtracted
if another extractor (such as Request
) has previously taken the request extensions. Thus PathRejection
now contains a variant with ExtensionsAlreadyExtracted
. This is not a breaking change since PathRejection
is marked as #[non_exhaustive]
(#619)PathRejection
if extensions had previously been extracted (#619)AtomicU32
internally, rather than AtomicU64
, to improve portability (#616)axum-core
(#592)axum::response::Response
now exists as a shorthand for writing Response<BoxBody>
(#590)MethodRouter
that works similarly to Router
:MethodRouter::layer
and MethodRouter::route_layer
.MethodRouter::merge
MethodRouter::fallback
FromRequest
and RequestParts
has been removed. Use FromRequest<Body>
and RequestParts<Body>
to get the previous behavior (#564)FromRequest
and IntoResponse
are now defined in a new called axum-core
. This crate is intended for library authors to depend on, rather than axum
itself, if possible. axum-core
has a smaller API and will thus receive fewer breaking changes. FromRequest
and IntoResponse
are re-exported from axum
in the same location so nothing is changed for axum
users (#564)axum::body::box_body
function has been removed. Use axum::body::boxed
instead..route("/", get(_)).route("/", post(_))
.routing::handler_method_router
and routing::service_method_router
has been removed in favor of routing::{get, get_service, ..., MethodRouter}
.HandleErrorExt
has been removed in favor of MethodRouter::handle_error
.HandleErrorLayer
now requires the handler function to be async
(#534)HandleErrorLayer
now supports running extractors.Handler<B, T>
trait is now defined as Handler<T, B = Body>
. That is the type parameters have been swapped and B
defaults to axum::body::Body
(#527)Router::merge
will panic if both routers have fallbacks. Previously the left side fallback would be silently discarded (#529)Router::nest
will panic if the nested router has a fallback. Previously it would be silently discarded (#529)charset=utf-8
for text content type. (#554)Body
and BodyError
associated types on the IntoResponse
trait have been removed - instead, .into_response()
will now always return Response<BoxBody>
(#571)PathParamsRejection
has been renamed to PathRejection
and its variants renamed to FailedToDeserializePathParams
and MissingPathParams
. This makes it more consistent with the rest of axum (#574)Path
‘s rejection type now provides data about exactly which part of the path couldn’t be deserialized (#574)box_body
has been renamed to boxed
. box_body
still exists but is deprecated (#530)FromRequest
for http::request::Parts
so it can be used an extractor (#489)IntoResponse
for http::response::Parts
(#490)Router::route_layer
for applying middleware that will only run on requests that match a route. This is useful for middleware that return early, such as authorization (#474)Clone
for IntoMakeServiceWithConnectInfo
(#471)fixed: All known compile time issues are resolved, including those with boxed
and those introduced by Rust 1.56 (#404)
breaking: The router's type is now always Router
regardless of how many routes or middleware are applied (#404)
This means router types are all always nameable:
fn my_routes() -> Router { Router::new().route( "/users", post(|| async { "Hello, World!" }), ) }
breaking: Added feature flags for HTTP1 and JSON. This enables removing a few dependencies if your app only uses HTTP2 or doesn't use JSON. This is only a breaking change if you depend on axum with default_features = false
. (#286)
breaking: Route::boxed
and BoxRoute
have been removed as they're no longer necessary (#404)
breaking: Nested
, Or
types are now private. They no longer had to be public because Router
is internally boxed (#404)
breaking: Remove routing::Layered
as it didn‘t actually do anything and thus wasn’t necessary
breaking: Vendor AddExtensionLayer
and AddExtension
to reduce public dependencies
breaking: body::BoxBody
is now a type alias for http_body::combinators::UnsyncBoxBody
and thus is no longer Sync
. This is because bodies are streams and requiring streams to be Sync
is unnecessary.
added: Implement IntoResponse
for http_body::combinators::UnsyncBoxBody
.
added: Add Handler::into_make_service
for serving a handler without a Router
.
added: Add Handler::into_make_service_with_connect_info
for serving a handler without a Router
, and storing info about the incoming connection.
breaking: axum's minimum supported rust version is now 1.56
.route("/api/users/*rest", service)
are now supported.Router::route
call. So .route("/", get(get_handler).post(post_handler))
and not .route("/", get(get_handler)).route("/", post(post_handler))
.axum::handler
to axum::routing
. So axum::handler::get
now lives at axum::routing::get
(#405)axum::service
to axum::routing::service_method_routing
. So axum::service::get
now lives at axum::routing::service_method_routing::get
, etc. (#405)Router::or
renamed to Router::merge
and will now panic on overlapping routes. It now only accepts Router
s and not general Service
s. Use Router::fallback
for adding fallback routes (#408)Router::fallback
for adding handlers for request that didn't match any routes. Router::fallback
must be use instead of nest("/", _)
(#408)EmptyRouter
has been renamed to MethodNotAllowed
as it's only used in method routers and not in path routers (Router
)CONNECT
method. An example of combining axum with and HTTP proxy can be found here (#428)i128
and u128
in extract::Path
extract::Path
(#272)Connected::connect_info
to return Self
and remove the associated type ConnectInfo
(#396)extract::MatchedPath
for accessing path in router that matched the request (#412)breaking: Simplify error handling model (#402):
error_handling
module.Router::check_infallible
has been removed since routers are always infallible with the error handling changes.IntoResponse
.With these changes handling errors from fallible middleware is done like so:
use axum::{ routing::get, http::StatusCode, error_handling::HandleErrorLayer, response::IntoResponse, Router, BoxError, }; use tower::ServiceBuilder; use std::time::Duration; let middleware_stack = ServiceBuilder::new() // Handle errors from middleware // // This middleware most be added above any fallible // ones if you're using `ServiceBuilder`, due to how ordering works .layer(HandleErrorLayer::new(handle_error)) // Return an error after 30 seconds .timeout(Duration::from_secs(30)); let app = Router::new() .route("/", get(|| async { /* ... */ })) .layer(middleware_stack); fn handle_error(_error: BoxError) -> impl IntoResponse { StatusCode::REQUEST_TIMEOUT }
And handling errors from fallible leaf services is done like so:
use axum::{ Router, service, body::Body, routing::service_method_routing::get, response::IntoResponse, http::{Request, Response}, error_handling::HandleErrorExt, // for `.handle_error` }; use std::{io, convert::Infallible}; use tower::service_fn; let app = Router::new() .route( "/", get(service_fn(|_req: Request<Body>| async { let contents = tokio::fs::read_to_string("some_file").await?; Ok::<_, io::Error>(Response::new(Body::from(contents))) })) .handle_error(handle_io_error), ); fn handle_io_error(error: io::Error) -> impl IntoResponse { // ... }
handler::any
and service::any
only accepts standard HTTP methods (#337)StreamExt::split
with WebSocket
(#291)BoxRoute
used to be Sync
but was accidental made !Sync
(#273)/
will no longer be matched by /:key
(#264)Router::boxed
(#269)Redirect::to
constructor (#255)IntoResponse
for custom error type (#258)Overall:
Routing:
Router
to replace the RoutingDsl
trait (#214)Router::or
for combining routes (#108)Router::new().route("/", get(...)).route("/", post(...))
now accepts both GET
and POST
. Previously only POST
would be accepted (#224)get
routes will now also be called for HEAD
requests but will always have the response body removed (#129)axum::route(...)
with axum::Router::new().route(...)
. This means there is now only one way to create a new router. Same goes for axum::routing::nest
. (#215)routing::MethodFilter
via bitflags
(#158)handle_error
from ServiceExt
to service::OnMethod
(#160)With these changes this app using 0.1:
use axum::{extract::Extension, prelude::*, routing::BoxRoute, AddExtensionLayer}; let app = route("/", get(|| async { "hi" })) .nest("/api", api_routes()) .layer(AddExtensionLayer::new(state)); fn api_routes() -> BoxRoute<Body> { route( "/users", post(|Extension(state): Extension<State>| async { "hi from nested" }), ) .boxed() }
Becomes this in 0.2:
use axum::{ extract::Extension, handler::{get, post}, routing::BoxRoute, Router, }; let app = Router::new() .route("/", get(|| async { "hi" })) .nest("/api", api_routes()); fn api_routes() -> Router<BoxRoute> { Router::new() .route( "/users", post(|Extension(state): Extension<State>| async { "hi from nested" }), ) .boxed() }
Extractors:
FromRequest
default to being generic over body::Body
(#146)std::error::Error
for all rejections (#153)OriginalUri
for extracting original request URI in nested services (#197)FromRequest
for http::Extensions
(#169)RequestParts::{new, try_into_request}
public so extractors can be used outside axum (#194)FromRequest
for axum::body::Body
(#241)extract::UrlParams
and extract::UrlParamsMap
. Use extract::Path
instead (#154)extractor_middleware
now requires RequestBody: Default
(#167)RequestAlreadyExtracted
to an enum with each possible error variant (#167)extract::BodyStream
is no longer generic over the request body (#234)extract::Body
has been renamed to extract::RawBody
to avoid conflicting with body::Body
(#233)RequestParts
changes (#153)method
new returns an &http::Method
method_mut
new returns an &mut http::Method
take_method
has been removeduri
new returns an &http::Uri
uri_mut
new returns an &mut http::Uri
take_uri
has been removedResponses:
Headers
for easily customizing headers on a response (#193)Redirect
response (#192)body::StreamBody
for easily responding with a stream of byte chunks (#237)Body
and BodyError
types to IntoResponse
. This is required for returning responses with bodies other than hyper::Body
from handlers. See the docs for advice on how to implement IntoResponse
(#86)tower::util::Either
no longer implements IntoResponse
(#229)This IntoResponse
from 0.1:
use axum::{http::Response, prelude::*, response::IntoResponse}; struct MyResponse; impl IntoResponse for MyResponse { fn into_response(self) -> Response<Body> { Response::new(Body::empty()) } }
Becomes this in 0.2:
use axum::{body::Body, http::Response, response::IntoResponse}; struct MyResponse; impl IntoResponse for MyResponse { type Body = Body; type BodyError = <Self::Body as axum::body::HttpBody>::Error; fn into_response(self) -> Response<Self::Body> { Response::new(Body::empty()) } }
SSE:
response::sse::Sse
. This implements SSE using a response rather than a service (#98)axum::sse
. It has been replaced by axum::response::sse
(#98)Handler using SSE in 0.1:
use axum::{ prelude::*, sse::{sse, Event}, }; use std::convert::Infallible; let app = route( "/", sse(|| async { let stream = futures::stream::iter(vec![Ok::<_, Infallible>( Event::default().data("hi there!"), )]); Ok::<_, Infallible>(stream) }), );
Becomes this in 0.2:
use axum::{ handler::get, response::sse::{Event, Sse}, Router, }; use std::convert::Infallible; let app = Router::new().route( "/", get(|| async { let stream = futures::stream::iter(vec![Ok::<_, Infallible>( Event::default().data("hi there!"), )]); Sse::new(stream) }), );
WebSockets:
Message
an enum (#116)WebSocket
now uses Error
as its error type (#150)Handler using WebSockets in 0.1:
use axum::{ prelude::*, ws::{ws, WebSocket}, }; let app = route( "/", ws(|socket: WebSocket| async move { // do stuff with socket }), );
Becomes this in 0.2:
use axum::{ extract::ws::{WebSocket, WebSocketUpgrade}, handler::get, Router, }; let app = Router::new().route( "/", get(|ws: WebSocketUpgrade| async move { ws.on_upgrade(|socket: WebSocket| async move { // do stuff with socket }) }), );
Misc
tower-log
which exposes tower
's log
feature. (#218)body::BoxStdError
with axum::Error
, which supports downcasting (#150)EmptyRouter
now requires the response body to implement Send + Sync + 'static'
(#108)Router::check_infallible
now returns a CheckInfallible
service. This is to improve compile times (#198)Router::into_make_service
now returns routing::IntoMakeService
rather than tower::make::Shared
(#229)tower::BoxError
has been replaced with axum::BoxError
(#229)future
modules (#133)EmptyRouter
, ExtractorMiddleware
, ExtractorMiddlewareLayer
, and QueryStringMissing
no longer implement Copy
(#132)service::OnMethod
, handler::OnMethod
, and routing::Nested
have new response future types (#157)/
(#91)pin-project-lite
instead of pin-project
(#95)http
crate and hyper::Server
(#110)Query
and Form
extractors giving bad request error when query string is empty. (#117)Path
extractor. (#124)IntoResponse
of (HeaderMap, T)
and (StatusCode, HeaderMap, T)
would ignore headers from T
(#137)extract::UrlParams
and extract::UrlParamsMap
. Use extract::Path
instead (#138)Stream
for WebSocket
(#52)Sink
for WebSocket
(#52)Deref
most extractors (#56)405 Method Not Allowed
for unsupported method for route (#63)MissingExtension
rejections (#72)tower::Service
s (#69)axum::body::box_body
to converting an http_body::Body
to axum::body::BoxBody
(#69)axum::sse
for Server-Sent Events (#75)