Module follow_redirect
http and std only.Expand description
Middleware for following redirections.
§Overview
The FollowRedirect middleware retries requests with the inner Service to follow HTTP
redirections.
The middleware tries to clone the original Request when making a redirected request.
The request body cannot always be cloned. When the original body is
known to be empty by StreamingBody::size_hint, the middleware uses the Default
implementation of the body type to create a new request body. If you know that the body can be
cloned in some way, you can tell the middleware to clone it by configuring a policy.
By default every attempt — the original request included — runs on its own
fork of the caller’s request Extensions: each hop
reads everything the caller inserted, while what it (or any inner layer) inserts stays isolated
from the caller and from every other hop. Isolation is structural, not deep: an inherited value
is shared by handle, so interior mutation through it (an atomic, a lock) stays visible to the
caller and to every other hop. Only the entries a hop inserts itself are private to it.
Hop 1 pays for that fork too, redirect or not — its inserts have already happened by the time a
Location arrives, so the isolation cannot be deferred until one is seen. It is one
Extensions::fork (tens of nanoseconds, and one extra level on an extension miss).
§Layer placement
Place FollowRedirectLayer as early — as far outward — in the stack as your use case allows:
in front of everything whose work depends on the request’s target, e.g. proxy or route
selection, DNS overwrites, per-origin credentials or per-host limits.
Such a layer placed outside this middleware runs exactly once, for the original target, and
every hop then inherits the decision it made for a different host or resource: hop 2 to
internal.corp is routed by the proxy hop 1 picked for public.example. Placed inside, it is
consulted per hop and decides on that hop’s real target.
The same holds for the extensions themselves: an inner layer’s inserts cannot leak between hops,
but a layer outside this middleware inserts into the caller’s request store, which every hop —
cross-origin ones included — inherits and can read. rama’s Extensions are append-only, so no
policy (including FilterCredentials) can strip them afterwards. Keeping origin-scoped
state away from a redirect target therefore takes both halves: inserted inside this middleware
and derived from the hop’s own target. Inside alone only makes the decision re-decidable — a
layer that inserts the same value for every target (e.g. HttpProxyAddressLayer, which stamps
one configured proxy) hands that value to each hop regardless of where it points. When the value
is inserted by the caller, scope the value itself so it cannot authorize or configure an
unrelated redirect target.
The rama CLI keeps AddAuthorizationLayer outside on purpose: it sets a header rather than
an extension, so FilterCredentials can strip it on a cross-origin hop.
Consulting a per-request decider on every hop does mean an attacker-controlled Location chain
costs N decisions instead of 1, bounded by the redirect policy’s own limit. That is the
correct trade: the alternative is routing hop N by hop 1’s answer.
Paired with retry — which follows the same rules per attempt — keep this
middleware outermost, so a retry replays one hop instead of the entire redirect chain.
§Examples
§Basic usage
use rama_core::service::service_fn;
use rama_core::{extensions::ExtensionsRef, Service, Layer};
use rama_http::{Body, Request, Response, StatusCode, header};
use rama_http::layer::follow_redirect::{FollowRedirectLayer, RequestUri};
let mut client = FollowRedirectLayer::new().into_layer(http_client);
let request = Request::builder()
.uri("https://rust-lang.org/")
.body(Body::empty())
.unwrap();
let response = client.serve(request).await?;
// Get the final request URI.
assert_eq!(response.extensions().get_ref::<RequestUri>().unwrap().0.as_str(), "https://www.rust-lang.org/");§Customizing the Policy
You can use a Policy value to customize how the middleware handles redirections.
use rama_core::service::service_fn;
use rama_core::layer::MapErrLayer;
use rama_core::{Service, Layer};
use rama_http::{Body, Request, Response};
use rama_http::layer::follow_redirect::{
policy::{self, PolicyExt},
FollowRedirectLayer,
};
use rama_core::error::BoxError;
#[derive(Debug)]
enum MyError {
TooManyRedirects,
Other(BoxError),
}
impl MyError {
fn from_std(err: impl std::error::Error + Send + Sync + 'static) -> Self {
Self::Other(BoxError::from(err))
}
}
let policy = policy::Limited::new(10) // Set the maximum number of redirections to 10.
// Return an error when the limit was reached.
.or::<_, (), _>(policy::redirect_fn(|_| Err(MyError::TooManyRedirects)))
// Do not follow cross-origin redirections, and return the redirection responses as-is.
.and::<_, (), _>(policy::SameOrigin::new());
let client = (
FollowRedirectLayer::with_policy(policy),
MapErrLayer::new(MyError::from_std),
).into_layer(http_client);
// ...
_ = client.serve(Request::default()).await?;Modules§
- policy
- Tools for customizing the behavior of a
FollowRedirectmiddleware.
Structs§
- Follow
Redirect - Middleware that retries requests with a
Serviceto follow redirection responses. - Follow
Redirect Layer Layerfor retrying requests with aServiceto follow redirection responses.- Request
Uri - Response
Extensionsvalue that represents the effective request URI of a response returned by aFollowRedirectmiddleware.