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
|
||||
|
||||
- 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]
|
||||
- Update `hickory-resolver` dependency to `0.26.1`.
|
||||
- Update `rand` dependency to `0.10`.
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use std::{
|
|||
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 bytes::Bytes;
|
||||
use futures_core::ready;
|
||||
|
|
@ -16,6 +16,7 @@ use crate::{
|
|||
any_body::AnyBody,
|
||||
client::{InvalidUrl, SendRequestError},
|
||||
connect::{ConnectRequest, ConnectResponse},
|
||||
responses::FinalUrl,
|
||||
ClientResponse,
|
||||
};
|
||||
|
||||
|
|
@ -236,7 +237,12 @@ where
|
|||
|
||||
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"),
|
||||
},
|
||||
|
|
@ -625,6 +631,52 @@ mod tests {
|
|||
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]
|
||||
async fn test_remove_sensitive_headers() {
|
||||
fn gen_headers() -> header::HeaderMap {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
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;
|
||||
|
||||
mod json_body;
|
||||
|
|
@ -12,6 +12,9 @@ mod response_body;
|
|||
pub use self::response_body::{MessageBody, ResponseBody};
|
||||
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
|
||||
const DEFAULT_BODY_LIMIT: usize = 2 * 1024 * 1024;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use std::{
|
|||
|
||||
use actix_http::{
|
||||
error::PayloadError, header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Payload,
|
||||
ResponseHead, StatusCode, Version,
|
||||
ResponseHead, StatusCode, Uri, Version,
|
||||
};
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
use bytes::Bytes;
|
||||
|
|
@ -66,6 +66,18 @@ impl<S> ClientResponse<S> {
|
|||
&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.
|
||||
///
|
||||
/// Closure receives the response head and the current body type.
|
||||
|
|
|
|||
Loading…
Reference in New Issue