wreq-rb 0.5.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +1922 -397
  3. data/LICENSE +203 -0
  4. data/README.md +19 -15
  5. data/ext/wreq_rb/Cargo.toml +4 -6
  6. data/ext/wreq_rb/src/client.rs +41 -48
  7. data/lib/wreq-rb/version.rb +1 -1
  8. data/patches/0001-add-transfer-size-tracking.patch +76 -67
  9. data/vendor/wreq/Cargo.toml +119 -71
  10. data/vendor/wreq/README.md +25 -20
  11. data/vendor/wreq/bench/http1.rs +25 -0
  12. data/vendor/wreq/bench/http1_over_tls.rs +25 -0
  13. data/vendor/wreq/bench/http2.rs +25 -0
  14. data/vendor/wreq/bench/http2_over_tls.rs +25 -0
  15. data/vendor/wreq/bench/support/bench.rs +91 -0
  16. data/vendor/wreq/bench/support/client.rs +217 -0
  17. data/vendor/wreq/bench/support/server.rs +188 -0
  18. data/vendor/wreq/bench/support.rs +56 -0
  19. data/vendor/wreq/examples/cert_store.rs +4 -4
  20. data/vendor/wreq/examples/{emulation.rs → emulate.rs} +2 -2
  21. data/vendor/wreq/examples/http2_websocket.rs +2 -2
  22. data/vendor/wreq/examples/keylog.rs +3 -3
  23. data/vendor/wreq/examples/{request_with_emulation.rs → request_with_emulate.rs} +2 -2
  24. data/vendor/wreq/examples/rt.rs +23 -0
  25. data/vendor/wreq/src/client/body.rs +23 -61
  26. data/vendor/wreq/src/client/emulate.rs +119 -0
  27. data/vendor/wreq/src/client/{http/future.rs → future.rs} +11 -32
  28. data/vendor/wreq/src/client/{http → layer}/client/pool.rs +66 -61
  29. data/vendor/wreq/src/client/{http → layer}/client.rs +416 -270
  30. data/vendor/wreq/src/client/layer/config.rs +27 -6
  31. data/vendor/wreq/src/client/layer/decoder.rs +9 -4
  32. data/vendor/wreq/src/client/layer/redirect/future.rs +6 -3
  33. data/vendor/wreq/src/client/layer/redirect.rs +4 -5
  34. data/vendor/wreq/src/client/layer/retry.rs +8 -5
  35. data/vendor/wreq/src/client/layer/timeout/body.rs +15 -6
  36. data/vendor/wreq/src/client/layer/timeout/future.rs +23 -18
  37. data/vendor/wreq/src/client/layer/timeout.rs +24 -74
  38. data/vendor/wreq/src/client/layer.rs +1 -2
  39. data/vendor/wreq/src/client/multipart.rs +137 -154
  40. data/vendor/wreq/src/client/request.rs +202 -118
  41. data/vendor/wreq/src/client/response.rs +46 -45
  42. data/vendor/wreq/src/client/upgrade.rs +15 -0
  43. data/vendor/wreq/src/client/ws.rs +73 -25
  44. data/vendor/wreq/src/client.rs +1655 -17
  45. data/vendor/wreq/src/config.rs +11 -11
  46. data/vendor/wreq/src/{client/conn → conn}/connector.rs +139 -137
  47. data/vendor/wreq/src/conn/descriptor.rs +143 -0
  48. data/vendor/wreq/src/conn/http.rs +484 -0
  49. data/vendor/wreq/src/conn/net/io.rs +75 -0
  50. data/vendor/wreq/src/conn/net/tcp/compio.rs +71 -0
  51. data/vendor/wreq/src/conn/net/tcp/tokio.rs +57 -0
  52. data/vendor/wreq/src/conn/net/tcp.rs +561 -0
  53. data/vendor/wreq/src/conn/net/uds/compio.rs +60 -0
  54. data/vendor/wreq/src/{client/conn/uds.rs → conn/net/uds/tokio.rs} +18 -12
  55. data/vendor/wreq/src/conn/net/uds.rs +11 -0
  56. data/vendor/wreq/src/conn/net.rs +130 -0
  57. data/vendor/wreq/src/{client/conn → conn}/proxy/socks.rs +2 -9
  58. data/vendor/wreq/src/{client/conn → conn}/proxy/tunnel.rs +21 -56
  59. data/vendor/wreq/src/conn/tls_info.rs +47 -0
  60. data/vendor/wreq/src/{client/conn.rs → conn.rs} +202 -54
  61. data/vendor/wreq/src/cookie.rs +302 -142
  62. data/vendor/wreq/src/dns/gai/compio.rs +77 -0
  63. data/vendor/wreq/src/dns/gai/tokio.rs +90 -0
  64. data/vendor/wreq/src/dns/gai.rs +14 -164
  65. data/vendor/wreq/src/dns/hickory.rs +16 -23
  66. data/vendor/wreq/src/dns/resolve.rs +7 -41
  67. data/vendor/wreq/src/dns.rs +90 -7
  68. data/vendor/wreq/src/error.rs +57 -31
  69. data/vendor/wreq/src/ext.rs +25 -0
  70. data/vendor/wreq/src/group.rs +211 -0
  71. data/vendor/wreq/src/header.rs +100 -112
  72. data/vendor/wreq/src/lib.rs +124 -73
  73. data/vendor/wreq/src/proxy.rs +6 -20
  74. data/vendor/wreq/src/redirect.rs +1 -1
  75. data/vendor/wreq/src/rt.rs +208 -0
  76. data/vendor/wreq/src/sync.rs +97 -98
  77. data/vendor/wreq/src/tls/compress.rs +124 -0
  78. data/vendor/wreq/src/tls/conn/ext.rs +54 -45
  79. data/vendor/wreq/src/tls/conn/service.rs +14 -18
  80. data/vendor/wreq/src/tls/conn.rs +169 -241
  81. data/vendor/wreq/src/tls/keylog.rs +68 -5
  82. data/vendor/wreq/src/tls/session.rs +205 -0
  83. data/vendor/wreq/src/tls/{x509 → trust}/identity.rs +4 -21
  84. data/vendor/wreq/src/tls/{x509/parser.rs → trust/parse.rs} +1 -1
  85. data/vendor/wreq/src/tls/{x509 → trust}/store.rs +42 -81
  86. data/vendor/wreq/src/tls/{x509.rs → trust.rs} +8 -2
  87. data/vendor/wreq/src/tls.rs +489 -25
  88. data/vendor/wreq/src/trace.rs +0 -12
  89. data/vendor/wreq/src/util.rs +1 -1
  90. data/vendor/wreq/tests/badssl.rs +10 -10
  91. data/vendor/wreq/tests/client.rs +3 -9
  92. data/vendor/wreq/tests/cookie.rs +6 -8
  93. data/vendor/wreq/tests/{emulation.rs → emulate.rs} +130 -22
  94. data/vendor/wreq/tests/multipart.rs +43 -1
  95. data/vendor/wreq/tests/proxy.rs +1 -1
  96. data/vendor/wreq/tests/support/layer.rs +1 -0
  97. metadata +49 -71
  98. data/patches/0002-add-cancel-connections.patch +0 -181
  99. data/vendor/wreq/src/client/conn/conn.rs +0 -231
  100. data/vendor/wreq/src/client/conn/http.rs +0 -1023
  101. data/vendor/wreq/src/client/conn/tls_info.rs +0 -98
  102. data/vendor/wreq/src/client/core/body/incoming.rs +0 -485
  103. data/vendor/wreq/src/client/core/body/length.rs +0 -118
  104. data/vendor/wreq/src/client/core/body.rs +0 -34
  105. data/vendor/wreq/src/client/core/common/buf.rs +0 -149
  106. data/vendor/wreq/src/client/core/common/rewind.rs +0 -141
  107. data/vendor/wreq/src/client/core/common/watch.rs +0 -76
  108. data/vendor/wreq/src/client/core/common.rs +0 -3
  109. data/vendor/wreq/src/client/core/conn/http1.rs +0 -342
  110. data/vendor/wreq/src/client/core/conn/http2.rs +0 -307
  111. data/vendor/wreq/src/client/core/conn.rs +0 -11
  112. data/vendor/wreq/src/client/core/dispatch.rs +0 -299
  113. data/vendor/wreq/src/client/core/error.rs +0 -435
  114. data/vendor/wreq/src/client/core/ext.rs +0 -201
  115. data/vendor/wreq/src/client/core/http1.rs +0 -178
  116. data/vendor/wreq/src/client/core/http2.rs +0 -483
  117. data/vendor/wreq/src/client/core/proto/h1/conn.rs +0 -988
  118. data/vendor/wreq/src/client/core/proto/h1/decode.rs +0 -1170
  119. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +0 -684
  120. data/vendor/wreq/src/client/core/proto/h1/encode.rs +0 -580
  121. data/vendor/wreq/src/client/core/proto/h1/io.rs +0 -879
  122. data/vendor/wreq/src/client/core/proto/h1/role.rs +0 -694
  123. data/vendor/wreq/src/client/core/proto/h1.rs +0 -104
  124. data/vendor/wreq/src/client/core/proto/h2/client.rs +0 -650
  125. data/vendor/wreq/src/client/core/proto/h2/ping.rs +0 -539
  126. data/vendor/wreq/src/client/core/proto/h2.rs +0 -379
  127. data/vendor/wreq/src/client/core/proto/headers.rs +0 -138
  128. data/vendor/wreq/src/client/core/proto.rs +0 -58
  129. data/vendor/wreq/src/client/core/rt/bounds.rs +0 -57
  130. data/vendor/wreq/src/client/core/rt/timer.rs +0 -150
  131. data/vendor/wreq/src/client/core/rt/tokio.rs +0 -99
  132. data/vendor/wreq/src/client/core/rt.rs +0 -25
  133. data/vendor/wreq/src/client/core/upgrade.rs +0 -267
  134. data/vendor/wreq/src/client/core.rs +0 -16
  135. data/vendor/wreq/src/client/emulation.rs +0 -161
  136. data/vendor/wreq/src/client/http/client/error.rs +0 -142
  137. data/vendor/wreq/src/client/http/client/exec.rs +0 -29
  138. data/vendor/wreq/src/client/http/client/extra.rs +0 -77
  139. data/vendor/wreq/src/client/http/client/util.rs +0 -104
  140. data/vendor/wreq/src/client/http.rs +0 -1629
  141. data/vendor/wreq/src/client/layer/config/options.rs +0 -156
  142. data/vendor/wreq/src/client/layer/cookie.rs +0 -161
  143. data/vendor/wreq/src/hash.rs +0 -143
  144. data/vendor/wreq/src/tls/conn/cache.rs +0 -123
  145. data/vendor/wreq/src/tls/conn/cert_compression.rs +0 -125
  146. data/vendor/wreq/src/tls/keylog/handle.rs +0 -64
  147. data/vendor/wreq/src/tls/options.rs +0 -464
  148. /data/vendor/wreq/src/client/{http → layer}/client/lazy.rs +0 -0
  149. /data/vendor/wreq/src/{client/conn → conn}/proxy.rs +0 -0
  150. /data/vendor/wreq/src/{client/conn → conn}/verbose.rs +0 -0
@@ -1,124 +1,69 @@
1
- #[macro_use]
2
- pub mod error;
3
- mod exec;
4
- pub mod extra;
1
+ //! Much of this codebase is adapted and refined from [hyper](https://github.com/hyperium/hyper-util),
2
+
5
3
  mod lazy;
6
4
  mod pool;
7
- mod util;
8
5
 
9
6
  use std::{
7
+ error::Error as StdError,
8
+ fmt,
10
9
  future::Future,
11
- num::NonZeroU32,
12
- pin::Pin,
13
- sync::Arc,
10
+ num::NonZeroUsize,
14
11
  task::{self, Poll},
15
12
  time::Duration,
16
13
  };
17
14
 
18
15
  use bytes::Bytes;
19
- use futures_util::future::{Either, FutureExt, TryFutureExt};
16
+ use futures_util::future::{self, BoxFuture, Either, FutureExt, TryFutureExt};
20
17
  use http::{
21
18
  HeaderValue, Method, Request, Response, Uri, Version,
22
19
  header::{HOST, PROXY_AUTHORIZATION},
20
+ uri::{Authority, PathAndQuery, Scheme},
23
21
  };
24
22
  use http_body::Body;
25
23
  use pool::Ver;
26
24
  use tokio::io::{AsyncRead, AsyncWrite};
27
- use tower::util::Oneshot;
28
-
29
- use self::{
30
- error::{ClientConnectError, Error, ErrorKind, TrySendError},
31
- exec::Exec,
32
- extra::{ConnectExtra, ConnectIdentity},
33
- lazy::{Started as Lazy, lazy},
25
+ use tower::{BoxError, util::Oneshot};
26
+ use wreq_proto::{
27
+ body::Incoming,
28
+ conn::{self, TrySendError as ConnTrySendError},
29
+ http1::Http1Options,
30
+ http2::Http2Options,
31
+ rt::Executor as _,
34
32
  };
33
+ #[cfg(feature = "cookies")]
34
+ use {
35
+ crate::cookie::{CookieStore, Cookies},
36
+ http::header::COOKIE,
37
+ std::sync::Arc,
38
+ };
39
+
40
+ use self::lazy::{Started as Lazy, lazy};
35
41
  use crate::{
36
- client::{
37
- conn::{Connected, Connection},
38
- core::{
39
- body::Incoming,
40
- conn::{self, TrySendError as ConnTrySendError},
41
- http1::Http1Options,
42
- http2::Http2Options,
43
- rt::{ArcTimer, Executor, Timer},
44
- },
45
- layer::config::RequestOptions,
46
- },
42
+ client::layer::config::RequestOptions,
47
43
  config::RequestConfig,
48
- error::BoxError,
49
- hash::{HASHER, HashMemo},
50
- tls::AlpnProtocol,
44
+ conn::{
45
+ Connected, Connection,
46
+ descriptor::{ConnectionDescriptor, ConnectionId},
47
+ proxy,
48
+ },
49
+ error::ProxyConnect,
50
+ rt::{Executor, Timer},
51
51
  };
52
52
 
53
- type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
54
-
55
- /// Parameters required to initiate a new connection.
56
- ///
57
- /// [`ConnectRequest`] holds the target URI and all connection-specific options
58
- /// (protocol, proxy, TCP/TLS settings) needed to establish a new network connection.
59
- /// Used by connectors to drive the connection setup process.
60
- #[must_use]
61
- #[derive(Clone)]
62
- pub struct ConnectRequest {
63
- uri: Uri,
64
- identifier: ConnectIdentity,
65
- }
66
-
67
- // ===== impl ConnectRequest =====
68
-
69
- impl ConnectRequest {
70
- /// Create a new [`ConnectRequest`] with the given URI and identifier.
71
- #[inline]
72
- fn new<T>(uri: Uri, identifier: T) -> ConnectRequest
73
- where
74
- T: Into<Option<RequestOptions>>,
75
- {
76
- ConnectRequest {
77
- uri: uri.clone(),
78
- identifier: Arc::new(HashMemo::with_hasher(
79
- ConnectExtra::new(uri, identifier),
80
- HASHER,
81
- )),
82
- }
83
- }
84
-
85
- /// Returns a reference to the [`Uri`].
86
- #[inline]
87
- pub fn uri(&self) -> &Uri {
88
- &self.uri
89
- }
90
-
91
- /// Returns a mutable reference to the [`Uri`].
92
- #[inline]
93
- pub fn uri_mut(&mut self) -> &mut Uri {
94
- &mut self.uri
95
- }
96
-
97
- /// Returns a unique [`ConnectIdentity`].
98
- #[inline]
99
- pub(crate) fn identify(&self) -> ConnectIdentity {
100
- self.identifier.clone()
101
- }
102
-
103
- /// Returns the [`ConnectExtra`] connection extra.
104
- #[inline]
105
- pub(crate) fn extra(&self) -> &ConnectExtra {
106
- self.identifier.as_ref().as_ref()
107
- }
108
- }
109
-
110
53
  /// A HttpClient to make outgoing HTTP requests.
111
54
  ///
112
55
  /// `HttpClient` is cheap to clone and cloning is the recommended way to share a `HttpClient`. The
113
56
  /// underlying connection pool will be reused.
114
57
  #[must_use]
115
- pub struct HttpClient<C, B> {
58
+ pub(crate) struct HttpClient<C, B> {
116
59
  config: Config,
117
60
  connector: C,
118
- exec: Exec,
61
+ exec: Executor,
119
62
  h1_builder: conn::http1::Builder,
120
- h2_builder: conn::http2::Builder<Exec>,
121
- pool: pool::Pool<PoolClient<B>, ConnectIdentity>,
63
+ h2_builder: conn::http2::Builder<Executor>,
64
+ pool: pool::Pool<PoolClient<B>, ConnectionId>,
65
+ #[cfg(feature = "cookies")]
66
+ cookie_store: RequestConfig<Arc<dyn CookieStore>>,
122
67
  }
123
68
 
124
69
  #[derive(Clone, Copy)]
@@ -128,21 +73,71 @@ struct Config {
128
73
  ver: Ver,
129
74
  }
130
75
 
76
+ #[derive(Debug)]
77
+ pub struct Error {
78
+ kind: ErrorKind,
79
+ source: Option<BoxError>,
80
+ #[allow(unused)]
81
+ connect_info: Option<Connected>,
82
+ }
83
+
84
+ #[derive(Debug)]
85
+ enum ErrorKind {
86
+ Canceled,
87
+ ChannelClosed,
88
+ Connect,
89
+ ProxyConnect,
90
+ UserUnsupportedRequestMethod,
91
+ UserUnsupportedVersion,
92
+ UserAbsoluteUriRequired,
93
+ SendRequest,
94
+ }
95
+
96
+ enum ClientConnectError {
97
+ Normal(Error),
98
+ CheckoutIsClosed(pool::Error),
99
+ }
100
+
101
+ #[allow(clippy::large_enum_variant)]
102
+ enum TrySendError<B> {
103
+ Retryable {
104
+ error: Error,
105
+ req: Request<B>,
106
+ connection_reused: bool,
107
+ },
108
+ Nope(Error),
109
+ }
110
+
111
+ macro_rules! e {
112
+ ($kind:ident) => {
113
+ Error {
114
+ kind: ErrorKind::$kind,
115
+ source: None,
116
+ connect_info: None,
117
+ }
118
+ };
119
+ ($kind:ident, $src:expr) => {
120
+ Error {
121
+ kind: ErrorKind::$kind,
122
+ source: Some($src.into()),
123
+ connect_info: None,
124
+ }
125
+ };
126
+ }
127
+
131
128
  // ===== impl HttpClient =====
132
129
 
133
130
  impl HttpClient<(), ()> {
134
- /// Create a builder to configure a new `HttpClient`.
135
- pub fn builder<E>(executor: E) -> Builder
136
- where
137
- E: Executor<BoxSendFuture> + Send + Sync + Clone + 'static,
138
- {
131
+ /// Create a builder to configure a new [`HttpClient`].
132
+ #[inline]
133
+ pub fn builder(executor: Executor) -> Builder {
139
134
  Builder::new(executor)
140
135
  }
141
136
  }
142
137
 
143
138
  impl<C, B> HttpClient<C, B>
144
139
  where
145
- C: tower::Service<ConnectRequest> + Clone + Send + Sync + 'static,
140
+ C: tower::Service<ConnectionDescriptor> + Clone + Send + Sync + 'static,
146
141
  C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
147
142
  C::Error: Into<BoxError>,
148
143
  C::Future: Unpin + Send + 'static,
@@ -150,64 +145,69 @@ where
150
145
  B::Data: Send,
151
146
  B::Error: Into<BoxError>,
152
147
  {
153
- /// Send a constructed `Request` using this `HttpClient`.
154
- fn request(&self, mut req: Request<B>) -> ResponseFuture {
148
+ fn request(
149
+ &self,
150
+ mut req: Request<B>,
151
+ ) -> BoxFuture<'static, Result<Response<Incoming>, BoxError>> {
155
152
  let is_http_connect = req.method() == Method::CONNECT;
156
153
  // Validate HTTP version early
157
154
  match req.version() {
158
155
  Version::HTTP_10 if is_http_connect => {
159
156
  warn!("CONNECT is not allowed for HTTP/1.0");
160
- return ResponseFuture::new(futures_util::future::err(Error::new_kind(
161
- ErrorKind::UserUnsupportedRequestMethod,
162
- )));
157
+ return Box::pin(future::err(e!(UserUnsupportedRequestMethod).into()));
163
158
  }
164
159
  Version::HTTP_10 | Version::HTTP_11 | Version::HTTP_2 => {}
165
160
  // completely unsupported HTTP version (like HTTP/0.9)!
166
161
  _unsupported => {
167
162
  warn!("Request has unsupported version: {:?}", _unsupported);
168
- return ResponseFuture::new(futures_util::future::err(Error::new_kind(
169
- ErrorKind::UserUnsupportedVersion,
170
- )));
163
+ return Box::pin(future::err(e!(UserUnsupportedVersion).into()));
171
164
  }
172
165
  };
173
166
 
174
167
  // Extract and normalize URI
175
- let uri = match util::normalize_uri(&mut req, is_http_connect) {
168
+ let uri = match normalize_uri(&mut req, is_http_connect) {
176
169
  Ok(uri) => uri,
177
- Err(err) => return ResponseFuture::new(futures_util::future::err(err)),
170
+ Err(err) => {
171
+ return Box::pin(future::err(e!(UserAbsoluteUriRequired, err).into()));
172
+ }
178
173
  };
179
174
 
180
175
  let mut this = self.clone();
181
176
 
182
- // Extract per-request options from the request extensions and apply them to the client
183
- // builder. This allows each request to override HTTP/1 and HTTP/2 options as
184
- // needed.
185
- let options = RequestConfig::<RequestOptions>::remove(req.extensions_mut());
186
-
187
- // Apply HTTP/1 and HTTP/2 options if provided
188
- if let Some(opts) = options.as_ref().map(RequestOptions::transport_opts) {
189
- if let Some(opts) = opts.http1_options() {
190
- this.h1_builder.options(opts.clone());
177
+ // Extract per-request options from the request extensions and apply them to the client.
178
+ let descriptor = {
179
+ let RequestOptions {
180
+ group,
181
+ proxy,
182
+ version,
183
+ tls_options,
184
+ http1_options,
185
+ http2_options,
186
+ socket_bind_options,
187
+ } = RequestConfig::<RequestOptions>::remove(req.extensions_mut()).unwrap_or_default();
188
+
189
+ if let Some(opts) = http1_options {
190
+ this.h1_builder = this.h1_builder.options(opts);
191
191
  }
192
-
193
- if let Some(opts) = opts.http2_options() {
194
- this.h2_builder.options(opts.clone());
192
+ if let Some(opts) = http2_options {
193
+ this.h2_builder = this.h2_builder.options(opts);
195
194
  }
196
- }
197
195
 
198
- let connect_req = ConnectRequest::new(uri, options);
199
- ResponseFuture::new(this.send_request(req, connect_req))
196
+ ConnectionDescriptor::new(uri, group, proxy, version, tls_options, socket_bind_options)
197
+ };
198
+
199
+ Box::pin(this.send_request(req, descriptor).map_err(Into::into))
200
200
  }
201
201
 
202
202
  async fn send_request(
203
203
  self,
204
204
  mut req: Request<B>,
205
- connect_req: ConnectRequest,
205
+ descriptor: ConnectionDescriptor,
206
206
  ) -> Result<Response<Incoming>, Error> {
207
207
  let uri = req.uri().clone();
208
208
 
209
209
  loop {
210
- req = match self.try_send_request(req, connect_req.clone()).await {
210
+ req = match self.try_send_request(req, descriptor.clone()).await {
211
211
  Ok(resp) => return Ok(resp),
212
212
  Err(TrySendError::Nope(err)) => return Err(err),
213
213
  Err(TrySendError::Retryable {
@@ -235,41 +235,34 @@ where
235
235
  async fn try_send_request(
236
236
  &self,
237
237
  mut req: Request<B>,
238
- connect_req: ConnectRequest,
238
+ descriptor: ConnectionDescriptor,
239
239
  ) -> Result<Response<Incoming>, TrySendError<B>> {
240
240
  let mut pooled = self
241
- .connection_for(connect_req)
241
+ .connection_for(descriptor)
242
242
  .await
243
243
  // `connection_for` already retries checkout errors, so if
244
244
  // it returns an error, there's not much else to retry
245
245
  .map_err(TrySendError::Nope)?;
246
246
 
247
+ let uri = req.uri().clone();
248
+
247
249
  if pooled.is_http1() {
248
250
  if req.version() == Version::HTTP_2 {
249
251
  warn!("Connection is HTTP/1, but request requires HTTP/2");
250
252
  return Err(TrySendError::Nope(
251
- Error::new_kind(ErrorKind::UserUnsupportedVersion)
252
- .with_connect_info(pooled.conn_info.clone()),
253
+ e!(UserUnsupportedVersion).with_connect_info(pooled.conn_info.clone()),
253
254
  ));
254
255
  }
255
256
 
256
257
  if self.config.set_host {
257
- let uri = req.uri().clone();
258
- req.headers_mut().entry(HOST).or_insert_with(|| {
259
- let hostname = uri.host().expect("authority implies host");
260
- if let Some(port) = util::get_non_default_port(&uri) {
261
- let s = format!("{hostname}:{port}");
262
- HeaderValue::from_maybe_shared(Bytes::from(s))
263
- } else {
264
- HeaderValue::from_str(hostname)
265
- }
266
- .expect("uri host is valid header value")
267
- });
258
+ req.headers_mut()
259
+ .entry(HOST)
260
+ .or_insert_with(|| generate_host_header(&uri));
268
261
  }
269
262
 
270
263
  // CONNECT always sends authority-form, so check it first...
271
264
  if req.method() == Method::CONNECT {
272
- util::authority_form(req.uri_mut());
265
+ authority_form(req.uri_mut());
273
266
  } else if pooled.conn_info.is_proxied() {
274
267
  if let Some(auth) = pooled.conn_info.proxy_auth() {
275
268
  req.headers_mut()
@@ -281,12 +274,40 @@ where
281
274
  crate::util::replace_headers(req.headers_mut(), headers.clone());
282
275
  }
283
276
 
284
- util::absolute_form(req.uri_mut());
277
+ absolute_form(req.uri_mut());
285
278
  } else {
286
- util::origin_form(req.uri_mut());
279
+ origin_form(req.uri_mut());
287
280
  }
288
281
  } else if req.method() == Method::CONNECT && !pooled.is_http2() {
289
- util::authority_form(req.uri_mut());
282
+ authority_form(req.uri_mut());
283
+ }
284
+
285
+ #[cfg(feature = "cookies")]
286
+ let cookie_store = self.cookie_store.fetch(req.extensions()).cloned();
287
+
288
+ #[cfg(feature = "cookies")]
289
+ if let Some(ref cookie_store) = cookie_store {
290
+ let headers = req.headers_mut();
291
+
292
+ if !headers.contains_key(COOKIE) {
293
+ let version = if pooled.is_http2() {
294
+ Version::HTTP_2
295
+ } else {
296
+ Version::HTTP_11
297
+ };
298
+
299
+ match cookie_store.cookies(&uri, version) {
300
+ Cookies::Compressed(value) => {
301
+ headers.insert(COOKIE, value);
302
+ }
303
+ Cookies::Uncompressed(values) => {
304
+ for value in values {
305
+ headers.append(COOKIE, value);
306
+ }
307
+ }
308
+ Cookies::Empty => (),
309
+ }
310
+ }
290
311
  }
291
312
 
292
313
  let mut res = match pooled.try_send_request(req).await {
@@ -308,9 +329,24 @@ where
308
329
  }
309
330
  };
310
331
 
332
+ #[cfg(feature = "cookies")]
333
+ if let Some(cookie_store) = cookie_store {
334
+ let mut cookies = res
335
+ .headers()
336
+ .get_all(http::header::SET_COOKIE)
337
+ .iter()
338
+ .peekable();
339
+ if cookies.peek().is_some() {
340
+ cookie_store.set_cookies(&mut cookies, &uri);
341
+ }
342
+ }
343
+
311
344
  // If the Connector included 'extra' info, add to Response...
312
345
  pooled.conn_info.set_extras(res.extensions_mut());
313
346
 
347
+ // If the Connector included connection info, add to Response...
348
+ res.extensions_mut().insert(pooled.conn_info.clone());
349
+
314
350
  // If pooled is HTTP/2, we can toss this reference immediately.
315
351
  //
316
352
  // when pooled is dropped, it will try to insert back into the
@@ -333,10 +369,10 @@ where
333
369
 
334
370
  async fn connection_for(
335
371
  &self,
336
- req: ConnectRequest,
337
- ) -> Result<pool::Pooled<PoolClient<B>, ConnectIdentity>, Error> {
372
+ descriptor: ConnectionDescriptor,
373
+ ) -> Result<pool::Pooled<PoolClient<B>, ConnectionId>, Error> {
338
374
  loop {
339
- match self.one_connection_for(req.clone()).await {
375
+ match self.one_connection_for(descriptor.clone()).await {
340
376
  Ok(pooled) => return Ok(pooled),
341
377
  Err(ClientConnectError::Normal(err)) => return Err(err),
342
378
  Err(ClientConnectError::CheckoutIsClosed(reason)) => {
@@ -356,12 +392,12 @@ where
356
392
 
357
393
  async fn one_connection_for(
358
394
  &self,
359
- req: ConnectRequest,
360
- ) -> Result<pool::Pooled<PoolClient<B>, ConnectIdentity>, ClientConnectError> {
395
+ descriptor: ConnectionDescriptor,
396
+ ) -> Result<pool::Pooled<PoolClient<B>, ConnectionId>, ClientConnectError> {
361
397
  // Return a single connection if pooling is not enabled
362
398
  if !self.pool.is_enabled() {
363
399
  return self
364
- .connect_to(req)
400
+ .connect_to(descriptor)
365
401
  .await
366
402
  .map_err(ClientConnectError::Normal);
367
403
  }
@@ -376,8 +412,8 @@ where
376
412
  // - If a new connection is started, but the Checkout wins after (an idle connection became
377
413
  // available first), the started connection future is spawned into the runtime to
378
414
  // complete, and then be inserted into the pool as an idle connection.
379
- let checkout = self.pool.checkout(req.identify());
380
- let connect = self.connect_to(req);
415
+ let checkout = self.pool.checkout(descriptor.id());
416
+ let connect = self.connect_to(descriptor);
381
417
  let is_ver_h2 = self.config.ver == Ver::Http2;
382
418
 
383
419
  // The order of the `select` is depended on below...
@@ -447,8 +483,8 @@ where
447
483
 
448
484
  fn connect_to(
449
485
  &self,
450
- req: ConnectRequest,
451
- ) -> impl Lazy<Output = Result<pool::Pooled<PoolClient<B>, ConnectIdentity>, Error>>
486
+ descriptor: ConnectionDescriptor,
487
+ ) -> impl Lazy<Output = Result<pool::Pooled<PoolClient<B>, ConnectionId>, Error>>
452
488
  + Send
453
489
  + Unpin
454
490
  + 'static {
@@ -457,8 +493,8 @@ where
457
493
 
458
494
  let h1_builder = self.h1_builder.clone();
459
495
  let h2_builder = self.h2_builder.clone();
460
- let ver = match req.extra().alpn_protocol() {
461
- Some(AlpnProtocol::HTTP2) => Ver::Http2,
496
+ let ver = match descriptor.version() {
497
+ Some(Version::HTTP_2) => Ver::Http2,
462
498
  _ => self.config.ver,
463
499
  };
464
500
  let is_ver_h2 = ver == Ver::Http2;
@@ -469,16 +505,15 @@ where
469
505
  // If the pool_key is for HTTP/2, and there is already a
470
506
  // connection being established, then this can't take a
471
507
  // second lock. The "connect_to" future is Canceled.
472
- let connecting = match pool.connecting(req.identify(), ver) {
508
+ let connecting = match pool.connecting(descriptor.id(), ver) {
473
509
  Some(lock) => lock,
474
510
  None => {
475
- let canceled = Error::new_kind(ErrorKind::Canceled);
476
511
  // HTTP/2 connection in progress.
477
- return Either::Right(futures_util::future::err(canceled));
512
+ return Either::Right(futures_util::future::err(e!(Canceled)));
478
513
  }
479
514
  };
480
515
  Either::Left(
481
- Oneshot::new(connector, req)
516
+ Oneshot::new(connector, descriptor)
482
517
  .map_err(|src| Error::new(ErrorKind::Connect, src))
483
518
  .and_then(move |io| {
484
519
  let connected = io.connected();
@@ -629,7 +664,7 @@ where
629
664
 
630
665
  impl<C, B> tower::Service<Request<B>> for HttpClient<C, B>
631
666
  where
632
- C: tower::Service<ConnectRequest> + Clone + Send + Sync + 'static,
667
+ C: tower::Service<ConnectionDescriptor> + Clone + Send + Sync + 'static,
633
668
  C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
634
669
  C::Error: Into<BoxError>,
635
670
  C::Future: Unpin + Send + 'static,
@@ -638,31 +673,8 @@ where
638
673
  B::Error: Into<BoxError>,
639
674
  {
640
675
  type Response = Response<Incoming>;
641
- type Error = Error;
642
- type Future = ResponseFuture;
643
-
644
- fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
645
- Poll::Ready(Ok(()))
646
- }
647
-
648
- fn call(&mut self, req: Request<B>) -> Self::Future {
649
- self.request(req)
650
- }
651
- }
652
-
653
- impl<C, B> tower::Service<Request<B>> for &'_ HttpClient<C, B>
654
- where
655
- C: tower::Service<ConnectRequest> + Clone + Send + Sync + 'static,
656
- C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
657
- C::Error: Into<BoxError>,
658
- C::Future: Unpin + Send + 'static,
659
- B: Body + Send + 'static + Unpin,
660
- B::Data: Send,
661
- B::Error: Into<BoxError>,
662
- {
663
- type Response = Response<Incoming>;
664
- type Error = Error;
665
- type Future = ResponseFuture;
676
+ type Error = BoxError;
677
+ type Future = BoxFuture<'static, Result<Response<Incoming>, Self::Error>>;
666
678
 
667
679
  fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
668
680
  Poll::Ready(Ok(()))
@@ -682,6 +694,8 @@ impl<C: Clone, B> Clone for HttpClient<C, B> {
682
694
  h2_builder: self.h2_builder.clone(),
683
695
  connector: self.connector.clone(),
684
696
  pool: self.pool.clone(),
697
+ #[cfg(feature = "cookies")]
698
+ cookie_store: self.cookie_store.clone(),
685
699
  }
686
700
  }
687
701
  }
@@ -700,40 +714,43 @@ enum PoolTx<B> {
700
714
  // ===== impl PoolClient =====
701
715
 
702
716
  impl<B> PoolClient<B> {
717
+ #[inline]
703
718
  fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Error>> {
704
719
  match self.tx {
705
720
  PoolTx::Http1(ref mut tx) => tx.poll_ready(cx).map_err(Error::closed),
706
-
707
721
  PoolTx::Http2(_) => Poll::Ready(Ok(())),
708
722
  }
709
723
  }
710
724
 
725
+ #[inline]
711
726
  fn is_http1(&self) -> bool {
712
727
  !self.is_http2()
713
728
  }
714
729
 
730
+ #[inline]
715
731
  fn is_http2(&self) -> bool {
716
732
  match self.tx {
717
733
  PoolTx::Http1(_) => false,
718
-
719
734
  PoolTx::Http2(_) => true,
720
735
  }
721
736
  }
722
737
 
738
+ #[inline]
723
739
  fn is_poisoned(&self) -> bool {
724
740
  self.conn_info.poisoned()
725
741
  }
726
742
 
743
+ #[inline]
727
744
  fn is_ready(&self) -> bool {
728
745
  match self.tx {
729
746
  PoolTx::Http1(ref tx) => tx.is_ready(),
730
-
731
747
  PoolTx::Http2(ref tx) => tx.is_ready(),
732
748
  }
733
749
  }
734
750
  }
735
751
 
736
752
  impl<B: Body + 'static> PoolClient<B> {
753
+ #[inline]
737
754
  fn try_send_request(
738
755
  &mut self,
739
756
  req: Request<B>,
@@ -752,6 +769,7 @@ impl<B> pool::Poolable for PoolClient<B>
752
769
  where
753
770
  B: Send + 'static,
754
771
  {
772
+ #[inline]
755
773
  fn is_open(&self) -> bool {
756
774
  !self.is_poisoned() && self.is_ready()
757
775
  }
@@ -777,61 +795,30 @@ where
777
795
  }
778
796
  }
779
797
 
798
+ #[inline]
780
799
  fn can_share(&self) -> bool {
781
800
  self.is_http2()
782
801
  }
783
802
  }
784
803
 
785
- /// A `Future` that will resolve to an HTTP Response.
786
- #[must_use = "futures do nothing unless polled"]
787
- pub struct ResponseFuture {
788
- inner: Pin<Box<dyn Future<Output = Result<Response<Incoming>, Error>> + Send>>,
789
- }
790
-
791
- // ===== impl ResponseFuture =====
792
-
793
- impl ResponseFuture {
794
- #[inline]
795
- pub(super) fn new<F>(value: F) -> ResponseFuture
796
- where
797
- F: Future<Output = Result<Response<Incoming>, Error>> + Send + 'static,
798
- {
799
- ResponseFuture {
800
- inner: Box::pin(value),
801
- }
802
- }
803
- }
804
-
805
- impl Future for ResponseFuture {
806
- type Output = Result<Response<Incoming>, Error>;
807
-
808
- #[inline]
809
- fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
810
- self.inner.as_mut().poll(cx)
811
- }
812
- }
813
-
814
804
  /// A builder to configure a new [`HttpClient`].
815
805
  #[derive(Clone)]
816
806
  pub struct Builder {
817
807
  config: Config,
818
- exec: Exec,
819
-
808
+ exec: Executor,
820
809
  h1_builder: conn::http1::Builder,
821
- h2_builder: conn::http2::Builder<Exec>,
810
+ h2_builder: conn::http2::Builder<Executor>,
822
811
  pool_config: pool::Config,
823
- pool_timer: Option<ArcTimer>,
812
+ pool_timer: Timer,
813
+ #[cfg(feature = "cookies")]
814
+ cookie_store: Option<Arc<dyn CookieStore>>,
824
815
  }
825
816
 
826
817
  // ===== impl Builder =====
827
818
 
828
819
  impl Builder {
829
820
  /// Construct a new Builder.
830
- pub fn new<E>(executor: E) -> Self
831
- where
832
- E: Executor<BoxSendFuture> + Send + Sync + Clone + 'static,
833
- {
834
- let exec = Exec::new(executor);
821
+ pub fn new(exec: Executor) -> Self {
835
822
  Self {
836
823
  config: Config {
837
824
  retry_canceled_requests: true,
@@ -839,17 +826,19 @@ impl Builder {
839
826
  ver: Ver::Auto,
840
827
  },
841
828
  exec: exec.clone(),
842
-
843
- h1_builder: conn::http1::Builder::new(),
829
+ h1_builder: conn::http1::Builder::default(),
844
830
  h2_builder: conn::http2::Builder::new(exec),
845
831
  pool_config: pool::Config {
846
832
  idle_timeout: Some(Duration::from_secs(90)),
847
833
  max_idle_per_host: usize::MAX,
848
834
  max_pool_size: None,
849
835
  },
850
- pool_timer: None,
836
+ pool_timer: Timer::default(),
837
+ #[cfg(feature = "cookies")]
838
+ cookie_store: None,
851
839
  }
852
840
  }
841
+
853
842
  /// Set an optional timeout for idle sockets being kept-alive.
854
843
  /// A `Timer` is required for this to take effect. See `Builder::pool_timer`
855
844
  ///
@@ -878,7 +867,7 @@ impl Builder {
878
867
  ///
879
868
  /// Default is `None` (no limit).
880
869
  #[inline]
881
- pub fn pool_max_size(mut self, max_size: impl Into<Option<NonZeroU32>>) -> Self {
870
+ pub fn pool_max_size(mut self, max_size: impl Into<Option<NonZeroUsize>>) -> Self {
882
871
  self.pool_config.max_pool_size = max_size.into();
883
872
  self
884
873
  }
@@ -906,11 +895,8 @@ impl Builder {
906
895
  ///
907
896
  /// [`http2::client::Builder::timer`]: https://docs.rs/http2/latest/http2/client/struct.Builder.html#method.timer
908
897
  #[inline]
909
- pub fn http2_timer<M>(mut self, timer: M) -> Self
910
- where
911
- M: Timer + Send + Sync + 'static,
912
- {
913
- self.h2_builder.timer(timer);
898
+ pub fn http2_timer(mut self, timer: Timer) -> Self {
899
+ self.h2_builder = self.h2_builder.timer(timer);
914
900
  self
915
901
  }
916
902
 
@@ -921,7 +907,7 @@ impl Builder {
921
907
  O: Into<Option<Http1Options>>,
922
908
  {
923
909
  if let Some(opts) = opts.into() {
924
- self.h1_builder.options(opts);
910
+ self.h1_builder = self.h1_builder.options(opts);
925
911
  }
926
912
 
927
913
  self
@@ -934,54 +920,30 @@ impl Builder {
934
920
  O: Into<Option<Http2Options>>,
935
921
  {
936
922
  if let Some(opts) = opts.into() {
937
- self.h2_builder.options(opts);
923
+ self.h2_builder = self.h2_builder.options(opts);
938
924
  }
939
925
  self
940
926
  }
941
927
 
942
928
  /// Provide a timer to be used for timeouts and intervals in connection pools.
943
929
  #[inline]
944
- pub fn pool_timer<M>(mut self, timer: M) -> Self
945
- where
946
- M: Timer + Clone + Send + Sync + 'static,
947
- {
948
- self.pool_timer = Some(ArcTimer::new(timer));
949
- self
950
- }
951
-
952
- /// Set whether to retry requests that get disrupted before ever starting
953
- /// to write.
954
- ///
955
- /// This means a request that is queued, and gets given an idle, reused
956
- /// connection, and then encounters an error immediately as the idle
957
- /// connection was found to be unusable.
958
- ///
959
- /// When this is set to `false`, the related `ResponseFuture` would instead
960
- /// resolve to an `Error::Cancel`.
961
- ///
962
- /// Default is `true`.
963
- #[inline]
964
- pub fn retry_canceled_requests(mut self, val: bool) -> Self {
965
- self.config.retry_canceled_requests = val;
930
+ pub fn pool_timer(mut self, timer: Timer) -> Self {
931
+ self.pool_timer = timer;
966
932
  self
967
933
  }
968
934
 
969
- /// Set whether to automatically add the `Host` header to requests.
970
- ///
971
- /// If true, and a request does not include a `Host` header, one will be
972
- /// added automatically, derived from the authority of the `Uri`.
973
- ///
974
- /// Default is `true`.
935
+ /// Provide a cookie store for automatic cookie management.
975
936
  #[inline]
976
- pub fn set_host(mut self, val: bool) -> Self {
977
- self.config.set_host = val;
937
+ #[cfg(feature = "cookies")]
938
+ pub fn cookie_store(mut self, cookie_store: Option<Arc<dyn CookieStore>>) -> Self {
939
+ self.cookie_store = cookie_store;
978
940
  self
979
941
  }
980
942
 
981
943
  /// Combine the configuration of this builder with a connector to create a `HttpClient`.
982
944
  pub fn build<C, B>(self, connector: C) -> HttpClient<C, B>
983
945
  where
984
- C: tower::Service<ConnectRequest> + Clone + Send + Sync + 'static,
946
+ C: tower::Service<ConnectionDescriptor> + Clone + Send + Sync + 'static,
985
947
  C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
986
948
  C::Error: Into<BoxError>,
987
949
  C::Future: Unpin + Send + 'static,
@@ -993,11 +955,195 @@ impl Builder {
993
955
  HttpClient {
994
956
  config: self.config,
995
957
  exec: exec.clone(),
996
-
958
+ connector,
997
959
  h1_builder: self.h1_builder,
998
960
  h2_builder: self.h2_builder,
999
- connector,
1000
961
  pool: pool::Pool::new(self.pool_config, exec, timer),
962
+ #[cfg(feature = "cookies")]
963
+ cookie_store: RequestConfig::new(self.cookie_store),
1001
964
  }
1002
965
  }
1003
966
  }
967
+
968
+ // ==== impl Error ====
969
+
970
+ impl fmt::Display for Error {
971
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
972
+ write!(f, "client error ({:?})", self.kind)
973
+ }
974
+ }
975
+
976
+ impl StdError for Error {
977
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
978
+ self.source.as_ref().map(|e| &**e as _)
979
+ }
980
+ }
981
+
982
+ impl Error {
983
+ fn new<E>(kind: ErrorKind, error: E) -> Self
984
+ where
985
+ E: Into<BoxError>,
986
+ {
987
+ let error = error.into();
988
+ let kind = if error.is::<proxy::tunnel::TunnelError>() || error.is::<ProxyConnect>() || {
989
+ #[cfg(feature = "socks")]
990
+ {
991
+ error.is::<proxy::socks::SocksError>()
992
+ }
993
+ #[cfg(not(feature = "socks"))]
994
+ {
995
+ false
996
+ }
997
+ } {
998
+ ErrorKind::ProxyConnect
999
+ } else {
1000
+ kind
1001
+ };
1002
+
1003
+ Self {
1004
+ kind,
1005
+ source: Some(error),
1006
+ connect_info: None,
1007
+ }
1008
+ }
1009
+
1010
+ /// Returns true if this was an error from [`ErrorKind::Connect`].
1011
+ #[inline]
1012
+ pub fn is_connect(&self) -> bool {
1013
+ matches!(self.kind, ErrorKind::Connect)
1014
+ }
1015
+
1016
+ /// Returns true if this was an error from [`ErrorKind::ProxyConnect`].
1017
+ #[inline]
1018
+ pub fn is_proxy_connect(&self) -> bool {
1019
+ matches!(self.kind, ErrorKind::ProxyConnect)
1020
+ }
1021
+
1022
+ #[inline]
1023
+ fn with_connect_info(self, connect_info: Connected) -> Self {
1024
+ Self {
1025
+ connect_info: Some(connect_info),
1026
+ ..self
1027
+ }
1028
+ }
1029
+
1030
+ #[inline]
1031
+ fn is_canceled(&self) -> bool {
1032
+ matches!(self.kind, ErrorKind::Canceled)
1033
+ }
1034
+
1035
+ #[inline]
1036
+ fn tx(src: wreq_proto::Error) -> Self {
1037
+ Self::new(ErrorKind::SendRequest, src)
1038
+ }
1039
+
1040
+ #[inline]
1041
+ fn closed(src: wreq_proto::Error) -> Self {
1042
+ Self::new(ErrorKind::ChannelClosed, src)
1043
+ }
1044
+ }
1045
+
1046
+ fn origin_form(uri: &mut Uri) {
1047
+ let path = match uri.path_and_query() {
1048
+ Some(path) if path.as_str() != "/" => {
1049
+ let mut parts = ::http::uri::Parts::default();
1050
+ parts.path_and_query.replace(path.clone());
1051
+ Uri::from_parts(parts).expect("path is valid uri")
1052
+ }
1053
+ _none_or_just_slash => {
1054
+ debug_assert!(Uri::default() == "/");
1055
+ Uri::default()
1056
+ }
1057
+ };
1058
+ *uri = path
1059
+ }
1060
+
1061
+ fn absolute_form(uri: &mut Uri) {
1062
+ debug_assert!(uri.scheme().is_some(), "absolute_form needs a scheme");
1063
+ debug_assert!(
1064
+ uri.authority().is_some(),
1065
+ "absolute_form needs an authority"
1066
+ );
1067
+ }
1068
+
1069
+ fn authority_form(uri: &mut Uri) {
1070
+ if let Some(path) = uri.path_and_query() {
1071
+ // `https://hyper.rs` would parse with `/` path, don't
1072
+ // annoy people about that...
1073
+ if path != "/" {
1074
+ warn!("HTTP/1.1 CONNECT request stripping path: {:?}", path);
1075
+ }
1076
+ }
1077
+ *uri = match uri.authority() {
1078
+ Some(auth) => {
1079
+ let mut parts = ::http::uri::Parts::default();
1080
+ parts.authority = Some(auth.clone());
1081
+ Uri::from_parts(parts).expect("authority is valid")
1082
+ }
1083
+ None => {
1084
+ unreachable!("authority_form with relative uri");
1085
+ }
1086
+ };
1087
+ }
1088
+
1089
+ fn normalize_uri<B>(req: &mut Request<B>, is_http_connect: bool) -> Result<Uri, Error> {
1090
+ let uri = req.uri().clone();
1091
+
1092
+ let build_base_uri = |scheme: Scheme, authority: Authority| {
1093
+ Uri::builder()
1094
+ .scheme(scheme)
1095
+ .authority(authority)
1096
+ .path_and_query(PathAndQuery::from_static("/"))
1097
+ .build()
1098
+ .expect("valid base URI")
1099
+ };
1100
+
1101
+ match (uri.scheme(), uri.authority()) {
1102
+ (Some(scheme), Some(auth)) => Ok(build_base_uri(scheme.clone(), auth.clone())),
1103
+ (None, Some(auth)) if is_http_connect => {
1104
+ let scheme = match auth.port_u16() {
1105
+ Some(443) => Scheme::HTTPS,
1106
+ _ => Scheme::HTTP,
1107
+ };
1108
+ set_scheme(req.uri_mut(), scheme.clone());
1109
+ Ok(build_base_uri(scheme, auth.clone()))
1110
+ }
1111
+ _ => {
1112
+ debug!("Client requires absolute-form URIs, received: {:?}", uri);
1113
+ Err(e!(UserAbsoluteUriRequired))
1114
+ }
1115
+ }
1116
+ }
1117
+
1118
+ fn generate_host_header(uri: &Uri) -> HeaderValue {
1119
+ let hostname = uri.host().expect("authority implies host");
1120
+ let port = match (uri.port().map(|p| p.as_u16()), is_schema_secure(uri)) {
1121
+ (Some(443), true) | (Some(80), false) => None,
1122
+ _ => uri.port(),
1123
+ };
1124
+ if let Some(port) = port {
1125
+ let host = format!("{hostname}:{port}");
1126
+ HeaderValue::from_maybe_shared(Bytes::from(host))
1127
+ } else {
1128
+ HeaderValue::from_str(hostname)
1129
+ }
1130
+ .expect("uri host is valid header value")
1131
+ }
1132
+
1133
+ fn set_scheme(uri: &mut Uri, scheme: Scheme) {
1134
+ debug_assert!(
1135
+ uri.scheme().is_none(),
1136
+ "set_scheme expects no existing scheme"
1137
+ );
1138
+ let old = std::mem::take(uri);
1139
+ let mut parts: ::http::uri::Parts = old.into();
1140
+ parts.scheme = Some(scheme);
1141
+ parts.path_and_query = Some(PathAndQuery::from_static("/"));
1142
+ *uri = Uri::from_parts(parts).expect("scheme is valid");
1143
+ }
1144
+
1145
+ fn is_schema_secure(uri: &Uri) -> bool {
1146
+ uri.scheme_str()
1147
+ .map(|scheme_str| matches!(scheme_str, "wss" | "https"))
1148
+ .unwrap_or_default()
1149
+ }