Tower

repository·master·Indexed 26 days ago

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

A library of modular and reusable components for building robust networking clients and servers in Rust. It is protocol-agnostic and centered around the `Service` trait, which represents an asynchronous function mapping a request to a response. The ecosystem includes `tower-layer` for decorating services, `tower-service` for the core abstraction, and `tower-test` for simulating service interactions. While the core crate is not `no_std` compatible, `tower-layer` and `tower-service` (requiring `alloc`) are.

Tokens
13.7K
Snippets
21
Records
60
Agent score
87%

What's inside tower

  1. Overview of Tower

    master

    Tower is a library of modular and reusable components designed for building robust networking clients and servers. It is protocol-agnostic and operates primarily around a request/response pattern.

    Note: If your protocol is entirely stream-based, Tower may not be the appropriate fit.

  2. Use Tower Layer to decorate Services

    master

    The Layer trait in tower-layer allows you to write reusable components that decorate a Tower Service. These layers can transform either the request or the response. Because they are generic, they can be applied to services operating on different protocols and used on both the client and server sides of a network transaction.

    tower-layer is no_std compatible.

  3. Understand the Service trait

    master

    The Service trait is the foundational interface for the Tower ecosystem. It represents an asynchronous function that maps a request to a response.

    Key concepts:

    • Interface: A Service takes a Request and returns a future that resolves to a Result<Response, Error>.
    • Usage: Services can represent both clients (which use an instance of a service) and servers (which implement the service trait).
    • Middleware: Middleware is implemented by creating a Service that wraps another Service, allowing for request modification or response handling.
  4. Use tower-test for Service testing

    master
    The tower-test crate provides utilities designed to help developers write tests for both client and server implementations of the Service trait. It simplifies the process of mocking or simulating service interactions to ensure correctness in asynchronous networking or request/response logic.
  5. Create a custom Response Future for middleware

    master

    When a middleware needs to modify the behavior of a response (e.g., adding a timeout), you cannot simply return the inner service's future. Instead, you must implement a custom Future that wraps the inner future and any other auxiliary futures (like a timer).

    To avoid the overhead of Box<dyn Future>, implement the Future trait manually on a custom struct. Because polling a field of a pinned struct requires 'pin projection', it is highly recommended to use the pin-project crate. This allows you to safely obtain Pin<&mut Field> from a Pin<&mut Struct>.

    use pin_project::pin_project;
    use std::{pin::Pin, future::Future, task::{Context, Poll}};
    use tokio::time::Sleep;
    
    #[pin_project]
    pub struct ResponseFuture<F> {
        #[pin]
        response_future: F,
        #[pin]
        sleep: Sleep,
    }
    
    impl<F, Response, Error> Future for ResponseFuture<F>
    where
        F: Future<Output = Result<Response, Error>>,
    {
        type Output = Result<Response, Error>;
    
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            let this = self.project();
    
            // 1. Poll the inner response future
            match this.response_future.poll(cx) {
                Poll::Ready(result) => return Poll::Ready(result),
                Poll::Pending => {}
            }
    
            // 2. Poll the auxiliary future (e.g., a timer)
            match this.sleep.poll(cx) {
                Poll::Ready(()) => {
                    // Handle timeout/auxiliary completion
                    todo!("Return error")
                }
                Poll::Pending => {}
            }
    
            // 3. Neither is ready
            Poll::Pending
        }
    }
  6. Handle errors in middleware using BoxError

    master

    When building middleware, you often need to return an error type that can accommodate both the inner service's error and your own middleware-specific errors (e.g., a TimeoutError).

    Tower uses a boxed trait object pattern to solve this. Instead of complex nested enums, define your middleware's error type to be BoxError, which is a type alias for Box<dyn std::error::Error + Send + Sync>.

    Advantages:

    • Stability: Changing the order of middleware doesn't change the final error type.
    • Constant Size: The error type has a fixed size on the stack regardless of how many middleware layers are applied.
    • Ease of use: You can extract specific errors using error.downcast_ref::<YourErrorType>() instead of massive match statements.

    Requirements: To use this pattern, your middleware's Service implementation must require that the inner service's error type implements Into<BoxError>.

    // Use this type alias for middleware errors
    type BoxError = Box<dyn std::error::Error + Send + Sync>;
    
    // Ensure your Service implementation has this bound
    impl<S, Request> Service<Request> for MyMiddleware<S>
    where
        S: Service<Request>,
        S::Error: Into<BoxError>,
    {
        type Response = S::Response;
        type Error = BoxError;
        type Future = MyMiddlewareFuture<S::Future>;
    
        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
            self.inner.poll_ready(cx).map_err(Into::into)
        }
    
        fn call(&mut self, request: Request) -> Self::Future {
            // ...
        }
    }
  7. Learn the fundamentals of the Service trait

    master
    If you are new to Tower, start by understanding the design of the fundamental Service trait. This guide walks through how the trait could be designed from scratch to help you grasp the absolute basics of the library's core abstraction.
  8. Implement a Service-based middleware from scratch

    master

    To build a middleware in Tower, you typically wrap an inner Service in a new struct. This struct should implement the Service trait, allowing it to intercept requests and responses.

    Key requirements for a robust middleware struct:

    • Implement Clone: Services should be clonable so that &mut self from Service::call can be converted into an owned self if needed for the response future.
    • Implement Debug: For better observability.
    • Constructor: Provide a new(inner: S, ...) method. It is recommended to omit trait bounds on the generic parameter S in the constructor, even if you expect it to implement Service later.
    • poll_ready: Forward the readiness check to the inner service to respect backpressure.
    • call: Intercept the request and return a custom Future that wraps the inner service's future.
    use std::time::Duration;
    use tower::Service;
    use std::task::{Context, Poll};
    
    #[derive(Debug, Clone)]
    struct MyMiddleware<S> {
        inner: S,
        config: Duration,
    }
    
    impl<S> MyMiddleware<S> {
        pub fn new(inner: S, config: Duration) -> Self {
            MyMiddleware { inner, config }
        }
    }
    
    impl<S, Request> Service<Request> for MyMiddleware<S>
    where
        S: Service<Request>,
    {
        type Response = S::Response;
        type Error = S::Error;
        type Future = S::Future; // Or a custom Future type
    
        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
            self.inner.poll_ready(cx)
        }
    
        fn call(&mut self, request: Request) -> Self::Future {
            self.inner.call(request)
        }
    }
  9. Learn how to build middleware from scratch

    master
    To understand how to implement custom middleware, follow the guide that walks through building the Timeout middleware as it exists in Tower today. This provides a deep dive into the implementation patterns used for Tower middleware.
  10. Implement a Service middleware from scratch

    master

    The standard pattern for implementing Tower middleware is to create a struct that wraps an inner Service and returns a custom Future that wraps the inner service's Future.

    Steps to implement:

    1. Define the Middleware struct: Holds the inner service and any configuration (like a Duration for timeouts).
    2. Implement Service for the struct:
      • poll_ready should call the inner service's poll_ready and map the error using .map_err(Into::into) if using BoxError.
      • call should call the inner service's call and return your custom ResponseFuture.
    3. Define the ResponseFuture struct: Use pin_project to manage the pinning of the inner future and any other state (like a timer).
    4. Implement Future for ResponseFuture:
      • In poll, poll the inner future first. If it returns Ready(Ok(res)), return the result.
      • If the inner future returns an error, map it using .map_err(Into::into).
      • If the inner future is Pending, poll your middleware's internal state (e.g., a Sleep timer). If the timer expires, return your middleware's specific error.
    use pin_project::pin_project;
    use std::task::{Context, Poll};
    use std::pin::Pin;
    use tower::Service;
    
    #[derive(Debug, Clone)]
    struct MyMiddleware<S> {
        inner: S,
        // configuration...
    }
    
    impl<S, Request> Service<Request> for MyMiddleware<S>
    where
        S: Service<Request>,
        S::Error: Into<BoxError>,
    {
        type Response = S::Response;
        type Error = BoxError;
        type Future = MyMiddlewareFuture<S::Future>;
    
        fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
            self.inner.poll_ready(cx).map_err(Into::into)
        }
    
        fn call(&mut self, request: Request) -> Self::Future {
            let response_future = self.inner.call(request);
            MyMiddlewareFuture { response_future /* ... */ }
        }
    }
    
    #[pin_project]
    struct MyMiddlewareFuture<F> {
        #[pin]
        response_future: F,
        // other state like sleep...
    }
    
    impl<F, Response, Error> Future for MyMiddlewareFuture<F>
    where
        F: Future<Output = Result<Response, Error>>,
        Error: Into<BoxError>,
    {
        type Output = Result<Response, BoxError>;
    
        fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
            let this = self.project();
    
            match this.response_future.poll(cx) {
                Poll::Ready(result) => {
                    return Poll::Ready(result.map_err(Into::into));
                }
                Poll::Pending => {}
            }
    
            // Check middleware logic (e.g. timeout)
            // ...
    
            Poll::Pending
        }
    }