Skip to content

Commit 46278eb

Browse files
0x676e67Copilot
andauthored
feat(redirect): support accessing redirect history in response (#917)
close: https://github.com/0x676e67/wreq/issues/915 --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 7a1c86a commit 46278eb

10 files changed

Lines changed: 214 additions & 55 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ required-features = ["webpki-roots", "tracing"]
257257
[[example]]
258258
name = "request_with_redirect"
259259
path = "examples/request_with_redirect.rs"
260-
required-features = ["tracing"]
261260

262261
[[example]]
263262
name = "request_with_version"

src/client/http/future.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,7 @@ use pin_project_lite::pin_project;
88
use tower::util::Oneshot;
99

1010
use super::{Body, Response, types::ClientRef};
11-
use crate::{
12-
Error,
13-
client::{body, layer::redirect::RequestUri},
14-
};
11+
use crate::{Error, client::body, ext::RequestUri};
1512

1613
type ResponseFuture = Oneshot<ClientRef, Request<Body>>;
1714

src/client/http/mod.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ struct Config {
137137
proxies: Vec<ProxyMatcher>,
138138
auto_sys_proxy: bool,
139139
redirect_policy: RedirectPolicy,
140+
redirect_history: bool,
140141
referer: bool,
141142
timeout_options: TimeoutOptions,
142143
#[cfg(feature = "cookies")]
@@ -204,6 +205,7 @@ impl ClientBuilder {
204205
proxies: Vec::new(),
205206
auto_sys_proxy: true,
206207
redirect_policy: RedirectPolicy::none(),
208+
redirect_history: false,
207209
referer: true,
208210
timeout_options: TimeoutOptions::default(),
209211
#[cfg(feature = "hickory-dns")]
@@ -369,7 +371,8 @@ impl ClientBuilder {
369371
let service = {
370372
let policy = FollowRedirectPolicy::new(config.redirect_policy)
371373
.with_referer(config.referer)
372-
.with_https_only(config.https_only);
374+
.with_https_only(config.https_only)
375+
.with_history(config.redirect_history);
373376

374377
ServiceBuilder::new()
375378
.layer(FollowRedirectLayer::with_policy(policy))
@@ -720,6 +723,15 @@ impl ClientBuilder {
720723
self
721724
}
722725

726+
/// Enable or disable redirect history tracking.
727+
///
728+
/// Default is `false`.
729+
#[inline]
730+
pub fn history(mut self, enable: bool) -> ClientBuilder {
731+
self.config.redirect_history = enable;
732+
self
733+
}
734+
723735
/// Enable or disable automatic setting of the `Referer` header.
724736
///
725737
/// Default is `true`.

src/client/layer/redirect/future.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@ use pin_project_lite::pin_project;
1616
use tower::{Service, util::Oneshot};
1717

1818
use super::{
19-
BodyRepr, RequestUri,
19+
BodyRepr,
2020
policy::{Action, Attempt, Policy},
2121
};
22+
use crate::ext::RequestUri;
2223

2324
pin_project! {
2425
/// Response future for [`FollowRedirectLayer`].
@@ -100,7 +101,11 @@ where
100101
drop_payload_headers(headers);
101102
}
102103
StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {}
103-
_ => return Poll::Ready(Ok(res)),
104+
_ => {
105+
// Not a redirect status code, return the response as is.
106+
policy.on_response(&mut res);
107+
return Poll::Ready(Ok(res));
108+
}
104109
};
105110

106111
let take_body = if let Some(body) = body.take() {
@@ -129,7 +134,7 @@ where
129134
match policy.redirect(&attempt)? {
130135
Action::Follow => {
131136
*uri = location;
132-
body.try_clone_from(&take_body, &policy);
137+
body.try_clone_from(&take_body, policy);
133138

134139
let mut req = Request::new(take_body);
135140
*req.uri_mut() = uri.clone();

src/client/layer/redirect/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::{
99
};
1010

1111
use futures_util::future::Either;
12-
use http::{Request, Response, Uri};
12+
use http::{Request, Response};
1313
use http_body::Body;
1414
use tower::{Layer, Service};
1515

@@ -104,14 +104,6 @@ where
104104
}
105105
}
106106

107-
/// Response [`http::Extensions`] value that represents the effective request URI of
108-
/// a response returned by a [`FollowRedirect`] middleware.
109-
///
110-
/// The value differs from the original request's effective URI if the middleware has followed
111-
/// redirections.
112-
#[derive(Clone)]
113-
pub struct RequestUri(pub Uri);
114-
115107
#[derive(Debug)]
116108
enum BodyRepr<B> {
117109
Some(B),

src/client/layer/redirect/policy.rs

Lines changed: 11 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ pub trait Policy<B, E> {
1919
/// The default implementation does nothing.
2020
fn on_request(&mut self, _request: &mut Request<B>) {}
2121

22+
/// Invoked right after the service received a response, regardless of whether it is redirected
23+
/// or not.
24+
///
25+
/// This can for example be used to inspect the response before any redirection is handled.
26+
///
27+
/// The default implementation does nothing.
28+
fn on_response<Body>(&mut self, _response: &mut http::Response<Body>) {}
29+
2230
/// Loads redirect policy configuration from the request's [`Extensions`].
2331
///
2432
/// This method is called once at the beginning of request processing to extract
@@ -28,7 +36,9 @@ pub trait Policy<B, E> {
2836
///
2937
/// The default implementation does nothing, meaning the policy uses its default
3038
/// configuration for all requests.
31-
fn on_extensions(&mut self, _extensions: &Extensions);
39+
///
40+
/// The default implementation does nothing.
41+
fn on_extensions(&mut self, _extensions: &Extensions) {}
3242

3343
/// Returns whether redirection is currently permitted by this policy.
3444
///
@@ -51,36 +61,6 @@ pub trait Policy<B, E> {
5161
}
5262
}
5363

54-
impl<B, E, P> Policy<B, E> for &mut P
55-
where
56-
P: Policy<B, E> + ?Sized,
57-
{
58-
#[inline(always)]
59-
fn redirect(&mut self, attempt: &Attempt<'_>) -> Result<Action, E> {
60-
(**self).redirect(attempt)
61-
}
62-
63-
#[inline(always)]
64-
fn on_request(&mut self, request: &mut Request<B>) {
65-
(**self).on_request(request)
66-
}
67-
68-
#[inline(always)]
69-
fn on_extensions(&mut self, extensions: &Extensions) {
70-
(**self).on_extensions(extensions)
71-
}
72-
73-
#[inline(always)]
74-
fn allowed(&self) -> bool {
75-
(**self).allowed()
76-
}
77-
78-
#[inline(always)]
79-
fn clone_body(&self, body: &B) -> Option<B> {
80-
(**self).clone_body(body)
81-
}
82-
}
83-
8464
/// A type that holds information on a redirection attempt.
8565
pub struct Attempt<'a> {
8666
pub(super) status: StatusCode,

src/client/response.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
Error, Upgraded,
1717
core::{client::connect::HttpInfo, ext::ReasonPhrase},
1818
ext::RequestUri,
19+
redirect::{self, History},
1920
};
2021

2122
/// A Response to a submitted `Request`.
@@ -113,6 +114,16 @@ impl Response {
113114
.map(HttpInfo::remote_addr)
114115
}
115116

117+
/// Get the redirect history of this `Response`.
118+
#[inline]
119+
pub fn history(&self) -> impl Iterator<Item = &History> {
120+
self.res
121+
.extensions()
122+
.get::<redirect::RedirectHistory>()
123+
.map(|h| h.0.iter())
124+
.unwrap_or_else(|| [].iter())
125+
}
126+
116127
/// Returns a reference to the associated extensions.
117128
#[inline]
118129
pub fn extensions(&self) -> &http::Extensions {

src/ext.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ pub trait ResponseExt {
6969
fn uri(&self) -> Option<&Uri>;
7070
}
7171

72-
#[derive(Debug, Clone, PartialEq)]
72+
/// Extension type to store the request URI in a response's extensions.
73+
#[derive(Clone)]
7374
pub(crate) struct RequestUri(pub Uri);
7475

7576
impl UriExt for Uri {

0 commit comments

Comments
 (0)