Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/async_impl/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use quinn::VarInt;
use tokio::time::Sleep;
use tower::util::BoxCloneSyncServiceLayer;
use tower::{Layer, Service};
use tower_http::follow_redirect::FollowRedirect;
use tower_http::follow_redirect::{FollowRedirect, UriAndPolicyExtensions};

/// An asynchronous `Client` to make Requests with.
///
Expand Down Expand Up @@ -995,7 +995,7 @@ impl ClientBuilder {
p
};

let hyper = FollowRedirect::with_policy(hyper_service, policy.clone());
let hyper = FollowRedirect::with_policy_extension(hyper_service, policy.clone());

Ok(Client {
inner: Arc::new(ClientRef {
Expand All @@ -1015,7 +1015,7 @@ impl ClientBuilder {
config.pool_idle_timeout,
config.cookie_store,
);
Some(FollowRedirect::with_policy(h3_service, policy))
Some(FollowRedirect::with_policy_extension(h3_service, policy))
}
None => None,
},
Expand Down Expand Up @@ -2762,9 +2762,9 @@ struct ClientRef {
#[cfg(feature = "cookies")]
cookie_store: Option<Arc<dyn cookie::CookieStore>>,
headers: HeaderMap,
hyper: FollowRedirect<HyperService, TowerRedirectPolicy>,
hyper: FollowRedirect<HyperService, TowerRedirectPolicy, UriAndPolicyExtensions>,
#[cfg(feature = "http3")]
h3_client: Option<FollowRedirect<H3Client, TowerRedirectPolicy>>,
h3_client: Option<FollowRedirect<H3Client, TowerRedirectPolicy, UriAndPolicyExtensions>>,
referer: bool,
request_timeout: RequestConfig<RequestTimeout>,
read_timeout: Option<Duration>,
Expand Down Expand Up @@ -2845,9 +2845,23 @@ pin_project! {
}

enum ResponseFuture {
Default(tower_http::follow_redirect::ResponseFuture<HyperService, Body, TowerRedirectPolicy>),
Default(
tower_http::follow_redirect::ResponseFuture<
HyperService,
Body,
TowerRedirectPolicy,
UriAndPolicyExtensions,
>,
),
#[cfg(feature = "http3")]
H3(tower_http::follow_redirect::ResponseFuture<H3Client, Body, TowerRedirectPolicy>),
H3(
tower_http::follow_redirect::ResponseFuture<
H3Client,
Body,
TowerRedirectPolicy,
UriAndPolicyExtensions,
>,
),
}

impl PendingRequest {
Expand Down
21 changes: 21 additions & 0 deletions src/async_impl/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ use serde::de::DeserializeOwned;
#[cfg(feature = "json")]
use serde_json;
use tokio::time::Sleep;
use tower_http::follow_redirect::FollowedPolicy;
use url::Url;

use super::body::Body;
use super::decoder::{Accepts, Decoder};
use crate::async_impl::body::ResponseBody;
#[cfg(feature = "cookies")]
use crate::cookie;
use crate::redirect::TowerRedirectPolicy;

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

/// Get all the intermediate `Url`s traversed by redirects.
#[inline]
pub fn history(&self) -> &[Url] {
self.extensions()
.get::<FollowedPolicy<TowerRedirectPolicy>>()
.map_or(&[], |p| &p.0.urls)
}

/// Get all the `Url`s, in sequential order, that were requested,
/// including any redirects and the final url.
#[inline]
pub fn all_urls(&self) -> impl Iterator<Item = &Url> {
self.history()
.iter()
.chain(std::iter::once(self.url.as_ref()))
}

/// Get the remote address used to get this `Response`.
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.res
Expand Down Expand Up @@ -509,5 +528,7 @@ mod tests {

assert_eq!(response.status(), 200);
assert_eq!(*response.url(), url);
assert!(response.history().is_empty());
assert_eq!(response.all_urls().collect::<Vec<_>>(), vec![&url]);
}
}
13 changes: 13 additions & 0 deletions src/blocking/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,19 @@ impl Response {
self.inner.url()
}

/// Get all the intermediate `Url`s traversed by redirects.
#[inline]
pub fn history(&self) -> &[Url] {
self.inner.history()
}

/// Get all the `Url`s, in sequential order, that were requested,
/// including any redirects and the final url.
#[inline]
pub fn all_urls(&self) -> impl Iterator<Item = &Url> {
self.inner.all_urls()
}

/// Get the remote address used to get this `Response`.
///
/// # Example
Expand Down
2 changes: 1 addition & 1 deletion src/redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ impl StdError for TooManyRedirects {}
pub(crate) struct TowerRedirectPolicy {
policy: Arc<Policy>,
referer: bool,
urls: Vec<Url>,
pub(crate) urls: Vec<Url>,
https_only: bool,
}

Expand Down
12 changes: 12 additions & 0 deletions tests/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ fn test_response_text() {
let url = format!("http://{}/text", server.addr());
let res = reqwest::blocking::get(&url).unwrap();
assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.content_length(), Some(5));

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

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

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

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

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.remote_addr(), Some(server.addr()));

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

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

Expand Down Expand Up @@ -155,6 +161,7 @@ fn test_post_form() {
.expect("request send");

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

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

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

Expand Down Expand Up @@ -251,6 +259,7 @@ fn test_override_default_headers() {
.unwrap();

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

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

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);

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

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

Expand Down Expand Up @@ -403,6 +414,7 @@ fn test_response_no_tls_info_for_http() {

let res = client.get(&url).send().unwrap();
assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.content_length(), Some(5));
let tls_info = res.extensions().get::<reqwest::tls::TlsInfo>();
Expand Down
1 change: 1 addition & 0 deletions tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ async fn auto_headers() {
.unwrap();

assert_eq!(res.url().as_str(), &url);
assert!(res.history().is_empty());
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(res.remote_addr(), Some(server.addr()));
}
Expand Down
48 changes: 48 additions & 0 deletions tests/redirect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod support;
use http_body_util::BodyExt;
use reqwest::Body;
use support::server;
use url::Url;

#[tokio::test]
async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
Expand Down Expand Up @@ -34,6 +35,14 @@ async fn test_redirect_301_and_302_and_303_changes_post_to_get() {
let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.post(&url).send().await.unwrap();
assert_eq!(res.url().as_str(), dst);
assert_eq!(
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url, &dst]
);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
Expand Down Expand Up @@ -70,6 +79,14 @@ async fn test_redirect_307_and_308_tries_to_get_again() {
let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.get(&url).send().await.unwrap();
assert_eq!(res.url().as_str(), dst);
assert_eq!(
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url, &dst]
);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
Expand Down Expand Up @@ -119,6 +136,14 @@ async fn test_redirect_307_and_308_tries_to_post_again() {
let dst = format!("http://{}/{}", redirect.addr(), "dst");
let res = client.post(&url).body("Hello").send().await.unwrap();
assert_eq!(res.url().as_str(), dst);
assert_eq!(
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url, &dst]
);
assert_eq!(res.status(), reqwest::StatusCode::OK);
assert_eq!(
res.headers().get(reqwest::header::SERVER).unwrap(),
Expand Down Expand Up @@ -163,6 +188,11 @@ fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
.send()
.unwrap();
assert_eq!(res.url().as_str(), url);
assert!(res.history().is_empty());
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(res.status(), code);
}
}
Expand Down Expand Up @@ -253,6 +283,11 @@ async fn test_redirect_policy_can_stop_redirects_without_an_error() {
.unwrap();

assert_eq!(res.url().as_str(), url);
assert!(res.history().is_empty());
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(res.status(), reqwest::StatusCode::FOUND);
}

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

assert_eq!(res.url().as_str(), url);
assert!(res.history().is_empty());
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(res.status(), reqwest::StatusCode::FOUND);
}

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

assert_eq!(res.url().as_str(), dst);
assert_eq!(
res.history().iter().map(Url::as_str).collect::<Vec<_>>(),
vec![&url]
);
assert_eq!(
res.all_urls().map(Url::as_str).collect::<Vec<_>>(),
vec![&url, &dst]
);
assert_eq!(res.status(), reqwest::StatusCode::OK);
}

Expand Down