发布 Axum

2021 年 7 月 30 日

今天我们很高兴宣布 axum:一个易于使用但功能强大的 Web 框架,旨在充分利用 Tokio 生态系统。

高级特性

  • 使用无宏 API 将请求路由到处理程序。
  • 使用提取器声明式地解析请求。
  • 简单且可预测的错误处理模型。
  • 以最少的样板代码生成响应。
  • 充分利用 towertower-http 中间件、服务和实用程序生态系统。

特别是最后一点使 axum 从现有框架中脱颖而出。axum 没有自己的中间件系统,而是使用 tower::Service。这意味着 axum 可以免费获得超时、跟踪、压缩、授权等功能。它还使您能够与使用 hypertonic 编写的应用程序共享中间件。

使用示例

axum 的 “hello world” 示例如下

use axum::prelude::*;
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    let app = route("/", get(root));

    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    hyper::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn root() -> &'static str {
    "Hello, World!"
}

这将使用 200 OK 响应来响应 GET / 请求,其中响应正文为 Hello, World!。任何其他请求都将导致 404 Not Found 响应。

提取器

可以使用“提取器”声明式地解析请求。提取器是一种实现 FromRequest 的类型。提取器可以用作处理程序的参数,并且将在请求 URI 匹配时运行。

例如,Json 是一个提取器,它消耗请求正文并将其解析为 JSON

use axum::{prelude::*, extract::Json};
use serde::Deserialize;

#[derive(Deserialize)]
struct CreateUser {
    username: String,
}

async fn create_user(Json(payload): Json<CreateUser>) {
    // `payload` is a `CreateUser`
}

let app = route("/users", post(create_user));

axum 附带了许多有用的提取器,例如

  • BytesStringBodyBodyStream,用于消耗请求正文。
  • MethodHeaderMapUri,用于获取请求的特定部分。
  • FormQueryUrlParamsUrlParamsMap,用于更高级别的请求解析。
  • [Extension],用于在处理程序之间共享状态。
  • 如果您想要完全控制,可以使用 Request<hyper::Body>
  • 使用 Result<T, E>Option<T> 使提取器成为可选的。

您还可以通过实现 FromRequest 来定义自己的提取器。

构建响应

处理程序可以返回任何实现了 IntoResponse 的内容,它将自动转换为响应

use http::StatusCode;
use axum::response::{Html, Json};
use serde_json::{json, Value};

// We've already seen returning &'static str
async fn text() -> &'static str {
    "Hello, World!"
}

// String works too
async fn string() -> String {
    "Hello, World!".to_string()
}

// Returning a tuple of `StatusCode` and another `IntoResponse` will
// change the status code
async fn not_found() -> (StatusCode, &'static str) {
    (StatusCode::NOT_FOUND, "not found")
}

// `Html` gives a content-type of `text/html`
async fn html() -> Html<&'static str> {
    Html("<h1>Hello, World!</h1>")
}

// `Json` gives a content-type of `application/json` and works with any type
// that implements `serde::Serialize`
async fn json() -> Json<Value> {
    Json(json!({ "data": 42 }))
}

这意味着在实践中,您很少需要构建自己的 Response。您还可以实现 IntoResponse 以创建您自己的特定于域的响应。

路由

可以使用简单的 DSL 组合多个路由

use axum::prelude::*;

let app = route("/", get(root))
    .route("/users", get(list_users).post(create_user))
    .route("/users/:id", get(show_user).delete(delete_user));

中间件

axum 支持来自 towertower-http 的中间件

use axum::prelude::*;
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
use tower::ServiceBuilder;
use std::time::Duration;

let middleware_stack = ServiceBuilder::new()
    // timeout all requests after 10 seconds
    .timeout(Duration::from_secs(10))
    // add high level tracing of requests and responses
    .layer(TraceLayer::new_for_http())
    // compression responses
    .layer(CompressionLayer::new())
    // convert the `ServiceBuilder` into a `tower::Layer`
    .into_inner();

let app = route("/", get(|| async { "Hello, World!" }))
    // wrap our application in the middleware stack
    .layer(middleware_stack);

此功能至关重要,因为它允许我们编写一次中间件并在应用程序之间共享它们。例如,axum 不必提供自己的跟踪/日志记录中间件,可以直接使用来自 tower-httpTraceLayer。相同的中间件也可以用于使用 tonic 构建的客户端或服务器。

路由到任何 tower::Service

axum 也可以将请求路由到任何 tower 叶子服务。可以是您使用 service_fn 编写的服务,也可以是来自另一个 crate 的服务,例如来自 tower-httpServeFile

use axum::{service, prelude::*};
use http::Response;
use std::convert::Infallible;
use tower::{service_fn, BoxError};
use tower_http::services::ServeFile;

let app = route(
    // Any request to `/` goes to a some `Service`
    "/",
    service::any(service_fn(|_: Request<Body>| async {
        let res = Response::new(Body::from("Hi from `GET /`"));
        Ok::<_, Infallible>(res)
    }))
).route(
    // GET `/static/Cargo.toml` goes to a service from tower-http
    "/static/Cargo.toml",
    service::get(ServeFile::new("Cargo.toml"))
);

了解更多

这只是 axum 提供功能的一小部分示例。错误处理、Web 套接字和解析 multipart/form-data 请求是一些此处未展示的功能。请参阅 文档 了解更多详情。

我们还鼓励您查看 repo 中的示例,以了解一些使用 axum 编写的稍大型的应用程序。

与往常一样,如果您有任何问题,可以在 Tokio Discord 服务器中找到我们。

— David Pedersen (@davidpdrsn)