+ where
+ Self: Sized,
+ {
+ FollowRedirect::new(self)
+ }
+
+ /// Creates a new middleware that retries requests with a [`Service`](tower::Service) to follow redirection responses
+ /// with the given redirection [`Policy`](crate::follow_redirect::policy::Policy).
+ ///
+ /// See the [follow_redirect](crate::follow_redirect) for more details.
+ #[cfg(feature = "follow-redirect")]
+ fn follow_redirect_with_policy(self, policy: P) -> FollowRedirect
+ where
+ Self: Sized,
+ P: Clone,
+ {
+ FollowRedirect::with_policy(self, policy)
+ }
+
+ /// Creates a new middleware that intercepts requests with body lengths greater than the
+ /// configured limit and converts them into `413 Payload Too Large` responses.
+ ///
+ /// See the [limit](crate::limit) for an example.
+ #[cfg(feature = "limit")]
+ fn limit_request_body(self, limit: usize) -> RequestBodyLimit
+ where
+ Self: Sized,
+ {
+ RequestBodyLimit::new(self, limit)
+ }
+
+ /// Creates a new middleware that apply a transformation to the request body.
+ #[cfg(feature = "map-request-body")]
+ fn map_request_body(self, f: F) -> MapRequestBody
+ where
+ Self: Sized,
+ {
+ MapRequestBody::new(self, f)
+ }
+
+ /// Creates a new middleware that apply a transformation to the response body.
+ #[cfg(feature = "map-response-body")]
+ fn map_response_body(self, f: F) -> MapResponseBody
+ where
+ Self: Sized,
+ {
+ MapResponseBody::new(self, f)
+ }
+
+ /// Creates a new middleware that counts the number of in-flight requests.
+ #[cfg(feature = "metrics")]
+ fn count_in_flight_requests(self, counter: InFlightRequestsCounter) -> InFlightRequests
+ where
+ Self: Sized,
+ {
+ InFlightRequests::new(self, counter)
+ }
+
+ /// Creates a new middleware that normalizes paths.
+ ///
+ /// Any trailing slashes from request paths will be removed. For example, a request with `/foo/`
+ /// will be changed to `/foo` before reaching the inner service.
+ ///
+ /// See the [normalize_path](crate::normalize_path) for more details.
+ #[cfg(feature = "normalize-path")]
+ fn normalize_path(self) -> NormalizePath
+ where
+ Self: Sized,
+ {
+ NormalizePath::trim_trailing_slash(self)
+ }
+
+ /// Creates a new middleware that propagates headers from requests to responses.
+ ///
+ /// If the header is present on the request it'll be applied to the response as well. This could
+ /// for example be used to propagate headers such as `X-Request-Id`.
+ ///
+ /// See the [propagate_header](crate::propagate_header) for more details.
+ #[cfg(feature = "propagate-header")]
+ fn propagate_header(self, header_name: HeaderName) -> PropagateHeader
+ where
+ Self: Sized,
+ {
+ PropagateHeader::new(self, header_name)
+ }
+
+ /// Creates a new middleware that propagate request ids from requests to responses.
+ ///
+ /// If the request contains a matching header that header will be applied to responses. If a
+ /// [`RequestId`](crate::request_id::RequestId) extension is also present it will be propagated as well.
+ ///
+ /// See the [request_id](crate::request_id) for an example.
+ #[cfg(feature = "request-id")]
+ fn propagate_request_id(self, header_name: HeaderName) -> PropagateRequestId
+ where
+ Self: Sized,
+ {
+ PropagateRequestId::new(self, header_name)
+ }
+
+ /// Creates a new middleware that propagate request ids from requests to responses
+ /// using `x-request-id` as the header name.
+ ///
+ /// If the request contains a matching header that header will be applied to responses. If a
+ /// [`RequestId`](crate::request_id::RequestId) extension is also present it will be propagated as well.
+ ///
+ /// See the [request_id](crate::request_id) for an example.
+ #[cfg(feature = "request-id")]
+ fn propagate_x_request_id(self) -> PropagateRequestId
+ where
+ Self: Sized,
+ {
+ PropagateRequestId::new(self, HeaderName::from_static(X_REQUEST_ID))
+ }
+
+ /// Creates a new middleware that set request id headers and extensions on requests.
+ ///
+ /// If [`MakeRequestId::make_request_id`] returns `Some(_)` and the request doesn't already have a
+ /// header with the same name, then the header will be inserted.
+ ///
+ /// Additionally [`RequestId`](crate::request_id::RequestId) will be inserted into
+ /// the Request extensions so other services can access it.
+ ///
+ /// See the [request_id](crate::request_id) for an example.
+ #[cfg(feature = "request-id")]
+ fn set_request_id(self, header_name: HeaderName, make_request_id: M) -> SetRequestId
+ where
+ Self: Sized,
+ M: MakeRequestId,
+ {
+ SetRequestId::new(self, header_name, make_request_id)
+ }
+
+ /// Creates a new middleware that set request id headers and extensions on requests
+ /// using `x-request-id` as the header name.
+ ///
+ /// If [`MakeRequestId::make_request_id`] returns `Some(_)` and the request doesn't already have a
+ /// header with the same name, then the header will be inserted.
+ ///
+ /// Additionally [`RequestId`](crate::request_id::RequestId) will be inserted into
+ /// the Request extensions so other services can access it.
+ ///
+ /// See the [request_id](crate::request_id) for an example.
+ #[cfg(feature = "request-id")]
+ fn set_x_request_id(self, make_request_id: M) -> SetRequestId
+ where
+ Self: Sized,
+ M: MakeRequestId,
+ {
+ SetRequestId::new(self, HeaderName::from_static(X_REQUEST_ID), make_request_id)
+ }
+
+ /// Creates a new middleware that marks headers as [sensitive].
+ ///
+ /// See the [sensitive_headers](crate::sensitive_headers) for more details.
+ ///
+ /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
+ #[cfg(feature = "sensitive-headers")]
+ fn set_sensitive_headers(self, headers: I) -> SetSensitiveHeaders
+ where
+ Self: Sized,
+ I: IntoIterator- ,
+ {
+ use std::iter::FromIterator;
+ let headers = Vec::from_iter(headers);
+ SetSensitiveRequestHeaders::new(
+ SetSensitiveResponseHeaders::new(self, headers.iter().cloned()),
+ headers.into_iter(),
+ )
+ }
+
+ /// Creates a new middleware that marks request headers as [sensitive].
+ ///
+ /// See the [sensitive_headers](crate::sensitive_headers) for more details.
+ ///
+ /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
+ #[cfg(feature = "sensitive-headers")]
+ fn set_sensitive_request_headers(self, headers: I) -> SetSensitiveRequestHeaders
+ where
+ Self: Sized,
+ I: IntoIterator
- ,
+ {
+ SetSensitiveRequestHeaders::new(self, headers)
+ }
+
+ /// Creates a new middleware that marks response headers as [sensitive].
+ ///
+ /// See the [sensitive_headers](crate::sensitive_headers) for more details.
+ ///
+ /// [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
+ #[cfg(feature = "sensitive-headers")]
+ fn set_sensitive_response_headers(self, headers: I) -> SetSensitiveResponseHeaders
+ where
+ Self: Sized,
+ I: IntoIterator
- ,
+ {
+ SetSensitiveResponseHeaders::new(self, headers)
+ }
+
+ /// Creates a new middleware that sets a header on the request.
+ ///
+ /// If a previous value exists for the same header, it is removed and replaced with the new
+ /// header value.
+ #[cfg(feature = "set-header")]
+ fn override_request_header(
+ self,
+ header_name: HeaderName,
+ make: M,
+ ) -> SetRequestHeader
+ where
+ Self: Sized,
+ {
+ SetRequestHeader::overriding(self, header_name, make)
+ }
+
+ /// Creates a new middleware that sets a header on the request.
+ ///
+ /// The new header is always added, preserving any existing values. If previous values exist,
+ /// the header will have multiple values.
+ #[cfg(feature = "set-header")]
+ fn append_request_header(self, header_name: HeaderName, make: M) -> SetRequestHeader
+ where
+ Self: Sized,
+ {
+ SetRequestHeader::appending(self, header_name, make)
+ }
+
+ /// Creates a new middleware that sets a header on the request.
+ ///
+ /// If a previous value exists for the header, the new value is not inserted.
+ #[cfg(feature = "set-header")]
+ fn set_request_header_if_not_present(
+ self,
+ header_name: HeaderName,
+ make: M,
+ ) -> SetRequestHeader
+ where
+ Self: Sized,
+ {
+ SetRequestHeader::if_not_present(self, header_name, make)
+ }
+
+ /// Creates a new middleware that sets a header on the response.
+ ///
+ /// If a previous value exists for the same header, it is removed and replaced with the new
+ /// header value.
+ #[cfg(feature = "set-header")]
+ fn override_response_header(
+ self,
+ header_name: HeaderName,
+ make: M,
+ ) -> SetResponseHeader
+ where
+ Self: Sized,
+ {
+ SetResponseHeader::overriding(self, header_name, make)
+ }
+
+ /// Creates a new middleware that sets a header on the response.
+ ///
+ /// The new header is always added, preserving any existing values. If previous values exist,
+ /// the header will have multiple values.
+ #[cfg(feature = "set-header")]
+ fn append_response_header(
+ self,
+ header_name: HeaderName,
+ make: M,
+ ) -> SetResponseHeader
+ where
+ Self: Sized,
+ {
+ SetResponseHeader::appending(self, header_name, make)
+ }
+
+ /// Creates a new middleware that sets a header on the response.
+ ///
+ /// If a previous value exists for the header, the new value is not inserted.
+ #[cfg(feature = "set-header")]
+ fn set_response_header_if_not_present(
+ self,
+ header_name: HeaderName,
+ make: M,
+ ) -> SetResponseHeader
+ where
+ Self: Sized,
+ {
+ SetResponseHeader::if_not_present(self, header_name, make)
+ }
+
+ /// Creates a new middleware that override status codes.
+ ///
+ /// See the [set_status](crate::set_status) for more details.
+ #[cfg(feature = "set-status")]
+ fn set_status(self, status: StatusCode) -> SetStatus
+ where
+ Self: Sized,
+ {
+ SetStatus::new(self, status)
+ }
+
+ /// Creates a new middleware that applies a timeout to requests.
+ ///
+ /// If the request does not complete within the specified timeout it will be aborted and a `408
+ /// Request Timeout` response will be sent.
+ ///
+ /// See the [timeout](crate::timeout) for an example.
+ #[cfg(feature = "timeout")]
+ fn timeout(self, timeout: Duration) -> Timeout
+ where
+ Self: Sized,
+ {
+ Timeout::new(self, timeout)
+ }
+
+ /// Creates a new middleware that applies a timeout to request bodies.
+ ///
+ /// See the [timeout](crate::timeout) for an example.
+ #[cfg(feature = "timeout")]
+ fn timeout_request_body(self, timeout: Duration) -> RequestBodyTimeout
+ where
+ Self: Sized,
+ {
+ RequestBodyTimeout::new(self, timeout)
+ }
+
+ /// Creates a new middleware that applies a timeout to response bodies.
+ ///
+ /// See the [timeout](crate::timeout) for an example.
+ #[cfg(feature = "timeout")]
+ fn timeout_response_body(self, timeout: Duration) -> ResponseBodyTimeout
+ where
+ Self: Sized,
+ {
+ ResponseBodyTimeout::new(self, timeout)
+ }
+
+ /// Creates a new middleware that adds high level [tracing] to a [`Service`]
+ /// using the given [`MakeClassifier`].
+ ///
+ /// See the [trace](crate::trace) for an example.
+ ///
+ /// [tracing]: https://crates.io/crates/tracing
+ /// [`Service`]: tower_service::Service
+ #[cfg(feature = "trace")]
+ fn trace(
+ self,
+ make_classifier: M,
+ ) -> Trace<
+ Self,
+ M,
+ DefaultMakeSpan,
+ DefaultOnRequest,
+ DefaultOnResponse,
+ DefaultOnBodyChunk,
+ DefaultOnEos,
+ DefaultOnFailure,
+ >
+ where
+ Self: Sized,
+ M: MakeClassifier,
+ {
+ Trace::new(self, make_classifier)
+ }
+
+ /// Creates a new middleware that adds high level [tracing] to a [`Service`]
+ /// which supports classifying regular HTTP responses based on the status code.
+ ///
+ /// See the [trace](crate::trace) for an example.
+ ///
+ /// [tracing]: https://crates.io/crates/tracing
+ /// [`Service`]: tower_service::Service
+ #[cfg(feature = "trace")]
+ fn trace_http(
+ self,
+ ) -> Trace<
+ Self,
+ SharedClassifier,
+ DefaultMakeSpan,
+ DefaultOnRequest,
+ DefaultOnResponse,
+ DefaultOnBodyChunk,
+ DefaultOnEos,
+ DefaultOnFailure,
+ >
+ where
+ Self: Sized,
+ {
+ Trace::new_for_http(self)
+ }
+
+ /// Creates a new middleware that adds high level [tracing] to a [`Service`]
+ /// which supports classifying gRPC responses and streams based on the `grpc-status` header.
+ ///
+ /// See the [trace](crate::trace) for an example.
+ ///
+ /// [tracing]: https://crates.io/crates/tracing
+ /// [`Service`]: tower_service::Service
+ #[cfg(feature = "trace")]
+ fn trace_grpc(
+ self,
+ ) -> Trace<
+ Self,
+ SharedClassifier,
+ DefaultMakeSpan,
+ DefaultOnRequest,
+ DefaultOnResponse,
+ DefaultOnBodyChunk,
+ DefaultOnEos,
+ DefaultOnFailure,
+ >
+ where
+ Self: Sized,
+ {
+ Trace::new_for_grpc(self)
+ }
+
+ /// Creates a new middleware that authorize requests using a username and password pair.
+ ///
+ /// The `Authorization` header is required to be `Basic {credentials}` where `credentials` is
+ /// `base64_encode("{username}:{password}")`.
+ ///
+ /// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS
+ /// with this method. However use of HTTPS/TLS is not enforced by this middleware.
+ ///
+ /// See the [validate_request](crate::validate_request) for an example.
+ #[cfg(all(feature = "validate-request", feature = "auth"))]
+ fn validate_basic_authorization(
+ self,
+ username: &str,
+ password: &str,
+ ) -> ValidateRequestHeader>
+ where
+ Self: Sized,
+ Resbody: Body + Default,
+ {
+ ValidateRequestHeader::basic(self, username, password)
+ }
+
+ /// Creates a new middleware that authorize requests using a "bearer token".
+ /// Commonly used for OAuth 2.
+ ///
+ /// The `Authorization` header is required to be `Bearer {token}`.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the token is not a valid [`HeaderValue`](http::header::HeaderValue).
+ ///
+ /// See the [validate_request](crate::validate_request) for an example.
+ #[cfg(all(feature = "validate-request", feature = "auth"))]
+ fn validate_bearer_authorization(
+ self,
+ token: &str,
+ ) -> ValidateRequestHeader>
+ where
+ Self: Sized,
+ Resbody: Body + Default,
+ {
+ ValidateRequestHeader::bearer(self, token)
+ }
+
+ /// Creates a new middleware that authorize requests that have the required Accept header.
+ ///
+ /// The `Accept` header is required to be `*/*`, `type/*` or `type/subtype`,
+ /// as configured.
+ ///
+ /// # Panics
+ ///
+ /// See `AcceptHeader::new` for when this method panics.
+ ///
+ /// See the [validate_request](crate::validate_request) for an example.
+ #[cfg(feature = "validate-request")]
+ fn validate_accept_header(
+ self,
+ value: &str,
+ ) -> ValidateRequestHeader>
+ where
+ Self: Sized,
+ Resbody: Body + Default,
+ {
+ ValidateRequestHeader::accept(self, value)
+ }
+
+ /// Creates a new middleware that authorize requests using a custom method.
+ ///
+ /// See the [validate_request](crate::validate_request) for an example.
+ #[cfg(feature = "validate-request")]
+ fn validate(self, validate: T) -> ValidateRequestHeader
+ where
+ Self: Sized,
+ {
+ ValidateRequestHeader::custom(self, validate)
+ }
+}
+
+impl ServiceExt for T where T: tower_service::Service + Sized {}
diff --git a/tower-http/src/validate_request.rs b/tower-http/src/validate_request.rs
index 327266af..e7ccde18 100644
--- a/tower-http/src/validate_request.rs
+++ b/tower-http/src/validate_request.rs
@@ -416,9 +416,9 @@ where
mod tests {
#[allow(unused_imports)]
use super::*;
- use crate::test_helpers::Body;
+ use crate::{test_helpers::Body, ServiceExt};
use http::header;
- use tower::{BoxError, ServiceBuilder, ServiceExt};
+ use tower::{service_fn, BoxError, ServiceBuilder, ServiceExt as TowerServiceExt};
#[tokio::test]
async fn valid_accept_header() {
@@ -436,6 +436,20 @@ mod tests {
assert_eq!(res.status(), StatusCode::OK);
}
+ #[tokio::test]
+ async fn valid_accept_header_service_ext() {
+ let mut service = service_fn(echo).validate_accept_header("application/json");
+
+ let request = Request::get("/")
+ .header(header::ACCEPT, "application/json")
+ .body(Body::empty())
+ .unwrap();
+
+ let res = service.ready().await.unwrap().call(request).await.unwrap();
+
+ assert_eq!(res.status(), StatusCode::OK);
+ }
+
#[tokio::test]
async fn valid_accept_header_accept_all_json() {
let mut service = ServiceBuilder::new()