宣布 tower-http

2021年5月27日

今天我很高兴宣布 tower-http,它是一个 HTTP 特定中间件和实用程序的集合,使用 Tower 的 Service trait 构建。

Tower 本身包含的中间件都是协议无关的。例如,它的 timeout 中间件 与任何 Service 实现兼容,无论它使用哪种协议。这很棒,因为它意味着中间件更具可重用性,但也意味着您无法使用特定于协议的功能。 在 HTTP 的情况下,这意味着 Tower 中的任何中间件都不知道状态码、标头或其他 HTTP 特定功能。

另一方面,tower-http 包含特定于 HTTP 的中间件。它使用 httphttp-body crates,这意味着它与任何也使用这些的 crate 兼容,例如 hypertonicwarp

tower-http 的目标是提供丰富的中间件集合,用于解决构建 HTTP 客户端和服务器时的常见问题。 一些亮点包括:

  • Trace:轻松为您的应用程序添加高级跟踪/日志记录。 支持通过状态码以及 gRPC 特定标头确定成功或失败。 具有出色的默认值,但也支持深度自定义。
  • CompressionDecompression:自动压缩或解压缩响应体。 这与使用 ServeDir 服务静态文件非常搭配。
  • FollowRedirect:自动跟踪重定向响应。

以及一些较小的实用程序,例如设置请求和响应标头、从日志中隐藏敏感标头、授权等等。

使用 tower-http 中的组件构建一个小型服务器看起来像这样

use tower_http::{
    compression::CompressionLayer,
    auth::RequireAuthorizationLayer,
    trace::TraceLayer,
};
use tower::{ServiceBuilder, make::Shared};
use http::{Request, Response};
use hyper::{Body, Error, server::Server};
use std::net::SocketAddr;

// Our request handler. This is where we would implement the application logic
// for responding to HTTP requests...
async fn handler(request: Request<Body>) -> Result<Response<Body>, Error> {
    Ok(Response::new(Body::from("Hello, World!")))
}

#[tokio::main]
async fn main() {
    // Use `tower`'s `ServiceBuilder` API to build a stack of middleware
    // wrapping our request handler.
    let service = ServiceBuilder::new()
        // High level tracing of requests and responses.
        .layer(TraceLayer::new_for_http())
        // Compress responses.
        .layer(CompressionLayer::new())
        // Authorize requests using a token.
        .layer(RequireAuthorizationLayer::bearer("tower-is-cool"))
        // Wrap a `Service` in our middleware stack.
        .service_fn(handler);

    // And run our service using `hyper`.
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    Server::bind(&addr)
        .serve(Shared::new(service))
        .await
        .expect("server error");
}

构建客户端看起来像这样

use tower_http::{
    decompression::DecompressionLayer,
    set_header::SetRequestHeaderLayer,
};
use tower::ServiceBuilder;
use hyper::Body;
use http::{Request, Response, HeaderValue, header::USER_AGENT};

let client = ServiceBuilder::new()
    // Log failed requests
    .layer(
        TraceLayer::new_for_http()
            .on_request(())
            .on_response(())
            .on_body_chunk(())
            .on_eos(())
            // leave the `on_failure` callback as the default
    )
    // Set a `User-Agent` header on all requests
    .layer(SetRequestHeaderLayer::<_, Body>::overriding(
        USER_AGENT,
        HeaderValue::from_static("my-app")
    ))
    // Decompress response bodies
    .layer(DecompressionLayer::new())
    // Wrap a `hyper::Client` in our middleware stack
    .service(hyper::Client::new());

我们在文档上投入了大量精力,并确保所有内容都有易于理解的示例。 我们还构建了两个示例应用程序,一个使用 warp,另一个使用 tonic,向您展示如何将所有内容组合在一起。

非常欢迎贡献,如果您有任何问题,可以在 Tokio Discord 服务器中找到我们。

— David Pedersen (@davidpdrsn)