Use the tower crate by defining a Service for your core logic, wrapping it with Layers for cross-cutting concerns, and stacking them with ServiceBuilder.
use tower::{Service, ServiceBuilder, ServiceExt, layer::LayerFn};
use tower_http::trace::TraceLayer;
use http::{Request, Response, StatusCode};
use std::convert::Infallible;
// 1. Define your core service
struct MyService;
impl Service<Request<String>> for MyService {
type Response = Response<String>;
type Error = Infallible;
type Future = std::future::Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(
&mut self,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Result<(), Self::Error>> {
std::task::Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<String>) -> Self::Future {
let body = format!("Received: {}", req.into_body());
std::future::ready(Ok(Response::new(body)))
}
}
// 2. Build the stack
let mut service = ServiceBuilder::new()
.layer(TraceLayer::new_for_http()) // Middleware
.service(MyService);
// 3. Call the service
let req = Request::new("Hello".to_string());
let response = service.ready().await?.call(req);