Skip to main content

Module follow_redirect

Module follow_redirect 

Available on crate features 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 FollowRedirect middleware.

Structs§

FollowRedirect
Middleware that retries requests with a Service to follow redirection responses.
FollowRedirectLayer
Layer for retrying requests with a Service to follow redirection responses.
RequestUri
Response Extensions value that represents the effective request URI of a response returned by a FollowRedirect middleware.