-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
feat(redirect): Using FollowRedirect
from tower-http
to handle the redirect
loop
#2617
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(redirect): Using FollowRedirect
from tower-http
to handle the redirect
loop
#2617
Conversation
tower-http
policy instead.tower-http
policy.
b400be6
to
4cf8a0f
Compare
4cf8a0f
to
88c542e
Compare
88c542e
to
3a1064d
Compare
tower-http
policy.FollowRedirect
from tower-http
to handle the redirect
loop
3a1064d
to
02927c4
Compare
738184e
to
f7f14fa
Compare
23e4f11
to
234de4f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, thank you @linyihai for this change.
I understand the motivation that @seanmonstar described in #2576, but the current changes don't seem quite right to me. The implementation feels rather ad-hoc and makes future extensions more difficult.
We're introducing a new dependency for reqwest and a new Mutex
for the internal service. Additionally, we'll need to use tower_http::follow_redirect::ResponseFuture
throughout the codebase.
The API design of tower_http
doesn't align with the core concept of the reqwest client, where Client
only takes &self
for building and sending requests. This is also why we have to add a Mutex
.
What if we keep the redirect policy as it is and encourage users to use tower_http::follow_redirect
outside of reqwest::Client
, since we've already implemented the tower layer? We can build a new API, such as client.layer(tower_http::follow_redirect)
, and gradually deprecate our existing redirect policy.
I'm not sure what @seanmonstar's long-term plan is for reqwest's architecture. If the goal is to transform the reqwest client into a purely tower_layer-based framework, and the current implementation is just a transitional stage, I'm fine with that.
src/async_impl/client.rs
Outdated
@@ -2759,7 +2822,8 @@ impl PendingRequest { | |||
.body(body) | |||
.expect("valid request parts"); | |||
*req.headers_mut() = self.headers.clone(); | |||
ResponseFuture::Default(self.client.hyper.request(req)) | |||
let mut hyper = self.client.hyper.lock().unwrap(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative to using a Mutex is if the client can be cheaply and safely cloned, then the clone can be mutable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea.
faf04e5
to
db0be61
Compare
Hmm, the biggest compatible rustc version of |
Yea, don't try to use an older version. I suspect I might be able to get tower-http to drop its MSRV back down some, it seems it was bumped just for a dependency. If not, then we'll have to increase it in reqwest, but I'm hopeful. |
tower-http 0.6.4 was just published with a lower MSRV, so I'm restarting that CI job. |
f895f06
to
5b9addf
Compare
5b9addf
to
cde40f4
Compare
I'm content with the Ok, I think this PR is ready for review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks really good! Thanks so much!
I think this does everything, right? Is there anything we missed, that you can think of?
@@ -2855,12 +2920,15 @@ impl Future for PendingRequest { | |||
ResponseFuture::Default(r) => match Pin::new(r).poll(cx) { | |||
Poll::Ready(Err(e)) => { | |||
#[cfg(feature = "http2")] | |||
if self.as_mut().retry_error(&e) { | |||
continue; | |||
if e.is_request() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the change here for? Is it because the error was pushing into a reqwest::Error
type instead of the underlying hyper error? That sounds right, now that I type is out...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in the HyperService::call
where had wrapped the crate::error::request
error outside already.
Box::pin(async move { inner.call(req).await.map_err(crate::error::request) })
cde40f4
to
29a0c8f
Compare
I checked the BTW, there is something could be improved in feature:
|
Sounds good! Sorry, it looks merging another PR caused conflicts. Could you take a look at those? I'll merge quickly after so we don't have that again :) |
29a0c8f
to
d4a1e38
Compare
… over the redirect loop
d4a1e38
to
bb43a97
Compare
After merging that PR,I added the @seanmonstar Could you merge this PR if there is nothing left? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much! This is stellar, I appreciate you sticking with it.
Bumps reqwest from 0.12.15 to 0.12.16. Release notes Sourced from reqwest's releases. v0.12.16 Highlights Add ClientBuilder::http3_congestion_bbr() to enable BBR congestion control. Add ClientBuilder::http3_send_grease() to configure whether to send use QUIC grease. Add ClientBuilder::http3_max_field_section_size() to configure the maximum response headers. Add ClientBuilder::tcp_keepalive_interval() to configure TCP probe interval. Add ClientBuilder::tcp_keepalive_retries() to configure TCP probe count. Add Proxy::headers() to add extra headers that should be sent to a proxy. Fix redirect::Policy::limit() which had an off-by-1 error, allowing 1 more redirect than specified. Fix HTTP/3 to support streaming request bodies. (wasm) Fix null bodies when calling Response::bytes_stream(). What's Changed Clarify that Response::content_length() is not derived from a Content-Length header in docs by @babolivier in seanmonstar/reqwest#2588 docs: link to char::REPLACEMENT_CHARACTER by @marcospb19 in seanmonstar/reqwest#1880 feat: add H3 client config support by @smalls0098 in seanmonstar/reqwest#2609 chore: update brotli to v7 by @nyurik in seanmonstar/reqwest#2620 Do not pull in an entirely different DEFLATE implementation just for tests by @Shnatsel in seanmonstar/reqwest#2625 chore: fix some typos in comment by @xixishidibei in seanmonstar/reqwest#2628 fix(wasm): handle null body in bytes_stream by @alongubkin in seanmonstar/reqwest#2632 ClientBuilder::interface on macOS/Solarish OSes by @hawkw in seanmonstar/reqwest#2623 ci: use ubuntu-latest in nightly job by @seanmonstar in seanmonstar/reqwest#2646 feat: BBR congestion control for http3 by @threeninesixseven in seanmonstar/reqwest#2642 feat: Add extentions for Request by @Xuanwo in seanmonstar/reqwest#2647 refactor: Store request timeout in request extensions instead by @Xuanwo in seanmonstar/reqwest#2650 chore: make ci pass by @linyihai in seanmonstar/reqwest#2666 update h3 dependencys by @Ruben2424 in seanmonstar/reqwest#2670 Document reqwest can make TLS and cookie requests with Wasm by @nickbabcock in seanmonstar/reqwest#2661 fix(redirect): make the number of redirects of policy matches its maximum limit. by @linyihai in seanmonstar/reqwest#2664 Exposed hyper tcp keepalive interval and retries parameters by @mackliet in seanmonstar/reqwest#2675 refactor: use hyper-util's proxy::Matcher by @seanmonstar in seanmonstar/reqwest#2681 Support streaming request body in HTTP/3 by @ducaale in seanmonstar/reqwest#2673 refactor: use hyper-util Tunnel by @seanmonstar in seanmonstar/reqwest#2684 Upgrade webpki-roots to 1 by @djc in seanmonstar/reqwest#2688 refactor: remove futures-util unless using stream/multipart/compression/blocking by @paolobarbolini in seanmonstar/reqwest#2692 chore: replace rustls-pemfile with rustls-pki-types by @tottoto in seanmonstar/reqwest#2541 Ensure H3ResponseFuture Implements Sync by @ducaale in seanmonstar/reqwest#2685 feat(redirect): Using FollowRedirect from tower-http to handle the redirect loop by @linyihai in seanmonstar/reqwest#2617 feat: add customizable headers in proxy mode by @chanbengz in seanmonstar/reqwest#2600 Prepare v0.12.16 by @seanmonstar in seanmonstar/reqwest#2694 New Contributors @babolivier made their first contribution in seanmonstar/reqwest#2588 @marcospb19 made their first contribution in seanmonstar/reqwest#1880 @smalls0098 made their first contribution in seanmonstar/reqwest#2609 @Shnatsel made their first contribution in seanmonstar/reqwest#2625 @xixishidibei made their first contribution in seanmonstar/reqwest#2628 @alongubkin made their first contribution in seanmonstar/reqwest#2632 ... (truncated) Changelog Sourced from reqwest's changelog. v0.12.16 Add ClientBuilder::http3_congestion_bbr() to enable BBR congestion control. Add ClientBuilder::http3_send_grease() to configure whether to send use QUIC grease. Add ClientBuilder::http3_max_field_section_size() to configure the maximum response headers. Add ClientBuilder::tcp_keepalive_interval() to configure TCP probe interval. Add ClientBuilder::tcp_keepalive_retries() to configure TCP probe count. Add Proxy::headers() to add extra headers that should be sent to a proxy. Fix redirect::Policy::limit() which had an off-by-1 error, allowing 1 more redirect than specified. Fix HTTP/3 to support streaming request bodies. (wasm) Fix null bodies when calling Response::bytes_stream(). Commits 99259cb v0.12.16 57670ac feat: add customizable headers for reqwest::Proxy (#2600) d9cf60e refactor: Using FollowRedirect from tower-http to handle the redirect l... 75f62f2 fix: ensure H3ResponseFuture is sync (#2685) 0e1d188 chore: replace rustls-pemfile with rustls-pki-types (#2541) 705b613 refactor: remove futures-util unless using stream/multipart/compression... 7b80718 Upgrade webpki-roots to 1 (#2688) 152a560 refactor: use hyper-util Tunnel (#2684) df09c9e feat: support streaming request body in HTTP/3 (#2673) 4ec1fe5 refactor: use hyper-util's proxy::Matcher (#2681) Additional commits viewable in compare view Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase. Dependabot commands and options You can trigger Dependabot actions by commenting on this PR: @dependabot rebase will rebase this PR @dependabot recreate will recreate this PR, overwriting any edits that have been made to it @dependabot merge will merge this PR after your CI passes on it @dependabot squash and merge will squash and merge this PR after your CI passes on it @dependabot cancel merge will cancel a previously requested merge and block automerging @dependabot reopen will reopen this PR if it is closed @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually @dependabot show <dependency name> ignore conditions will show all of the ignore conditions of the specified dependency @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
if next_url.scheme() != "http" && next_url.scheme() != "https" { | ||
return Err(crate::error::url_bad_scheme(next_url)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might be missing something, but isn't this a pretty bad breaking change?
Given a request that returns a redirect to something else, say myscheme://
, I have previously been able to get that response with a client with Policy::none()
. Now I am getting this error instead, as the next URL is expected to be executable without checking the policy first.
I guess checks on next_url
should only be done if the policy returned Follow
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess checks on next_url should only be done if the policy returned Follow.
Thanks for point this out. I would make a patch for it.
What does this want to do
Working on #2576
This PR mainly split the redirect logic in the
loop
into three placeFollowRedirect<S, P>
to decide whether a redirection is neededHyperService
andH3Client
to handle thecookies
,H3Client
had implemented theService
trait as well.TowerRedirectPolicy
to handle the other logics, like check theURI
,remove_sensitive_headers
, insertreferer
headerThere is still exists the
retry
logic in theloop
, I don't really want to copy them intoHyperService
andH3Client
.How to test
The first commit add the
Limit
andCustom
policy test. The second commit make the implementation and updated the test.Others
Closes #2576