tower-http

repository·main·Indexed 21 days ago

https://github.com/tower-rs/tower-http

A collection of middleware and utilities for HTTP clients and servers in Rust, version 0.7.0. It provides functionality such as logging (Trace), compression, decompression, redirection handling, and request/response header manipulation. Designed to work with the standard http crate and frameworks like Axum, Tonic, and Warp. It includes the ServiceBuilderExt trait for chaining HTTP-specific layers and a response classification system via the ClassifyResponse trait.

Tokens
15.5K
Snippets
47
Records
73
Agent score
75%

What's inside tower-http

  1. Overview of Tower HTTP middleware

    main
    Tower HTTP provides a collection of middleware and utilities designed for both HTTP clients and servers. The middleware is built on top of the http crate, making it compatible with any Rust library or framework that implements the http interface, such as hyper.
  2. Run the tonic-key-value-store example

    main

    The tonic-key-value-store example demonstrates a simple key/value store using a gRPC API and a client built with tonic. You can run the server, set values, get values, or subscribe to a stream of new keys using the provided CLI interface.

    ### Running the server
    RUST_LOG=tonic_key_value_store=trace,tower_http=trace \
        cargo run --bin tonic-key-value-store -- -p 3000 server
    
    ### Setting a value
    echo "Hello, World" | RUST_LOG=tower_http=trace cargo run --bin tonic-key-value-store -- -p 3000 set -k foo
    
    ### Getting a value
    RUST_LOG=tower_http=trace cargo run --bin tonic-key-value-store -- -p 3000 get -k foo
    
    ### Subscribing to new keys
    RUST_LOG=tower_http=trace cargo run --bin tonic-key-value-store -- -p 3000 subscribe
  3. Explore Tower HTTP integration examples

    main

    The repository contains several examples demonstrating how to integrate Tower HTTP with different web frameworks and protocols:

    • Axum: See axum-key-value-store for an HTTP API implementation using the Axum framework.
    • Tonic: See tonic-key-value-store for a gRPC API and client implementation using Tonic.
    • Warp: See warp-key-value-store for an HTTP API implementation using the Warp framework.
  4. Overview of tower-http

    main

    tower-http is a library providing HTTP-specific middleware and utilities built on top of the tower ecosystem.

    All middleware uses the http and http-body crates as their core abstractions. This ensures compatibility with any HTTP library or framework that implements these crates, including hyper, tonic, and warp.

  5. How response classification works in tower-http

    main

    Response classification is the process of determining whether an HTTP response (or an error) should be treated as a success or a failure. This is primarily used by middleware like logging, metrics, or retry policies to react to the outcome of a request.

    There are three main levels of abstraction:

    1. ClassifyResponse: The core trait. It attempts to classify a response immediately (ClassifiedResponse::Ready) or indicates that classification must wait until the response body stream ends (ClassifiedResponse::RequiresEos).
    2. ClassifyEos: Used for streaming responses (like gRPC) where the final status might only be available in the response trailers at the End of Stream (EOS).
    3. MakeClassifier: A factory trait used when the classification logic depends on information from the incoming Request (e.g., the URI or HTTP method).

    If your classifier does not depend on the request, you can use SharedClassifier to wrap a ClassifyResponse implementation into a MakeClassifier.

    // Example of a simple classifier that only cares about errors
    #[derive(Clone, Copy)]
    struct MyClassifier;
    
    impl ClassifyResponse for MyClassifier {
        type FailureClass = String;
        type ClassifyEos = NeverClassifyEos<Self::FailureClass>;
    
        fn classify_response<B>(self, _res: &Response<B>) -> ClassifiedResponse<Self::FailureClass, Self::ClassifyEos> {
            ClassifiedResponse::Ready(Ok(()))
        }
    
        fn classify_error<E>(self, error: &E) -> Self::FailureClass
        where E: std::fmt::Display + 'static,
        {
            error.to_string()
        }
    }
    
    // Use SharedClassifier to turn it into a MakeClassifier
    let make_classifier = SharedClassifier::new(MyClassifier);
  6. Handle conditional requests with `ServeFile`

    main

    ServeFile supports standard HTTP conditional request headers to optimize bandwidth and caching:

    • If-Modified-Since: If the file has not been modified since the date provided in the header, the service returns 304 Not Modified and an empty body.
    • If-Unmodified-Since: If the file has been modified since the date provided, the service returns 425 Precondition Failed.

    It also automatically includes the Last-Modified header in successful responses.

  7. How to use ServiceBuilderExt to configure HTTP middleware

    main

    The ServiceBuilderExt trait extends tower::ServiceBuilder with specialized methods for adding tower-http middleware. This allows you to chain HTTP-specific layers (like compression, tracing, or header manipulation) directly onto a ServiceBuilder instance.

    Note that many of these methods are gated by Cargo features (e.g., compression-gzip, trace, set-header). Ensure the required feature is enabled in your Cargo.toml to access the corresponding method.

    use http::{Request, Response, header::HeaderName};
    use bytes::Bytes;
    use http_body_util::Full;
    use std::{time::Duration, convert::Infallible};
    use tower::{ServiceBuilder, ServiceExt, Service};
    use tower_http::ServiceBuilderExt;
    
    async fn handle(request: Request<Full<Bytes>>) -> Result<Response<Full<Bytes>>, Infallible> {
        Ok(Response::new(Full::default()))
    }
    
    #[tokio::main]
    async fn main() {
        let service = ServiceBuilder::new()
            // Methods from tower
            .timeout(Duration::from_secs(30))
            // Methods from tower-http
            .trace_for_http()
            .propagate_header(HeaderName::from_static("x-request-id"))
            .service_fn(handle);
        
        let mut service = service;
        service.ready().await.unwrap().call(Request::new(Full::default())).await.unwrap();
    }
  8. How body timeouts work in Tower HTTP

    main

    A TimeoutBody is a wrapper around an http_body::Body that enforces a timeout on the interval between consecutive data frames.

    Key Behaviors:

    • Inactivity Timeout: The timeout is enforced between consecutive Frame polls. It resets every time a new frame is successfully produced.
    • Total Duration: The total time to produce a full body can exceed the timeout duration, provided that no single interval between frames exceeds the specified Duration.
    • Error Trigger: If the underlying body does not produce a requested data frame within the timeout period, it returns a TimeoutError.

    Difference from Timeout middleware:

    • Timeout (from crate::timeout::Timeout) applies to the entire request future and does not reset as bytes are processed.
    • TimeoutBody is specifically for asynchronous body streaming, which occurs outside the standard Tower service stack's future lifecycle.
    use http_body_util::Full;
    use bytes::Bytes;
    use std::time::Duration;
    use tower::ServiceBuilder;
    use tower_http::timeout::RequestBodyTimeoutLayer;
    
    // Example of applying a body timeout layer to a service
    let svc = ServiceBuilder::new()
        .layer(RequestBodyTimeoutLayer::new(Duration::from_secs(30)))
        .service_fn(handle);