Skip to content

feature: expose redirect policy through extension #583

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

unusual-thoughts
Copy link

This adds a FollowedPolicy extension for the FollowRedirect middleware that exposes the policy struct to the response.

Motivation

Currently there is no way to inspect the URLs (and status codes) that a redirected response took, as there is no way to "get back" the redirect policy struct from follow_redirect::ResponseFuture.

Custom redirect policies might have useful state that the user wants to access once the request completes. A typical use case would be to log the urls and status codes that have been traversed, which is already necessary for example to avoid loops.

This implements part of the modifications suggested in #404 as I understand it. It is also required to implement seanmonstar/reqwest#2606

Solution

Implement a http::Extensions::Extension that stores a copy of the follow_redirect::policy::Policy struct that was run on the last request of the redirect chain.

Downsides

Currently this does another clone() of the policy struct for each new request

@@ -197,7 +197,7 @@ impl<ReqBody, ResBody, S, P> Service<Request<ReqBody>> for FollowRedirect<S, P>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone,
ReqBody: Body + Default,
P: Policy<ReqBody, S::Error> + Clone,
P: Policy<ReqBody, S::Error> + Clone + Send + Sync + 'static,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This would be considered breaking changes, since some types might not fit the bounds.

Can we insert a custom extension without the need to change this layer?

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure what you mean by layer ? the tower middleware Layer ?

The extra constraints come from the signature of http::extensions::Extensions

pub fn insert<T: Clone + Send + Sync + 'static>(&mut self, val: T) -> Option<T> 

Copy link
Author

@unusual-thoughts unusual-thoughts Jun 12, 2025

Choose a reason for hiding this comment

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

One way to avoid the breaking change would be something like:

trait MaybeAddExtension {
    fn add_policy_ext<ResBody>(&self, _res: &mut Response<ResBody>) {}
}

impl<T> MaybeAddExtension for T
where
    T: Clone + Send + Sync + 'static,
{
    fn add_policy_ext<ResBody>(&self, res: &mut Response<ResBody>) {
        res.extensions_mut().insert(FollowedPolicy(self.clone()));
    }
}

and then we can have

-    P: Policy<ReqBody, S::Error> + Clone,
+    P: Policy<ReqBody, S::Error> + Clone + MaybeAddExtension,

         res.extensions_mut().insert(RequestUri(this.uri.clone()));
+        this.policy.add_policy_ext(&mut res);

Copy link
Member

Choose a reason for hiding this comment

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

You'd still be adding a bound where there wasn't one before, it would be exactly the same breaking change. The problem is somebody could have implemented a custom policy that's not Send or not Sync, and passed that to with_policy. The change to this line would then result in FollowRedirect<_, CustomPolicy> no longer implementing Service.

Copy link
Author

Choose a reason for hiding this comment

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

oh you're right, somehow I mistakenly thought that the default empty implementation would be generated for non-Sync/Send policies.
So is the only way to avoid the breaking change to define a second layer variant that adds the extension?

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.

3 participants