mirror of https://github.com/fafhrd91/actix-web
feat(awc): add ClientResponse::url() for final URL after redirects
Add a method to retrieve the final URL after following redirects. The redirect middleware stores the resolved URL in the response's extensions, and ClientResponse::url() provides convenient access. Returns Some(uri) when the redirect middleware is active (default), None when redirects are disabled. Closes #3403
This commit is contained in:
parent
7ded7ee478
commit
c382f83a0a
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add `ClientResponse::url()` to retrieve the final URL after following redirects.
|
||||||
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
- Add camel-case header controls to `WebsocketsRequest` via `camel_case_headers()` and `set_camel_case_headers()`. [#3953]
|
||||||
- Update `hickory-resolver` dependency to `0.26.1`.
|
- Update `hickory-resolver` dependency to `0.26.1`.
|
||||||
- Update `rand` dependency to `0.10`.
|
- Update `rand` dependency to `0.10`.
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
task::{Context, Poll},
|
task::{Context, Poll},
|
||||||
};
|
};
|
||||||
|
|
||||||
use actix_http::{header, Method, RequestHead, RequestHeadType, StatusCode, Uri};
|
use actix_http::{header, HttpMessage as _, Method, RequestHead, RequestHeadType, StatusCode, Uri};
|
||||||
use actix_service::Service;
|
use actix_service::Service;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_core::ready;
|
use futures_core::ready;
|
||||||
|
|
@ -16,6 +16,7 @@ use crate::{
|
||||||
any_body::AnyBody,
|
any_body::AnyBody,
|
||||||
client::{InvalidUrl, SendRequestError},
|
client::{InvalidUrl, SendRequestError},
|
||||||
connect::{ConnectRequest, ConnectResponse},
|
connect::{ConnectRequest, ConnectResponse},
|
||||||
|
responses::FinalUrl,
|
||||||
ClientResponse,
|
ClientResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -236,7 +237,12 @@ where
|
||||||
|
|
||||||
self.poll(cx)
|
self.poll(cx)
|
||||||
}
|
}
|
||||||
_ => Poll::Ready(Ok(ConnectResponse::Client(res))),
|
_ => {
|
||||||
|
if let Some(final_uri) = uri.take() {
|
||||||
|
res.extensions_mut().insert(FinalUrl(final_uri));
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(ConnectResponse::Client(res)))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_ => unreachable!("ConnectRequest::Tunnel is not handled by Redirect"),
|
_ => unreachable!("ConnectRequest::Tunnel is not handled by Redirect"),
|
||||||
},
|
},
|
||||||
|
|
@ -625,6 +631,52 @@ mod tests {
|
||||||
assert_eq!(res.status().as_u16(), 400);
|
assert_eq!(res.status().as_u16(), 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_redirect_final_url() {
|
||||||
|
let client = ClientBuilder::new().finish();
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(web::resource("/final").route(web::to(|| async {
|
||||||
|
Ok::<_, Error>(HttpResponse::Ok().body("done"))
|
||||||
|
})))
|
||||||
|
.service(web::resource("/").route(web::to(|| async {
|
||||||
|
Ok::<_, Error>(
|
||||||
|
HttpResponse::Found()
|
||||||
|
.append_header(("location", "/final"))
|
||||||
|
.finish(),
|
||||||
|
)
|
||||||
|
})))
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
let final_url = res.url().expect("final URL should be set after redirect");
|
||||||
|
assert_eq!(
|
||||||
|
final_url.path(),
|
||||||
|
"/final",
|
||||||
|
"final URL should be the redirected path"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_no_redirect_final_url() {
|
||||||
|
let client = ClientBuilder::new().finish();
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new().service(web::resource("/").route(web::to(|| async {
|
||||||
|
Ok::<_, Error>(HttpResponse::Ok().body("no redirect"))
|
||||||
|
})))
|
||||||
|
});
|
||||||
|
|
||||||
|
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||||
|
assert_eq!(res.status().as_u16(), 200);
|
||||||
|
|
||||||
|
let final_url = res.url().expect("final URL should be set even without redirect");
|
||||||
|
assert_eq!(final_url.path(), "/");
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_remove_sensitive_headers() {
|
async fn test_remove_sensitive_headers() {
|
||||||
fn gen_headers() -> header::HeaderMap {
|
fn gen_headers() -> header::HeaderMap {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{future::Future, io, pin::Pin, task::Context};
|
use std::{future::Future, io, pin::Pin, task::Context};
|
||||||
|
|
||||||
use actix_http::error::PayloadError;
|
use actix_http::{error::PayloadError, Uri};
|
||||||
use actix_rt::time::Sleep;
|
use actix_rt::time::Sleep;
|
||||||
|
|
||||||
mod json_body;
|
mod json_body;
|
||||||
|
|
@ -12,6 +12,9 @@ mod response_body;
|
||||||
pub use self::response_body::{MessageBody, ResponseBody};
|
pub use self::response_body::{MessageBody, ResponseBody};
|
||||||
pub use self::{json_body::JsonBody, response::ClientResponse};
|
pub use self::{json_body::JsonBody, response::ClientResponse};
|
||||||
|
|
||||||
|
/// Stores the final URL in response extensions after following redirects.
|
||||||
|
pub(crate) struct FinalUrl(pub(crate) Uri);
|
||||||
|
|
||||||
/// Default body size limit: 2 MiB
|
/// Default body size limit: 2 MiB
|
||||||
const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
|
const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::{
|
||||||
|
|
||||||
use actix_http::{
|
use actix_http::{
|
||||||
error::PayloadError, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Payload,
|
error::PayloadError, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Payload,
|
||||||
ResponseHead, StatusCode, Version,
|
ResponseHead, StatusCode, Uri, Version,
|
||||||
};
|
};
|
||||||
use actix_rt::time::{sleep, Sleep};
|
use actix_rt::time::{sleep, Sleep};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
|
|
@ -66,6 +66,18 @@ impl<S> ClientResponse<S> {
|
||||||
&self.head().headers
|
&self.head().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the final URL of this response.
|
||||||
|
///
|
||||||
|
/// When redirects are followed, this returns the URL of the final response (after all
|
||||||
|
/// redirects). Returns `None` if the redirect middleware was not used (e.g., redirects
|
||||||
|
/// were disabled).
|
||||||
|
pub fn url(&self) -> Option<Uri> {
|
||||||
|
self.extensions
|
||||||
|
.borrow()
|
||||||
|
.get::<super::FinalUrl>()
|
||||||
|
.map(|u| u.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Map the current body type to another using a closure. Returns a new response.
|
/// Map the current body type to another using a closure. Returns a new response.
|
||||||
///
|
///
|
||||||
/// Closure receives the response head and the current body type.
|
/// Closure receives the response head and the current body type.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue