Skip to content

Commit 5586145

Browse files
feat: Expose redirect url history to response
1 parent 65102c9 commit 5586145

File tree

7 files changed

+109
-8
lines changed

7 files changed

+109
-8
lines changed

src/async_impl/client.rs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ use quinn::VarInt;
6060
use tokio::time::Sleep;
6161
use tower::util::BoxCloneSyncServiceLayer;
6262
use tower::{Layer, Service};
63-
use tower_http::follow_redirect::FollowRedirect;
63+
use tower_http::follow_redirect::extension::FollowRedirectExtension;
6464

6565
/// An asynchronous `Client` to make Requests with.
6666
///
@@ -989,7 +989,7 @@ impl ClientBuilder {
989989
p
990990
};
991991

992-
let hyper = FollowRedirect::with_policy(hyper_service, policy.clone());
992+
let hyper = FollowRedirectExtension::with_policy(hyper_service, policy.clone());
993993

994994
Ok(Client {
995995
inner: Arc::new(ClientRef {
@@ -1009,7 +1009,7 @@ impl ClientBuilder {
10091009
config.pool_idle_timeout,
10101010
config.cookie_store,
10111011
);
1012-
Some(FollowRedirect::with_policy(h3_service, policy))
1012+
Some(FollowRedirectExtension::with_policy(h3_service, policy))
10131013
}
10141014
None => None,
10151015
},
@@ -2741,9 +2741,9 @@ struct ClientRef {
27412741
#[cfg(feature = "cookies")]
27422742
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
27432743
headers: HeaderMap,
2744-
hyper: FollowRedirect<HyperService, TowerRedirectPolicy>,
2744+
hyper: FollowRedirectExtension<HyperService, TowerRedirectPolicy>,
27452745
#[cfg(feature = "http3")]
2746-
h3_client: Option<FollowRedirect<H3Client, TowerRedirectPolicy>>,
2746+
h3_client: Option<FollowRedirectExtension<H3Client, TowerRedirectPolicy>>,
27472747
referer: bool,
27482748
request_timeout: RequestConfig<RequestTimeout>,
27492749
read_timeout: Option<Duration>,
@@ -2824,9 +2824,15 @@ pin_project! {
28242824
}
28252825

28262826
enum ResponseFuture {
2827-
Default(tower_http::follow_redirect::ResponseFuture<HyperService, Body, TowerRedirectPolicy>),
2827+
Default(
2828+
tower_http::follow_redirect::extension::ResponseFuture<
2829+
HyperService,
2830+
Body,
2831+
TowerRedirectPolicy,
2832+
>,
2833+
),
28282834
#[cfg(feature = "http3")]
2829-
H3(tower_http::follow_redirect::ResponseFuture<H3Client, Body, TowerRedirectPolicy>),
2835+
H3(tower_http::follow_redirect::extension::ResponseFuture<H3Client, Body, TowerRedirectPolicy>),
28302836
}
28312837

28322838
impl PendingRequest {

src/async_impl/response.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ use serde::de::DeserializeOwned;
1212
#[cfg(feature = "json")]
1313
use serde_json;
1414
use tokio::time::Sleep;
15+
use tower_http::follow_redirect::extension::FollowedPolicy;
1516
use url::Url;
1617

1718
use super::body::Body;
1819
use super::decoder::{Accepts, Decoder};
1920
use crate::async_impl::body::ResponseBody;
2021
#[cfg(feature = "cookies")]
2122
use crate::cookie;
23+
use crate::redirect::TowerRedirectPolicy;
2224

2325
#[cfg(feature = "charset")]
2426
use encoding_rs::{Encoding, UTF_8};
@@ -116,6 +118,23 @@ impl Response {
116118
&self.url
117119
}
118120

121+
/// Get all the intermediate `Url`s traversed by redirects.
122+
#[inline]
123+
pub fn history(&self) -> &[Url] {
124+
self.extensions()
125+
.get::<FollowedPolicy<TowerRedirectPolicy>>()
126+
.map_or(&[], |p| &p.0.urls)
127+
}
128+
129+
/// Get all the `Url`s, in sequential order, that were requested,
130+
/// including any redirects and the final url.
131+
#[inline]
132+
pub fn all_urls(&self) -> impl Iterator<Item = &Url> {
133+
self.history()
134+
.iter()
135+
.chain(std::iter::once(self.url.as_ref()))
136+
}
137+
119138
/// Get the remote address used to get this `Response`.
120139
pub fn remote_addr(&self) -> Option<SocketAddr> {
121140
self.res
@@ -509,5 +528,7 @@ mod tests {
509528

510529
assert_eq!(response.status(), 200);
511530
assert_eq!(*response.url(), url);
531+
assert!(response.history().is_empty());
532+
assert_eq!(response.all_urls().collect::<Vec<_>>(), vec![&url]);
512533
}
513534
}

src/blocking/response.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ impl Response {
163163
self.inner.url()
164164
}
165165

166+
/// Get all the intermediate `Url`s traversed by redirects.
167+
#[inline]
168+
pub fn history(&self) -> &[Url] {
169+
self.inner.history()
170+
}
171+
172+
/// Get all the `Url`s, in sequential order, that were requested,
173+
/// including any redirects and the final url.
174+
#[inline]
175+
pub fn all_urls(&self) -> impl Iterator<Item = &Url> {
176+
self.inner.all_urls()
177+
}
178+
166179
/// Get the remote address used to get this `Response`.
167180
///
168181
/// # Example

src/redirect.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ impl StdError for TooManyRedirects {}
265265
pub(crate) struct TowerRedirectPolicy {
266266
policy: Arc<Policy>,
267267
referer: bool,
268-
urls: Vec<Url>,
268+
pub(crate) urls: Vec<Url>,
269269
https_only: bool,
270270
}
271271

tests/blocking.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ fn test_response_text() {
1313
let url = format!("http://{}/text", server.addr());
1414
let res = reqwest::blocking::get(&url).unwrap();
1515
assert_eq!(res.url().as_str(), &url);
16+
assert!(res.history().is_empty());
1617
assert_eq!(res.status(), reqwest::StatusCode::OK);
1718
assert_eq!(res.content_length(), Some(5));
1819

@@ -55,6 +56,7 @@ fn test_response_non_utf_8_text() {
5556
let url = format!("http://{}/text", server.addr());
5657
let res = reqwest::blocking::get(&url).unwrap();
5758
assert_eq!(res.url().as_str(), &url);
59+
assert!(res.history().is_empty());
5860
assert_eq!(res.status(), reqwest::StatusCode::OK);
5961
assert_eq!(res.content_length(), Some(4));
6062

@@ -71,6 +73,7 @@ fn test_response_json() {
7173
let url = format!("http://{}/json", server.addr());
7274
let res = reqwest::blocking::get(&url).unwrap();
7375
assert_eq!(res.url().as_str(), &url);
76+
assert!(res.history().is_empty());
7477
assert_eq!(res.status(), reqwest::StatusCode::OK);
7578
assert_eq!(res.content_length(), Some(7));
7679

@@ -85,6 +88,7 @@ fn test_response_copy_to() {
8588
let url = format!("http://{}/1", server.addr());
8689
let mut res = reqwest::blocking::get(&url).unwrap();
8790
assert_eq!(res.url().as_str(), &url);
91+
assert!(res.history().is_empty());
8892
assert_eq!(res.status(), reqwest::StatusCode::OK);
8993

9094
let mut dst = Vec::new();
@@ -100,6 +104,7 @@ fn test_get() {
100104
let res = reqwest::blocking::get(&url).unwrap();
101105

102106
assert_eq!(res.url().as_str(), &url);
107+
assert!(res.history().is_empty());
103108
assert_eq!(res.status(), reqwest::StatusCode::OK);
104109
assert_eq!(res.remote_addr(), Some(server.addr()));
105110

@@ -126,6 +131,7 @@ fn test_post() {
126131
.unwrap();
127132

128133
assert_eq!(res.url().as_str(), &url);
134+
assert!(res.history().is_empty());
129135
assert_eq!(res.status(), reqwest::StatusCode::OK);
130136
}
131137

@@ -155,6 +161,7 @@ fn test_post_form() {
155161
.expect("request send");
156162

157163
assert_eq!(res.url().as_str(), &url);
164+
assert!(res.history().is_empty());
158165
assert_eq!(res.status(), reqwest::StatusCode::OK);
159166
}
160167

@@ -217,6 +224,7 @@ fn test_default_headers() {
217224
let res = client.get(&url).send().unwrap();
218225

219226
assert_eq!(res.url().as_str(), &url);
227+
assert!(res.history().is_empty());
220228
assert_eq!(res.status(), reqwest::StatusCode::OK);
221229
}
222230

@@ -251,6 +259,7 @@ fn test_override_default_headers() {
251259
.unwrap();
252260

253261
assert_eq!(res.url().as_str(), &url);
262+
assert!(res.history().is_empty());
254263
assert_eq!(res.status(), reqwest::StatusCode::OK);
255264
}
256265

@@ -276,6 +285,7 @@ fn test_appended_headers_not_overwritten() {
276285
.unwrap();
277286

278287
assert_eq!(res.url().as_str(), &url);
288+
assert!(res.history().is_empty());
279289
assert_eq!(res.status(), reqwest::StatusCode::OK);
280290

281291
// make sure this also works with default headers
@@ -299,6 +309,7 @@ fn test_appended_headers_not_overwritten() {
299309
.unwrap();
300310

301311
assert_eq!(res.url().as_str(), &url);
312+
assert!(res.history().is_empty());
302313
assert_eq!(res.status(), reqwest::StatusCode::OK);
303314
}
304315

@@ -403,6 +414,7 @@ fn test_response_no_tls_info_for_http() {
403414

404415
let res = client.get(&url).send().unwrap();
405416
assert_eq!(res.url().as_str(), &url);
417+
assert!(res.history().is_empty());
406418
assert_eq!(res.status(), reqwest::StatusCode::OK);
407419
assert_eq!(res.content_length(), Some(5));
408420
let tls_info = res.extensions().get::<reqwest::tls::TlsInfo>();

tests/client.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ async fn auto_headers() {
5757
.unwrap();
5858

5959
assert_eq!(res.url().as_str(), &url);
60+
assert!(res.history().is_empty());
6061
assert_eq!(res.status(), reqwest::StatusCode::OK);
6162
assert_eq!(res.remote_addr(), Some(server.addr()));
6263
}

tests/redirect.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod support;
44
use http_body_util::BodyExt;
55
use reqwest::Body;
66
use support::server;
7+
use url::Url;
78

89
#[tokio::test]
910
async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
@@ -34,6 +35,14 @@ async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
3435
let dst = format!("http://{}/{}", redirect.addr(), "dst");
3536
let res = client.post(&url).send().await.unwrap();
3637
assert_eq!(res.url().as_str(), dst);
38+
assert_eq!(
39+
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
40+
vec![&url]
41+
);
42+
assert_eq!(
43+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
44+
vec![&url, &dst]
45+
);
3746
assert_eq!(res.status(), reqwest::StatusCode::OK);
3847
assert_eq!(
3948
res.headers().get(reqwest::header::SERVER).unwrap(),
@@ -70,6 +79,14 @@ async fn test_redirect_307_and_308_tries_to_get_again() {
7079
let dst = format!("http://{}/{}", redirect.addr(), "dst");
7180
let res = client.get(&url).send().await.unwrap();
7281
assert_eq!(res.url().as_str(), dst);
82+
assert_eq!(
83+
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
84+
vec![&url]
85+
);
86+
assert_eq!(
87+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
88+
vec![&url, &dst]
89+
);
7390
assert_eq!(res.status(), reqwest::StatusCode::OK);
7491
assert_eq!(
7592
res.headers().get(reqwest::header::SERVER).unwrap(),
@@ -119,6 +136,14 @@ async fn test_redirect_307_and_308_tries_to_post_again() {
119136
let dst = format!("http://{}/{}", redirect.addr(), "dst");
120137
let res = client.post(&url).body("Hello").send().await.unwrap();
121138
assert_eq!(res.url().as_str(), dst);
139+
assert_eq!(
140+
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
141+
vec![&url]
142+
);
143+
assert_eq!(
144+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
145+
vec![&url, &dst]
146+
);
122147
assert_eq!(res.status(), reqwest::StatusCode::OK);
123148
assert_eq!(
124149
res.headers().get(reqwest::header::SERVER).unwrap(),
@@ -163,6 +188,11 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
163188
.send()
164189
.unwrap();
165190
assert_eq!(res.url().as_str(), url);
191+
assert!(res.history().is_empty());
192+
assert_eq!(
193+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
194+
vec![&url]
195+
);
166196
assert_eq!(res.status(), code);
167197
}
168198
}
@@ -253,6 +283,11 @@ async fn test_redirect_policy_can_stop_redirects_without_an_error() {
253283
.unwrap();
254284

255285
assert_eq!(res.url().as_str(), url);
286+
assert!(res.history().is_empty());
287+
assert_eq!(
288+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
289+
vec![&url]
290+
);
256291
assert_eq!(res.status(), reqwest::StatusCode::FOUND);
257292
}
258293

@@ -298,6 +333,11 @@ async fn test_invalid_location_stops_redirect_gh484() {
298333
let res = reqwest::get(&url).await.unwrap();
299334

300335
assert_eq!(res.url().as_str(), url);
336+
assert!(res.history().is_empty());
337+
assert_eq!(
338+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
339+
vec![&url]
340+
);
301341
assert_eq!(res.status(), reqwest::StatusCode::FOUND);
302342
}
303343

@@ -346,6 +386,14 @@ async fn test_redirect_302_with_set_cookies() {
346386
let res = client.get(&url).send().await.unwrap();
347387

348388
assert_eq!(res.url().as_str(), dst);
389+
assert_eq!(
390+
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
391+
vec![&url]
392+
);
393+
assert_eq!(
394+
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
395+
vec![&url, &dst]
396+
);
349397
assert_eq!(res.status(), reqwest::StatusCode::OK);
350398
}
351399

0 commit comments

Comments
 (0)