Skip to content

Add propagate_extensions middleware#425

Open
bassmanitram wants to merge 3 commits into
tower-rs:mainfrom
bassmanitram:propagate_extension
Open

Add propagate_extensions middleware#425
bassmanitram wants to merge 3 commits into
tower-rs:mainfrom
bassmanitram:propagate_extension

Conversation

@bassmanitram
Copy link
Copy Markdown

@bassmanitram bassmanitram commented Oct 23, 2023

(See issue #423 )

This middleware is intended to be used as a way of sharing state between "pre service" layers/filters etc and those that run "post service", particularly when the service itself is unaware of that state.

This is in fact, almost a verbatim copy of propagate_header, replacing the Header semantics with those required by http::Extensions.

For ease of implementation it removes the state from the request before the inner service is invoked, and inserts the state into the response when the response is ready.

It does not concern itself with the initial insert of the state into the request since that is covered by an existing middleware, and may also be achieved by other means in one of the "pre service" components.

Because Extensions are managed by type, both the layer and the underlying service have to be generic over the type of the user's state object - and that type has to be cloneable as well as Send and Sync. In order to keep the compiler happy, both the service and the layer have a phantom data field. I am not 100% sure that this is necessary, but it does seem to be one of the few safe code instances where it probably is.

Copy link
Copy Markdown
Member

@jlizen jlizen left a comment

Choose a reason for hiding this comment

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

@bassmanitram - Big ++ for this being an important feature, thanks for cutting this PR. Sorry it took a while to get to. I'm on board with the general approach (minus the removal semantics) though have some feedback about specifics.

Let me know if you no longer have time to work on this, no problem, I'm glad to follow up with a revision on top.

CompressionBody::new(BodyInner::zstd(WrapBody::new(body, self.quality)))
}
#[cfg(feature = "fs")]
#[allow(unreachable_patterns)]
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.

Let's pull this out of this PR, no real relationship. Happy to review a separate one if still needed.

use tower_layer::Layer;
use tower_service::Service;

#[allow(unused_imports)]
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.

Let's only include the one we need rather than allowing unused (looks like only debug!)?

Let's also gate tracing imports/usage behind the trace feature and mention that you can enable it to get the event emissions.

}

fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
let extension: Option<X> = req.extensions_mut().remove();
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.

Let's clone it rather than remove it, and document it as such. (If you think it is useful to have an optional flag to remove instead of clone, I'm fine with it, but I think it is the wrong default). That will bring us in line with propagate_headers and be less footgun-prone.

Extensions anyway require Clone so it's not a meaningful change in bounds, though it would take tweaking some bounds specified in this change.

}
}

#[cfg(test)]
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.

Also need a test for the case where the targeted extension is missing

/// and this middleware will ensure that it is available to the post service mappers via the Response extensions.
///
/// See the [module docs](crate::propagate_extension) for more details.
#[derive(Clone, Debug)]
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 should derive Default as well

//! use tower_http::add_extension::AddExtensionLayer;
//! use tower_http::propagate_extension::PropagateExtensionLayer;
//! use tower_http::ServiceBuilderExt;
//! use hyper::Body;
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 should move to http-body-util and Full<Bytes>, the rest of the codebase has shifted since you opened this.

use tower::{Service, ServiceExt, ServiceBuilder};
use crate::add_extension::AddExtensionLayer;
use crate::builder::ServiceBuilderExt;
use hyper::Body;
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 should move to http-body-util and Full<Bytes>, the rest of the codebase has shifted since you opened this.

//! # }
//! ```

use futures_util::ready;
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 have std::task::ready available under our MSRV, let's use that.

let request = Request::builder().body(Body::empty()).expect("Expected an empty body");

// Call the service.
let ready = futures::executor::block_on(svc.ready()).expect("Expected the service to be ready");
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.

let's use #[tokio::test] in the tests instead of futures::executor::block_on for consistency with the rest of the codebase

@bassmanitram
Copy link
Copy Markdown
Author

bassmanitram commented May 25, 2026 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants