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
Xuanwo
left a comment
There was a problem hiding this comment.
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.
| .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.
An alternative to using a Mutex is if the client can be cheaply and safely cloned, then the clone can be mutable.
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. |
seanmonstar
left a comment
There was a problem hiding this comment.
This looks really good! Thanks so much!
I think this does everything, right? Is there anything we missed, that you can think of?
| #[cfg(feature = "http2")] | ||
| if self.as_mut().retry_error(&e) { | ||
| continue; | ||
| if e.is_request() { |
There was a problem hiding this comment.
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.
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? |
seanmonstar
left a comment
There was a problem hiding this comment.
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.
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.
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
loopinto three placeFollowRedirect<S, P>to decide whether a redirection is neededHyperServiceandH3Clientto handle thecookies,H3Clienthad implemented theServicetrait as well.TowerRedirectPolicyto handle the other logics, like check theURI,remove_sensitive_headers, insertrefererheaderThere is still exists the
retrylogic in theloop, I don't really want to copy them intoHyperServiceandH3Client.How to test
The first commit add the
LimitandCustompolicy test. The second commit make the implementation and updated the test.Others
Closes #2576