Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions examples/custom-future-multiple-bodies/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "custom-future-multiple-bodies"
version = "0.1.0"
authors = ["Tower Maintainers <team@tower-rs.com>"]
edition = "2021"
publish = false
license = "MIT"

[dependencies]
bytes = "1.12.0"
http = "1.4.2"
http-body-util = "0.1.3"
hyper = "1.10.1"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hyper is not needed here

pin-project-lite = "0.2.17"
tokio = { version = "1.32.0", features = ["full"] }
tower = { version = "0.5", features = ["full"] }
14 changes: 14 additions & 0 deletions examples/custom-future-multiple-bodies/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Custom future with multiple bodies

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mention that the Either is convenient but loses the concrete error type, and that you can refer to the tower_http::limit wrapper to preserve the type?

@Reza-Darius Reza-Darius Aug 1, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont quite understand what this means, doesnt the body stay fully preserved including the error?


This example serves to demonstrate how to build a service that returns a
response in case of an event (in this instance, a missing HTTP header) while
leaving the inner service's response untouched.

This requires wrapping the response body if the user wishes to leave the inner
service's body untouched.

## Running the example

```
cargo run -p custom-future-multiple-bodies
```
122 changes: 122 additions & 0 deletions examples/custom-future-multiple-bodies/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use bytes::Bytes;
use http::{Request, Response, StatusCode};
use http_body_util::{Either, Full};
use pin_project_lite::pin_project;
use std::{
future::Future,
pin::Pin,
task::{ready, Context, Poll},
};
use tower::Service;

use http_body_util::BodyExt;
use std::error::Error;
use tower::ServiceBuilder;
use tower::ServiceExt;

// think of Either as an enum that implements Body if both arms implement Body
// this allows us to combine multple bodies
type ResponseBody<B> = Either<B, Full<Bytes>>;

// some helper to go along with it
fn map_resp<B>(resp: Response<B>) -> Response<ResponseBody<B>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this really carries its weight as a helper

resp.map(|body| Either::Left(body))
}

fn new_err_resp<B>(status: StatusCode, body: &'static str) -> Response<ResponseBody<B>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: maybe call it rejection?

Response::builder()
.status(status)
.body(Either::Right(Full::from(body)))
.unwrap()
}

#[derive(Clone)]
pub struct RequireHeader<S> {
inner: S,
header_name: &'static str,
}

impl<S> RequireHeader<S> {
pub fn new(inner: S, header_name: &'static str) -> Self {
Self { inner, header_name }
}
}

impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for RequireHeader<S>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably add ResBody: http_body::Body<Data = Bytes> here, yeah, at the cost of taking the http-body = "1" dependency. That moves any error out to the middleware layer, which is clearer.

{
type Response = Response<ResponseBody<ResBody>>;
type Error = S::Error;
type Future = RequireHeaderFuture<S::Future>;

fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}

fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
if !req.headers().contains_key(self.header_name) {
return RequireHeaderFuture::HeaderNotFound;
}
RequireHeaderFuture::Ok {
fut: self.inner.call(req),
}
}
}

pin_project! {
#[project = EnumProj]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ResponseFutureProj to match the repo conventions?

pub enum RequireHeaderFuture<F> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth a doc comment pointing to how eg limit/future.rs encapsulate these types for semver safety, if used in a library? This seems fine for application usage.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't particularly mind demonstrating the encapsulated pattern in general, it's not particularly more verbose, up to you.

Ok{ #[pin] fut: F },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Future to match the repo conventions?

HeaderNotFound,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: MissingHeader to match the repo conventions?

}
}

impl<F, E, ResBody> Future for RequireHeaderFuture<F>
where
F: Future<Output = Result<Response<ResBody>, E>>,
{
type Output = Result<Response<ResponseBody<ResBody>>, E>;

fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project() {
EnumProj::Ok { fut } => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More readable as:

  fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
      let res = match self.project() {
              // `?` propagates an inner-service error. to turn it into a response
              // instead, replace `?` with
              //     .unwrap_or_else(|_| rejection(StatusCode::BAD_GATEWAY, "..."))
              // to convert every error, or `.or_else(..)` to convert only some and
              // keep propagating the rest.
          ResFutProj::Future { future } => ready!(future.poll(cx))?.map(Either::Left),
          ResFutProj::MissingHeader => rejection(StatusCode::BAD_REQUEST, "missing header"),
      };

      Poll::Ready(Ok(res))
  

let res = ready!(fut.poll(cx));

// we use our helper to unify the response body types
// its also possible to return a custom error response here if the inner service failed
Poll::Ready(res.map(|resp| map_resp(resp)))
}
EnumProj::HeaderNotFound => {
Poll::Ready(Ok(new_err_resp(StatusCode::BAD_REQUEST, "missing header")))
}
}
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need the unwrap here since we don't have Box<dyn Error>: From<Box<dyn Error + Send + Sync>>.

I'd suggest instead widening the signature to Result<(), Box<dyn Error + Send + Sync>> (or just Result<(), tower::BoxError>, which is equivalent) to avoid both the unwrap and the need to coerce explicitly to work around the missing From.

let inner_service = tower::service_fn(|_req: Request<Full<Bytes>>| async {
Ok::<_, std::convert::Infallible>(Response::new(Full::new(Bytes::from("Hello, World!"))))
});

let mut service = ServiceBuilder::new()
.layer_fn(|inner| RequireHeader::new(inner, "x-api-key"))
.service(inner_service);

let req_bad = Request::builder().body(Full::<Bytes>::default()).unwrap();
let res_bad = service.ready().await?.call(req_bad).await?;
assert_eq!(res_bad.status(), StatusCode::BAD_REQUEST);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably these assertions should be in test modules? That matches the other examples.

I'd also probably add some println!s so that cargo run -p custom-future-multiple-bodies prints something, given the README guides the user to run it.


let body = res_bad.into_body().collect().await.unwrap();
assert_eq!(body.to_bytes(), "missing header");

let req_good = Request::builder()
.header("x-api-key", "secret")
.body(Full::<Bytes>::default())
.unwrap();
let res_good = service.ready().await?.call(req_good).await?;
assert_eq!(res_good.status(), StatusCode::OK);

Ok(())
}
Loading