宣布 axum 0.6.0
2022 年 11 月 25 日
早在八月份,我们宣布了 axum
0.6.0-rc.1,今天我很高兴地宣布预发布期已结束,axum
0.6.0 正式发布!
axum
是一个符合人体工程学且模块化的 Web 框架,使用 tokio
、tower
和 hyper
构建。
本次发布还包括 axum-core
、axum-extra
和 axum-macros
的新的主要版本。
如果您已经阅读过 rc.1 公告,那么其中一些内容您会感到熟悉。然而,API 的许多细节都经过微调,以便更易于使用和更灵活。
类型安全的 State
提取器
以前,与处理程序共享状态的推荐方法是使用 Extension
中间件和提取器
use axum::{
Router,
Extension,
routing::get,
};
#[derive(Clone)]
struct AppState {}
let state = AppState {};
let app = Router::new()
.route("/", get(handler))
// Add `Extension` as a middleware
.layer(Extension(state));
async fn handler(
// And extract our shared `AppState` using `Extension`
Extension(state): Extension<AppState>,
) {}
然而,这不是类型安全的,因此如果您忘记了 .layer(Extension(...))
,代码可以正常编译,但在调用 handler
时会出现运行时错误。
在 0.6 版本中,您可以使用新的 State
提取器,它的工作方式类似于 Extension
,但类型安全
use axum::{
Router,
extract::State,
routing::get,
};
#[derive(Clone)]
struct AppState {}
let state = AppState {};
let app = Router::new()
.route("/", get(handler))
// Provide the state for the router
.with_state(state);
async fn handler(
// And extract our shared `AppState` using `State`
//
// This will only compile if the type passed to `Router::with_state`
// matches what we're extracting here
State(state): State<AppState>,
) {}
State
也支持提取“子状态”
use axum::{
extract::{State, FromRef},
routing::get,
Router,
};
// Our top level state that contains an `HttpClient` and a `Database`
//
// `#[derive(FromRef)]` makes them sub states so they can be extracted
// independently
#[derive(Clone, FromRef)]
struct AppState {
client: HttpClient,
database: Database,
}
#[derive(Clone)]
struct HttpClient {}
#[derive(Clone)]
struct Database {}
let state = AppState {
client: HttpClient {},
database: Database {},
};
let app = Router::new()
.route("/", get(handler))
.with_state(state);
async fn handler(
// We can extract both `State<HttpClient>` and `State<Database>`
State(client): State<HttpClient>,
State(database): State<Database>,
) {}
也可以在合并和嵌套的子路由器上使用不同的状态类型
let app = Router::new()
// A route on the outermost router that requires `OuterState` as the
// state
.route("/", get(|_: State<OuterState>| { ... }))
// Nest a router under `/api` that requires an `ApiState`
//
// We have to provide the state when nesting it into another router
// since it uses a different state type
.nest("/api", api_router().with_state(ApiState {}))
// Same goes for routers we merge
.merge(some_other_routes().with_state(SomeOtherState {}))
// Finally provide the `OuterState` needed by the first route we
// added
.with_state(OuterState {});
// We don't need to provide the state when constructing the sub routers
//
// We only need to do that when putting everything together. That means
// we don't need to pass the different states around to each function
// that builds a sub router
fn api_router() -> Router<ApiState> {
Router::new()
.route("/users", get(|_: State<ApiState>| { ... }))
}
fn some_other_state() -> Router<SomeOtherState> {
Router::new()
.route("/foo", get(|_: State<SomeOtherState>| { ... }))
}
#[derive(Clone)]
struct ApiState {};
#[derive(Clone)]
struct SomeOtherState {};
#[derive(Clone)]
struct OuterState {};
虽然 Extension
仍然有效,但我们建议用户迁移到 State
,不仅因为它更类型安全,而且因为它速度更快。
类型安全的提取器排序
延续类型安全的主题,axum
现在强制执行只有一个提取器可以消耗请求体。在 0.5 版本中,这可以正常编译,但在运行时会失败
use axum::{
Router,
Json,
routing::post,
body::Body,
http::Request,
};
let app = Router::new()
.route("/", post(handler).get(other_handler));
async fn handler(
// This would consume the request body
json_body: Json<serde_json::Value>,
// This would also attempt to consume the body but fail
// since it is gone
request: Request<Body>,
) {}
async fn other_handler(
request: Request<Body>,
// This would also fail at runtime, even if the `AppState` extension
// was set since `Request<Body>` consumes all extensions
state: Extension<AppState>,
) {}
解决方案是手动确保您只使用一个消耗请求的提取器,并且它是最后一个提取器。axum
0.6 现在在编译时强制执行此操作
use axum::{
Router,
Json,
routing::post,
body::Body,
http::Request,
};
let app = Router::new()
.route("/", post(handler).get(other_handler));
async fn handler(
// We cannot extract both `Request` and `Json`, have to pick one
json_body: Json<serde_json::Value>,
) {}
async fn other_handler(
state: Extension<AppState>,
// `Request` must be extracted last
request: Request<Body>,
) {}
这是通过重构 FromRequest
trait 并添加新的 FromRequestParts
trait 来完成的。
这也意味着,如果您有不需要请求体的 FromRequest
实现,那么您应该改为实现 FromRequestParts
。
从 middleware::from_fn
运行提取器
middleware::from_fn
使使用熟悉的 async/await 语法编写中间件变得容易。在 0.6 版本中,此类中间件也可以运行提取器
use axum::{
Router,
middleware::{self, Next},
response::{Response, IntoResponse},
http::Request,
routing::get,
};
use axum_extra::extract::cookie::{CookieJar, Cookie};
async fn my_middleware<B>(
// Run the `CookieJar` extractor as part of this middleware
cookie_jar: CookieJar,
request: Request<B>,
next: Next<B>,
) -> Response {
let response = next.run(request).await;
// Add a cookie to the jar
let updated_cookie_jar = cookie_jar.add(Cookie::new("foo", "bar"));
// Add the new cookies to the response
(updated_cookie_jar, response).into_response()
}
let app = Router::new()
.route("/", get(|| async { "Hello, World!" }))
.layer(middleware::from_fn(my_middleware));
还有新的 map_request
和 map_response
中间件函数,其工作方式类似于 middleware::from_fn
,以及支持提取 State
的 from_fn_with_state
、[map_request_with_state
,] 和 map_response_with_state
版本。
嵌套路由器的回退继承
在 axum
0.5 中,嵌套路由器不允许有回退,并且会导致 panic
let api_router = Router::new()
.route("/users", get(|| { ... }))
.fallback(api_fallback);
let app = Router::new()
// this would panic since `api_router` has a fallback
.nest("/api", api_router);
然而,在 0.6 版本中,这现在可以正常工作了,以 /api
开头但与 api_router
不匹配的请求将转到 api_fallback
。
如果嵌套路由器没有自己的回退,则外部路由器的回退仍然适用
// This time without a fallback
let api_router = Router::new().route("/users", get(|| { ... }));
let app = Router::new()
.nest("/api", api_router)
// `api_fallback` will inherit this fallback
.fallback(app_fallback);
因此,通常您只需在需要的地方放置回退,axum 就会做正确的事情!
WebAssembly 支持
axum
现在支持通过禁用 tokio
功能编译为 WebAssembly
axum = { version = "0.6", default-features = false }
default-features = false
将禁用 tokio
功能,该功能是默认功能之一。
这将禁用 tokio
、hyper
和 axum
中不支持 WebAssembly 的部分。
删除了尾部斜杠重定向
以前,如果您有 /foo
的路由,但收到了对 /foo/
的请求,axum
会向 /foo
发送重定向响应。然而,许多人发现此行为令人惊讶,并且当与执行自身重定向或使用中间件的服务结合使用时,它存在边缘情况错误,因此在 0.6 版本中,我们决定删除此功能。
推荐的解决方案是显式添加您想要的路由
use axum::{
Router,
routing::get,
};
let app = Router::new()
// Send `/foo` and `/foo/` to the same handler
.route("/foo", get(foo))
.route("/foo/", get(foo));
async fn foo() {}
如果您想选择使用旧的行为,可以使用来自 axum-extra
的 RouterExt::route_with_tsr
。
混合通配符路由和常规路由
axum
的 Router
现在更好地支持混合通配符路由和常规路由
use axum::{Router, routing::get};
let app = Router::new()
// In 0.5 these routes would be considered overlapping and not be
// allowed but in 0.6 it just works
.route("/foo/*rest", get(|| async {}))
.route("/foo/bar", get(|| async {}));
请参阅更新日志了解更多信息
我鼓励您阅读 更新日志,以查看所有更改以及有关如何从 0.5 更新到 0.6 的提示。
此外,如果您在更新时遇到问题或发现错误,请在 Discord 中提问或 提交 issue。