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_sizeWebSocketUpgrade::max_send_queueHandler 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 Handlers (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 Routers, 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 MethodRouters created with get, post, etc. Use the new Router::route_service for routing to any Services (#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 Responsetokio 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 Routers, 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:
FormJsonString(#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 Routers 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 Handlers (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 MethodRouters created with get, post, etc. Use the new Router::route_service for routing to any Services (#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:
FormJsonString(#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:RequestAlreadyExtractedRequestPartsAlreadyExtractedJsonRejectionFormRejectionContentLengthLimitRejectionWebSocketUpgradeRejection<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:ExtensionRejectionPathRejectionMatchedPathRejectionWebSocketUpgradeRejectionbreaking: 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 MakeServices 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)Routers 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::mergeMethodRouter::fallbackFromRequest 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 Routers and not general Services. 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::Pathextract::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::Methodmethod_mut new returns an &mut http::Methodtake_method has been removeduri new returns an &http::Uriuri_mut new returns an &mut http::Uritake_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::Services (#69)axum::body::box_body to converting an http_body::Body to axum::body::BoxBody (#69)axum::sse for Server-Sent Events (#75)