diff --git a/tower-http/src/set_header/mod.rs b/tower-http/src/set_header/mod.rs index 396527ef..ed729c5d 100644 --- a/tower-http/src/set_header/mod.rs +++ b/tower-http/src/set_header/mod.rs @@ -10,7 +10,7 @@ pub mod response; #[doc(inline)] pub use self::{ request::{SetRequestHeader, SetRequestHeaderLayer}, - response::{SetResponseHeader, SetResponseHeaderLayer}, + response::{HeaderMetadata, SetResponseHeader, SetResponseHeaderLayer}, }; /// Trait for producing header values. diff --git a/tower-http/src/set_header/response/mod.rs b/tower-http/src/set_header/response/mod.rs new file mode 100644 index 00000000..4eb5354e --- /dev/null +++ b/tower-http/src/set_header/response/mod.rs @@ -0,0 +1,173 @@ +//! Middleware for setting headers on HTTP responses. +//! +//! This module provides middleware for setting one or more headers on HTTP responses, either with fixed values or values determined dynamically from the response. +//! +//! # Single Header +//! +//! Use [`SetResponseHeaderLayer`] and [`SetResponseHeader`] to set a single header. The header value can be a fixed value or computed dynamically using a closure. See [`crate::set_header::MakeHeaderValue`] for details. +//! +//! ## Example: Fixed Value +//! +//! ``` +//! use http::{Request, Response, header::{self, HeaderValue}}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::set_header::SetResponseHeaderLayer; +//! use http_body_util::Full; +//! use bytes::Bytes; +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let render_html = tower::service_fn(|request: Request>| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! +//! let mut svc = ServiceBuilder::new() +//! .layer( +//! SetResponseHeaderLayer::if_not_present( +//! header::CONTENT_TYPE, +//! HeaderValue::from_static("text/html"), +//! ) +//! ) +//! .service(render_html); +//! +//! let request = Request::new(Full::default()); +//! +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["content-type"], "text/html"); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Example: Dynamic Value +//! +//! ``` +//! use http::{Request, Response, header::{self, HeaderValue}}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::set_header::SetResponseHeaderLayer; +//! use bytes::Bytes; +//! use http_body_util::Full; +//! use http_body::Body as _; // for `Body::size_hint` +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let render_html = tower::service_fn(|request: Request>| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(Full::from("1234567890"))) +//! # }); +//! +//! let mut svc = ServiceBuilder::new() +//! .layer( +//! SetResponseHeaderLayer::overriding( +//! header::CONTENT_LENGTH, +//! |response: &Response>| { +//! if let Some(size) = response.body().size_hint().exact() { +//! Some(HeaderValue::from_str(&size.to_string()).unwrap()) +//! } else { +//! None +//! } +//! } +//! ) +//! ) +//! .service(render_html); +//! +//! let request = Request::new(Full::default()); +//! +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["content-length"], "10"); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Multiple Headers +//! +//! Use [`SetMultipleResponseHeadersLayer`] and [`SetMultipleResponseHeader`] to set multiple headers at once. Each header can have a fixed value or be computed dynamically. +//! +//! ## Example: Multiple Fixed Values +//! +//! ``` +//! use http::{Request, Response, header::{self, HeaderValue}}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::set_header::response::{SetMultipleResponseHeadersLayer, HeaderMetadata}; +//! use http_body_util::Full; +//! use bytes::Bytes; +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let render_html = tower::service_fn(|request: Request>| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) +//! # }); +//! +//! let mut svc = ServiceBuilder::new() +//! .layer( +//! SetMultipleResponseHeadersLayer::overriding(vec![ +//! (header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into(), +//! (header::CACHE_CONTROL, HeaderValue::from_static("no-cache")).into(), +//! ]) +//! ) +//! .service(render_html); +//! +//! let request = Request::new(Full::default()); +//! +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["content-type"], "text/html"); +//! assert_eq!(response.headers()["cache-control"], "no-cache"); +//! # Ok(()) +//! # } +//! ``` +//! +//! ## Example: Multiple Dynamic Values +//! +//! ``` +//! use http::{Request, Response, header::{self, HeaderValue}}; +//! use tower::{Service, ServiceExt, ServiceBuilder}; +//! use tower_http::set_header::response::{SetMultipleResponseHeadersLayer, HeaderMetadata}; +//! use bytes::Bytes; +//! use http_body_util::Full; +//! use http_body::Body as _; // for `Body::size_hint` +//! +//! # #[tokio::main] +//! # async fn main() -> Result<(), Box> { +//! # let render_html = tower::service_fn(|request: Request>| async move { +//! # Ok::<_, std::convert::Infallible>(Response::new(Full::from("1234567890"))) +//! # }); +//! +//! let mut svc = ServiceBuilder::new() +//! .layer( +//! SetMultipleResponseHeadersLayer::overriding(vec![ +//! (header::CONTENT_LENGTH, |response: &Response>| { +//! if let Some(size) = response.body().size_hint().exact() { +//! Some(HeaderValue::from_str(&size.to_string()).unwrap()) +//! } else { +//! None +//! } +//! }).into(), +//! ]) +//! ) +//! .service(render_html); +//! +//! let request = Request::new(Full::default()); +//! +//! let response = svc.ready().await?.call(request).await?; +//! +//! assert_eq!(response.headers()["content-length"], "10"); +//! # Ok(()) +//! # } +//! ``` +//! +//! # Modes +//! +//! - `overriding`: If a previous value exists for the same header, it is removed and replaced with the new value. +//! - `appending`: The new header is always added, preserving any existing values. If previous values exist, the header will have multiple values. +//! - `if_not_present`: If a previous value exists for the header, the new value is not inserted. +//! +//! See [`SetResponseHeaderLayer`], [`SetResponseHeader`], [`SetMultipleResponseHeadersLayer`], and [`SetMultipleResponseHeader`] for more details. + +mod multiple_header; +mod single_header; + +pub use multiple_header::{ + HeaderMetadata, SetMultipleResponseHeader, SetMultipleResponseHeadersLayer, +}; +pub use single_header::{SetResponseHeader, SetResponseHeaderLayer}; diff --git a/tower-http/src/set_header/response/multiple_header.rs b/tower-http/src/set_header/response/multiple_header.rs new file mode 100644 index 00000000..aaa9a986 --- /dev/null +++ b/tower-http/src/set_header/response/multiple_header.rs @@ -0,0 +1,533 @@ +//! Set multiple headers on the response. +//! +//! See the root [`crate::set_header::response`] module for full documentation and usage examples. +//! +use http::{header::HeaderName, HeaderValue, Request, Response}; +use pin_project_lite::pin_project; +use std::{ + fmt, + future::Future, + pin::Pin, + task::{ready, Context, Poll}, +}; +use tower_layer::Layer; +use tower_service::Service; + +use crate::set_header::{InsertHeaderMode, MakeHeaderValue}; + +/// A trait that combines MakeHeaderValue and Clone capability for trait objects. +trait CloneableMakeHeaderValue: MakeHeaderValue + Send + Sync { + fn clone_box(&self) -> Box>; +} + +impl CloneableMakeHeaderValue for M +where + M: MakeHeaderValue + Clone + Send + Sync + 'static, +{ + fn clone_box(&self) -> Box> { + Box::new(self.clone()) + } +} + +/// A "Bridge" struct that allows for trait object-based header value generation. +struct BoxedMakeHeaderValue(Box>); + +impl BoxedMakeHeaderValue { + /// Create a new BoxedMakeHeaderValue from any maker that implements MakeHeaderValue and Clone. + fn new(maker: M) -> Self + where + M: MakeHeaderValue + Clone + Send + Sync + 'static, + { + Self(Box::new(maker)) + } +} + +impl Clone for BoxedMakeHeaderValue { + fn clone(&self) -> Self { + Self(self.0.clone_box()) + } +} + +impl MakeHeaderValue for BoxedMakeHeaderValue { + fn make_header_value(&mut self, message: &T) -> Option { + self.0.make_header_value(message) + } +} + +impl fmt::Debug for BoxedMakeHeaderValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BoxedMakeHeaderValue").finish() + } +} + +/// Metadata describing a response header to be set by [`SetMultipleResponseHeadersLayer`] or [`SetMultipleResponseHeader`]. +#[derive(Clone, Debug)] +pub struct HeaderMetadata { + /// The name of the header to set. + header_name: HeaderName, + /// The value or value factory for the header. + make: BoxedMakeHeaderValue, +} + +impl HeaderMetadata { + /// Create a new HeaderMetadata with the given header name and value factory. + fn new + Clone + 'static + Send + Sync>( + header_name: HeaderName, + make: M, + ) -> Self { + Self { + header_name, + make: BoxedMakeHeaderValue::new(make), + } + } + + /// Convert this metadata into a [`HeaderInsertionConfig`] with the given insertion mode. + fn build_config(self, mode: InsertHeaderMode) -> HeaderInsertionConfig { + HeaderInsertionConfig { + header_name: self.header_name, + make: self.make, + mode, + } + } +} + +impl From<(HeaderName, M)> for HeaderMetadata +where + M: MakeHeaderValue + Clone + 'static + Send + Sync, +{ + fn from((header_name, make): (HeaderName, M)) -> Self { + HeaderMetadata::new(header_name, make) + } +} + +struct HeaderInsertionConfig { + header_name: HeaderName, + make: BoxedMakeHeaderValue, + mode: InsertHeaderMode, +} + +impl Clone for HeaderInsertionConfig +where + BoxedMakeHeaderValue: Clone, +{ + fn clone(&self) -> Self { + Self { + header_name: self.header_name.clone(), + make: self.make.clone(), + mode: self.mode, + } + } +} + +impl fmt::Debug for HeaderInsertionConfig { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("HeaderInsertionConfig") + .field("header_name", &self.header_name) + .field("mode", &self.mode) + .field("make", &"BoxedMakeHeaderValue") + .finish() + } +} + +/// Layer that applies [`SetMultipleResponseHeader`] which adds multiple response headers. +/// +/// See [`SetMultipleResponseHeader`] for more details. +pub struct SetMultipleResponseHeadersLayer { + headers: Vec>, +} + +impl fmt::Debug for SetMultipleResponseHeadersLayer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SetMultipleResponseHeadersLayer") + .field("headers", &self.headers) + .finish() + } +} + +impl SetMultipleResponseHeadersLayer { + /// Create a new [`SetMultipleResponseHeadersLayer`] that overrides any existing values for the same header. + /// + /// If any previous value exists for the same header, it is removed and replaced with the new matching header value. + pub fn overriding(metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::Override)) + .collect(); + + Self::new(headers) + } + + /// Create a new [`SetMultipleResponseHeadersLayer`] that appends header values. + /// + /// The new header is always added, preserving any existing values. If previous values exist, the header will have multiple values. + pub fn appending(metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::Append)) + .collect(); + + Self::new(headers) + } + + /// Create a new [`SetMultipleResponseHeadersLayer`] that only inserts if the header is not already present. + /// + /// If a previous value exists for the header, the new value is not inserted. + pub fn if_not_present(metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::IfNotPresent)) + .collect(); + + Self::new(headers) + } + + /// Internal constructor for a new [`SetMultipleResponseHeadersLayer`] from a list of headers. + fn new(headers: Vec>) -> Self { + Self { headers } + } +} + +impl Layer for SetMultipleResponseHeadersLayer { + type Service = SetMultipleResponseHeader; + + fn layer(&self, inner: S) -> Self::Service { + SetMultipleResponseHeader { + inner, + headers: self.headers.clone(), + } + } +} + +/// Middleware that sets multiple headers on the response. + +#[derive(Clone)] +pub struct SetMultipleResponseHeader { + inner: S, + headers: Vec>, +} + +impl SetMultipleResponseHeader { + /// Create a new [`SetMultipleResponseHeader`] that overrides any existing values for the same header. + /// + /// If a previous value exists for the same header, it is removed and replaced with the new header value. + pub fn overriding(inner: S, metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::Override)) + .collect(); + + Self::new(inner, headers) + } + + /// Create a new [`SetMultipleResponseHeader`] that appends header values. + /// + /// The new header is always added, preserving any existing values. If previous values exist, the header will have multiple values. + pub fn appending(inner: S, metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::Append)) + .collect(); + + Self::new(inner, headers) + } + + /// Create a new [`SetMultipleResponseHeader`] that only inserts if the header is not already present. + /// + /// If a previous value exists for the header, the new value is not inserted. + pub fn if_not_present(inner: S, metadata: Vec>) -> Self { + let headers: Vec> = metadata + .into_iter() + .map(|m| m.build_config(InsertHeaderMode::IfNotPresent)) + .collect(); + + Self::new(inner, headers) + } + + /// Internal constructor for a new [`SetMultipleResponseHeader`] from an inner service and a list of headers. + fn new(inner: S, headers: Vec>) -> Self { + Self { inner, headers } + } + + define_inner_service_accessors!(); +} + +impl fmt::Debug for SetMultipleResponseHeader +where + S: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SetMultipleResponseHeader") + .field("inner", &self.inner) + .field("headers", &self.headers) + .finish() + } +} + +impl Service> + for SetMultipleResponseHeader> +where + S: Service, Response = Response>, +{ + type Response = S::Response; + type Error = S::Error; + type Future = ResponseFuture>; + + #[inline] + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + /// Call the inner service and apply all configured headers to the response. + fn call(&mut self, req: Request) -> Self::Future { + ResponseFuture { + future: self.inner.call(req), + headers: self.headers.clone(), + } + } +} + +pin_project! { + /// Response future for [`SetMultipleResponseHeader`]. + #[derive(Debug)] + pub struct ResponseFuture { + #[pin] + future: F, + headers: Vec>, + } +} + +impl Future for ResponseFuture> +where + F: Future, E>>, +{ + type Output = F::Output; + + /// Polls the inner future and applies all configured headers to the response before returning it. + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let mut res = ready!(this.future.poll(cx)?); + + for header in this.headers { + header + .mode + .apply(&header.header_name, &mut res, &mut header.make); + } + + Poll::Ready(Ok(res)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_helpers::Body; + use http::{header, HeaderValue}; + use std::convert::Infallible; + use tower::{service_fn, ServiceExt}; + + #[tokio::test] + async fn test_override_mode() { + let svc = SetMultipleResponseHeader::overriding( + service_fn(|_req: Request| async { + let res = Response::builder() + .header(header::CONTENT_TYPE, "good-content") + .body(Body::empty()) + .unwrap(); + Ok::<_, Infallible>(res) + }), + vec![(header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into()], + ); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + + let mut values = res.headers().get_all(header::CONTENT_TYPE).iter(); + assert_eq!(values.next().unwrap(), "text/html"); + assert_eq!(values.next(), None); + } + + #[tokio::test] + async fn test_append_mode() { + let svc = SetMultipleResponseHeader::appending( + service_fn(|_req: Request| async { + let res = Response::builder() + .header(header::CONTENT_TYPE, "good-content") + .body(Body::empty()) + .unwrap(); + Ok::<_, Infallible>(res) + }), + vec![(header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into()], + ); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + + let mut values = res.headers().get_all(header::CONTENT_TYPE).iter(); + assert_eq!(values.next().unwrap(), "good-content"); + assert_eq!(values.next().unwrap(), "text/html"); + assert_eq!(values.next(), None); + } + + #[tokio::test] + async fn test_skip_if_present_mode() { + let svc = SetMultipleResponseHeader::if_not_present( + service_fn(|_req: Request| async { + let res = Response::builder() + .header(header::CONTENT_TYPE, "good-content") + .body(Body::empty()) + .unwrap(); + Ok::<_, Infallible>(res) + }), + vec![(header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into()], + ); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + + let mut values = res.headers().get_all(header::CONTENT_TYPE).iter(); + assert_eq!(values.next().unwrap(), "good-content"); + assert_eq!(values.next(), None); + } + + #[tokio::test] + async fn test_skip_if_present_mode_when_not_present() { + let svc = SetMultipleResponseHeader::if_not_present( + service_fn(|_req: Request| async { + let res = Response::builder().body(Body::empty()).unwrap(); + Ok::<_, Infallible>(res) + }), + vec![(header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into()], + ); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + + let mut values = res.headers().get_all(header::CONTENT_TYPE).iter(); + assert_eq!(values.next().unwrap(), "text/html"); + assert_eq!(values.next(), None); + } + + #[test] + fn test_tuple_metadata_impl() { + let tuple: (HeaderName, HeaderValue) = + (header::CONTENT_TYPE, HeaderValue::from_static("foo")); + let meta: HeaderMetadata = tuple.into(); + assert_eq!(meta.header_name, header::CONTENT_TYPE); + // Check that the header value is correct by making a header value from meta.make + let mut make = meta.make.clone(); + assert_eq!( + make.make_header_value(&HeaderValue::from_static("foo")), + Some(HeaderValue::from_static("foo")) + ); + } + + #[test] + fn test_convert_to_header_config_struct_and_tuple() { + let meta: HeaderMetadata = HeaderMetadata:: { + header_name: header::CONTENT_TYPE, + make: BoxedMakeHeaderValue::new(HeaderValue::from_static("bar")), + }; + let rh = meta.build_config(crate::set_header::InsertHeaderMode::Override); + assert_eq!(rh.header_name, header::CONTENT_TYPE); + let mut make = rh.make.clone(); + assert_eq!( + make.make_header_value(&HeaderValue::from_static("bar")), + Some(HeaderValue::from_static("bar")) + ); + + let tuple: (HeaderName, HeaderValue) = + (header::CONTENT_TYPE, HeaderValue::from_static("baz")); + let meta: HeaderMetadata = tuple.into(); + let rh2 = meta.build_config(crate::set_header::InsertHeaderMode::Override); + assert_eq!(rh2.header_name, header::CONTENT_TYPE); + let mut make2 = rh2.make.clone(); + assert_eq!( + make2.make_header_value(&HeaderValue::from_static("baz")), + Some(HeaderValue::from_static("baz")) + ); + } + + #[test] + fn test_debug_impls() { + let meta: HeaderMetadata = + (header::CONTENT_TYPE, HeaderValue::from_static("bar")).into(); + let rh = meta + .clone() + .build_config(crate::set_header::InsertHeaderMode::Override); + let layer = SetMultipleResponseHeadersLayer::overriding(vec![meta]); + let debug_str = format!("{:?}", layer); + assert!(debug_str.contains("SetMultipleResponseHeadersLayer")); + let debug_rh = format!("{:?}", rh); + assert!(debug_rh.contains("HeaderInsertionConfig")); + + let svc = SetMultipleResponseHeader::overriding( + tower::service_fn(|_req: Request| async { + Ok::<_, std::convert::Infallible>(Response::new(Body::empty())) + }), + vec![(header::CONTENT_TYPE, HeaderValue::from_static("foo")).into()] + as Vec>, + ); + let debug_svc = format!("{:?}", svc); + assert!(debug_svc.contains("SetMultipleResponseHeader")); + } + + #[tokio::test] + async fn test_layer_construction_and_multiple_headers() { + // Multiple different headers in the same vec + let svc = tower::ServiceBuilder::new() + .layer(SetMultipleResponseHeadersLayer::overriding(vec![ + (header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into(), + (header::CACHE_CONTROL, HeaderValue::from_static("no-cache")).into(), + ])) + .service(service_fn(|_req: Request| async { + Ok::<_, Infallible>(Response::new(Body::empty())) + })); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + assert_eq!(res.headers()["content-type"], "text/html"); + assert_eq!(res.headers()["cache-control"], "no-cache"); + } + + #[tokio::test] + async fn test_layer_with_empty_vec() { + let svc = tower::ServiceBuilder::new() + .layer(SetMultipleResponseHeadersLayer::>::overriding(vec![])) + .service(service_fn(|_req: Request| async { + Ok::<_, Infallible>(Response::new(Body::empty())) + })); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + // No headers should be set + assert_eq!(res.headers().len(), 0); + } + + #[tokio::test] + async fn test_layer_with_static_and_closure_headers_fixed() { + // Wrap the static value + let static_meta = (header::CONTENT_TYPE, HeaderValue::from_static("text/html")).into(); + + // Wrap the closure + let closure_meta = (header::X_FRAME_OPTIONS, |_res: &Response| { + Some(HeaderValue::from_static("DENY")) + }) + .into(); + + let svc = tower::ServiceBuilder::new() + .layer(SetMultipleResponseHeadersLayer::overriding(vec![ + static_meta, + closure_meta, + ])) + .service(service_fn(|_req: Request| async { + Ok::<_, Infallible>(Response::new(Body::empty())) + })); + + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + assert_eq!(res.headers()["content-type"], "text/html"); + assert_eq!(res.headers()["x-frame-options"], "DENY"); + } + + #[test] + fn test_debug_layer_and_service() { + let meta: HeaderMetadata = + (header::CONTENT_TYPE, HeaderValue::from_static("foo")).into(); + let layer = SetMultipleResponseHeadersLayer::overriding(vec![meta]); + let debug_str = format!("{:?}", layer); + assert!(debug_str.contains("SetMultipleResponseHeadersLayer")); + } +} diff --git a/tower-http/src/set_header/response.rs b/tower-http/src/set_header/response/single_header.rs similarity index 71% rename from tower-http/src/set_header/response.rs rename to tower-http/src/set_header/response/single_header.rs index c7b8ea84..9381153f 100644 --- a/tower-http/src/set_header/response.rs +++ b/tower-http/src/set_header/response/single_header.rs @@ -1,100 +1,7 @@ -//! Set a header on the response. +//! Set a single header on the response. //! -//! The header value to be set may be provided as a fixed value when the -//! middleware is constructed, or determined dynamically based on the response -//! by a closure. See the [`MakeHeaderValue`] trait for details. +//! See the root [`crate::set_header::response`] module for full documentation and usage examples. //! -//! # Example -//! -//! Setting a header from a fixed value provided when the middleware is constructed: -//! -//! ``` -//! use http::{Request, Response, header::{self, HeaderValue}}; -//! use tower::{Service, ServiceExt, ServiceBuilder}; -//! use tower_http::set_header::SetResponseHeaderLayer; -//! use http_body_util::Full; -//! use bytes::Bytes; -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), Box> { -//! # let render_html = tower::service_fn(|request: Request>| async move { -//! # Ok::<_, std::convert::Infallible>(Response::new(request.into_body())) -//! # }); -//! # -//! let mut svc = ServiceBuilder::new() -//! .layer( -//! // Layer that sets `Content-Type: text/html` on responses. -//! // -//! // `if_not_present` will only insert the header if it does not already -//! // have a value. -//! SetResponseHeaderLayer::if_not_present( -//! header::CONTENT_TYPE, -//! HeaderValue::from_static("text/html"), -//! ) -//! ) -//! .service(render_html); -//! -//! let request = Request::new(Full::default()); -//! -//! let response = svc.ready().await?.call(request).await?; -//! -//! assert_eq!(response.headers()["content-type"], "text/html"); -//! # -//! # Ok(()) -//! # } -//! ``` -//! -//! Setting a header based on a value determined dynamically from the response: -//! -//! ``` -//! use http::{Request, Response, header::{self, HeaderValue}}; -//! use tower::{Service, ServiceExt, ServiceBuilder}; -//! use tower_http::set_header::SetResponseHeaderLayer; -//! use bytes::Bytes; -//! use http_body_util::Full; -//! use http_body::Body as _; // for `Body::size_hint` -//! -//! # #[tokio::main] -//! # async fn main() -> Result<(), Box> { -//! # let render_html = tower::service_fn(|request: Request>| async move { -//! # Ok::<_, std::convert::Infallible>(Response::new(Full::from("1234567890"))) -//! # }); -//! # -//! let mut svc = ServiceBuilder::new() -//! .layer( -//! // Layer that sets `Content-Length` if the body has a known size. -//! // Bodies with streaming responses wont have a known size. -//! // -//! // `overriding` will insert the header and override any previous values it -//! // may have. -//! SetResponseHeaderLayer::overriding( -//! header::CONTENT_LENGTH, -//! |response: &Response>| { -//! if let Some(size) = response.body().size_hint().exact() { -//! // If the response body has a known size, returning `Some` will -//! // set the `Content-Length` header to that value. -//! Some(HeaderValue::from_str(&size.to_string()).unwrap()) -//! } else { -//! // If the response body doesn't have a known size, return `None` -//! // to skip setting the header on this response. -//! None -//! } -//! } -//! ) -//! ) -//! .service(render_html); -//! -//! let request = Request::new(Full::default()); -//! -//! let response = svc.ready().await?.call(request).await?; -//! -//! assert_eq!(response.headers()["content-length"], "10"); -//! # -//! # Ok(()) -//! # } -//! ``` - -use super::{InsertHeaderMode, MakeHeaderValue}; use http::{header::HeaderName, Request, Response}; use pin_project_lite::pin_project; use std::{ @@ -106,6 +13,8 @@ use std::{ use tower_layer::Layer; use tower_service::Service; +use crate::set_header::{InsertHeaderMode, MakeHeaderValue}; + /// Layer that applies [`SetResponseHeader`] which adds a response header. /// /// See [`SetResponseHeader`] for more details.