wreq-rb 0.3.0

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 (167) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +2688 -0
  3. data/Cargo.toml +6 -0
  4. data/README.md +179 -0
  5. data/ext/wreq_rb/Cargo.toml +39 -0
  6. data/ext/wreq_rb/extconf.rb +22 -0
  7. data/ext/wreq_rb/src/client.rs +565 -0
  8. data/ext/wreq_rb/src/error.rs +25 -0
  9. data/ext/wreq_rb/src/lib.rs +20 -0
  10. data/ext/wreq_rb/src/response.rs +132 -0
  11. data/lib/wreq-rb/version.rb +5 -0
  12. data/lib/wreq-rb.rb +17 -0
  13. data/patches/0001-add-transfer-size-tracking.patch +292 -0
  14. data/vendor/wreq/Cargo.toml +306 -0
  15. data/vendor/wreq/LICENSE +202 -0
  16. data/vendor/wreq/README.md +122 -0
  17. data/vendor/wreq/examples/cert_store.rs +77 -0
  18. data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
  19. data/vendor/wreq/examples/emulation.rs +118 -0
  20. data/vendor/wreq/examples/form.rs +14 -0
  21. data/vendor/wreq/examples/http1_websocket.rs +37 -0
  22. data/vendor/wreq/examples/http2_websocket.rs +45 -0
  23. data/vendor/wreq/examples/json_dynamic.rs +41 -0
  24. data/vendor/wreq/examples/json_typed.rs +47 -0
  25. data/vendor/wreq/examples/keylog.rs +16 -0
  26. data/vendor/wreq/examples/request_with_emulation.rs +115 -0
  27. data/vendor/wreq/examples/request_with_interface.rs +37 -0
  28. data/vendor/wreq/examples/request_with_local_address.rs +16 -0
  29. data/vendor/wreq/examples/request_with_proxy.rs +13 -0
  30. data/vendor/wreq/examples/request_with_redirect.rs +22 -0
  31. data/vendor/wreq/examples/request_with_version.rs +15 -0
  32. data/vendor/wreq/examples/tor_socks.rs +24 -0
  33. data/vendor/wreq/examples/unix_socket.rs +33 -0
  34. data/vendor/wreq/src/client/body.rs +304 -0
  35. data/vendor/wreq/src/client/conn/conn.rs +231 -0
  36. data/vendor/wreq/src/client/conn/connector.rs +549 -0
  37. data/vendor/wreq/src/client/conn/http.rs +1023 -0
  38. data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
  39. data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
  40. data/vendor/wreq/src/client/conn/proxy.rs +39 -0
  41. data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
  42. data/vendor/wreq/src/client/conn/uds.rs +44 -0
  43. data/vendor/wreq/src/client/conn/verbose.rs +149 -0
  44. data/vendor/wreq/src/client/conn.rs +323 -0
  45. data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
  46. data/vendor/wreq/src/client/core/body/length.rs +118 -0
  47. data/vendor/wreq/src/client/core/body.rs +34 -0
  48. data/vendor/wreq/src/client/core/common/buf.rs +149 -0
  49. data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
  50. data/vendor/wreq/src/client/core/common/watch.rs +76 -0
  51. data/vendor/wreq/src/client/core/common.rs +3 -0
  52. data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
  53. data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
  54. data/vendor/wreq/src/client/core/conn.rs +11 -0
  55. data/vendor/wreq/src/client/core/dispatch.rs +299 -0
  56. data/vendor/wreq/src/client/core/error.rs +435 -0
  57. data/vendor/wreq/src/client/core/ext.rs +201 -0
  58. data/vendor/wreq/src/client/core/http1.rs +178 -0
  59. data/vendor/wreq/src/client/core/http2.rs +483 -0
  60. data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
  61. data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
  62. data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
  63. data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
  64. data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
  65. data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
  66. data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
  67. data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
  68. data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
  69. data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
  70. data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
  71. data/vendor/wreq/src/client/core/proto.rs +58 -0
  72. data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
  73. data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
  74. data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
  75. data/vendor/wreq/src/client/core/rt.rs +25 -0
  76. data/vendor/wreq/src/client/core/upgrade.rs +267 -0
  77. data/vendor/wreq/src/client/core.rs +16 -0
  78. data/vendor/wreq/src/client/emulation.rs +161 -0
  79. data/vendor/wreq/src/client/http/client/error.rs +142 -0
  80. data/vendor/wreq/src/client/http/client/exec.rs +29 -0
  81. data/vendor/wreq/src/client/http/client/extra.rs +77 -0
  82. data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
  83. data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
  84. data/vendor/wreq/src/client/http/client/util.rs +104 -0
  85. data/vendor/wreq/src/client/http/client.rs +1003 -0
  86. data/vendor/wreq/src/client/http/future.rs +99 -0
  87. data/vendor/wreq/src/client/http.rs +1629 -0
  88. data/vendor/wreq/src/client/layer/config/options.rs +156 -0
  89. data/vendor/wreq/src/client/layer/config.rs +116 -0
  90. data/vendor/wreq/src/client/layer/cookie.rs +161 -0
  91. data/vendor/wreq/src/client/layer/decoder.rs +139 -0
  92. data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
  93. data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
  94. data/vendor/wreq/src/client/layer/redirect.rs +145 -0
  95. data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
  96. data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
  97. data/vendor/wreq/src/client/layer/retry.rs +151 -0
  98. data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
  99. data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
  100. data/vendor/wreq/src/client/layer/timeout.rs +177 -0
  101. data/vendor/wreq/src/client/layer.rs +15 -0
  102. data/vendor/wreq/src/client/multipart.rs +717 -0
  103. data/vendor/wreq/src/client/request.rs +818 -0
  104. data/vendor/wreq/src/client/response.rs +534 -0
  105. data/vendor/wreq/src/client/ws/json.rs +99 -0
  106. data/vendor/wreq/src/client/ws/message.rs +453 -0
  107. data/vendor/wreq/src/client/ws.rs +714 -0
  108. data/vendor/wreq/src/client.rs +27 -0
  109. data/vendor/wreq/src/config.rs +140 -0
  110. data/vendor/wreq/src/cookie.rs +579 -0
  111. data/vendor/wreq/src/dns/gai.rs +249 -0
  112. data/vendor/wreq/src/dns/hickory.rs +78 -0
  113. data/vendor/wreq/src/dns/resolve.rs +180 -0
  114. data/vendor/wreq/src/dns.rs +69 -0
  115. data/vendor/wreq/src/error.rs +502 -0
  116. data/vendor/wreq/src/ext.rs +398 -0
  117. data/vendor/wreq/src/hash.rs +143 -0
  118. data/vendor/wreq/src/header.rs +506 -0
  119. data/vendor/wreq/src/into_uri.rs +187 -0
  120. data/vendor/wreq/src/lib.rs +586 -0
  121. data/vendor/wreq/src/proxy/mac.rs +82 -0
  122. data/vendor/wreq/src/proxy/matcher.rs +806 -0
  123. data/vendor/wreq/src/proxy/uds.rs +66 -0
  124. data/vendor/wreq/src/proxy/win.rs +31 -0
  125. data/vendor/wreq/src/proxy.rs +569 -0
  126. data/vendor/wreq/src/redirect.rs +575 -0
  127. data/vendor/wreq/src/retry.rs +198 -0
  128. data/vendor/wreq/src/sync.rs +129 -0
  129. data/vendor/wreq/src/tls/conn/cache.rs +123 -0
  130. data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
  131. data/vendor/wreq/src/tls/conn/ext.rs +82 -0
  132. data/vendor/wreq/src/tls/conn/macros.rs +34 -0
  133. data/vendor/wreq/src/tls/conn/service.rs +138 -0
  134. data/vendor/wreq/src/tls/conn.rs +681 -0
  135. data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
  136. data/vendor/wreq/src/tls/keylog.rs +99 -0
  137. data/vendor/wreq/src/tls/options.rs +464 -0
  138. data/vendor/wreq/src/tls/x509/identity.rs +122 -0
  139. data/vendor/wreq/src/tls/x509/parser.rs +71 -0
  140. data/vendor/wreq/src/tls/x509/store.rs +228 -0
  141. data/vendor/wreq/src/tls/x509.rs +68 -0
  142. data/vendor/wreq/src/tls.rs +154 -0
  143. data/vendor/wreq/src/trace.rs +55 -0
  144. data/vendor/wreq/src/util.rs +122 -0
  145. data/vendor/wreq/tests/badssl.rs +228 -0
  146. data/vendor/wreq/tests/brotli.rs +350 -0
  147. data/vendor/wreq/tests/client.rs +1098 -0
  148. data/vendor/wreq/tests/connector_layers.rs +227 -0
  149. data/vendor/wreq/tests/cookie.rs +306 -0
  150. data/vendor/wreq/tests/deflate.rs +347 -0
  151. data/vendor/wreq/tests/emulation.rs +260 -0
  152. data/vendor/wreq/tests/gzip.rs +347 -0
  153. data/vendor/wreq/tests/layers.rs +261 -0
  154. data/vendor/wreq/tests/multipart.rs +165 -0
  155. data/vendor/wreq/tests/proxy.rs +438 -0
  156. data/vendor/wreq/tests/redirect.rs +629 -0
  157. data/vendor/wreq/tests/retry.rs +135 -0
  158. data/vendor/wreq/tests/support/delay_server.rs +117 -0
  159. data/vendor/wreq/tests/support/error.rs +16 -0
  160. data/vendor/wreq/tests/support/layer.rs +183 -0
  161. data/vendor/wreq/tests/support/mod.rs +9 -0
  162. data/vendor/wreq/tests/support/server.rs +232 -0
  163. data/vendor/wreq/tests/timeouts.rs +281 -0
  164. data/vendor/wreq/tests/unix_socket.rs +135 -0
  165. data/vendor/wreq/tests/upgrade.rs +98 -0
  166. data/vendor/wreq/tests/zstd.rs +559 -0
  167. metadata +225 -0
@@ -0,0 +1,198 @@
1
+ //! Retry requests
2
+ //!
3
+ //! A `Client` has the ability to retry requests, by sending additional copies
4
+ //! to the server if a response is considered retryable.
5
+ //!
6
+ //! The [`Policy`] makes it easier to configure what requests to retry, along
7
+ //! with including best practices by default, such as a retry budget.
8
+ //!
9
+ //! # Defaults
10
+ //!
11
+ //! The default retry behavior of a `Client` is to only retry requests where an
12
+ //! error or low-level protocol NACK is encountered that is known to be safe to
13
+ //! retry. Note however that providing a specific retry policy will override
14
+ //! the default, and you will need to explicitly include that behavior.
15
+ //!
16
+ //! All policies default to including a retry budget that permits 20% extra
17
+ //! requests to be sent.
18
+ //!
19
+ //! # Scoped
20
+ //!
21
+ //! A client's retry policy is scoped. That means that the policy doesn't
22
+ //! apply to all requests, but only those within a user-defined scope.
23
+ //!
24
+ //! Since all policies include a budget by default, it doesn't make sense to
25
+ //! apply it on _all_ requests. Rather, the retry history applied by a budget
26
+ //! should likely only be applied to the same host.
27
+ //!
28
+ //! # Classifiers
29
+ //!
30
+ //! A retry policy needs to be configured with a classifier that determines
31
+ //! if a request should be retried. Knowledge of the destination server's
32
+ //! behavior is required to make a safe classifier. **Requests should not be
33
+ //! retried** if the server cannot safely handle the same request twice, or if
34
+ //! it causes side effects.
35
+ //!
36
+ //! Some common properties to check include if the request method is
37
+ //! idempotent, or if the response status code indicates a transient error.
38
+
39
+ use std::sync::Arc;
40
+
41
+ use http::Request;
42
+
43
+ use crate::{
44
+ Body,
45
+ client::layer::retry::{Action, Classifier, ClassifyFn, ReqRep, ScopeFn, Scoped},
46
+ };
47
+
48
+ /// A retry policy.
49
+ pub struct Policy {
50
+ pub(crate) budget: Option<f32>,
51
+ pub(crate) classifier: Classifier,
52
+ pub(crate) max_retries_per_request: u32,
53
+ pub(crate) scope: Scoped,
54
+ }
55
+
56
+ impl Policy {
57
+ /// Create a retry policy that will never retry any request.
58
+ ///
59
+ /// This is useful for disabling the `Client`s default behavior of retrying
60
+ /// protocol nacks.
61
+ #[inline]
62
+ pub fn never() -> Policy {
63
+ Self::scoped(|_| false).no_budget()
64
+ }
65
+
66
+ /// Create a retry policy scoped to requests for a specific host.
67
+ ///
68
+ /// This is a convenience method that creates a retry policy which only applies
69
+ /// to requests targeting the specified host. Requests to other hosts will not
70
+ /// be retried under this policy.
71
+ ///
72
+ /// # Arguments
73
+ /// * `host` - The hostname to match against request URIs (e.g., "api.example.com")
74
+ ///
75
+ /// # Example
76
+ /// ```rust
77
+ /// use wreq::retry::Policy;
78
+ ///
79
+ /// // Only retry requests to rust-lang.org
80
+ /// let policy = Policy::for_host("rust-lang.org");
81
+ /// ```
82
+ #[inline]
83
+ pub fn for_host<S>(host: S) -> Policy
84
+ where
85
+ S: for<'a> PartialEq<&'a str> + Send + Sync + 'static,
86
+ {
87
+ Self::scoped(move |req| {
88
+ req.uri()
89
+ .host()
90
+ .is_some_and(|request_host| host == request_host)
91
+ })
92
+ }
93
+
94
+ /// Create a scoped retry policy.
95
+ ///
96
+ /// For a more convenient constructor, see [`Policy::for_host()`].
97
+ #[inline]
98
+ fn scoped<F>(func: F) -> Policy
99
+ where
100
+ F: Fn(&Request<Body>) -> bool + Send + Sync + 'static,
101
+ {
102
+ Self {
103
+ budget: Some(0.2),
104
+ classifier: Classifier::Never,
105
+ max_retries_per_request: 2,
106
+ scope: Scoped::Dyn(Arc::new(ScopeFn(func))),
107
+ }
108
+ }
109
+
110
+ /// Set no retry budget.
111
+ ///
112
+ /// Sets that no budget will be enforced. This could also be considered
113
+ /// to be an infinite budget.
114
+ ///
115
+ /// This is NOT recommended. Disabling the budget can make your system more
116
+ /// susceptible to retry storms.
117
+ #[inline]
118
+ pub fn no_budget(mut self) -> Self {
119
+ self.budget = None;
120
+ self
121
+ }
122
+
123
+ /// Sets the max extra load the budget will allow.
124
+ ///
125
+ /// Think of the amount of requests your client generates, and how much
126
+ /// load that puts on the server. This option configures as a percentage
127
+ /// how much extra load is allowed via retries.
128
+ ///
129
+ /// For example, if you send 1,000 requests per second, setting a maximum
130
+ /// extra load value of `0.3` would allow 300 more requests per second
131
+ /// in retries. A value of `2.5` would allow 2,500 more requests.
132
+ ///
133
+ /// # Panics
134
+ ///
135
+ /// The `extra_percent` value must be within reasonable values for a
136
+ /// percentage. This method will panic if it is less than `0.0`, or greater
137
+ /// than `1000.0`.
138
+ #[inline]
139
+ pub fn max_extra_load(mut self, extra_percent: f32) -> Self {
140
+ assert!(extra_percent >= 0.0);
141
+ assert!(extra_percent <= 1000.0);
142
+ self.budget = Some(extra_percent);
143
+ self
144
+ }
145
+
146
+ /// Set the max retries allowed per request.
147
+ ///
148
+ /// For each logical (initial) request, only retry up to `max` times.
149
+ ///
150
+ /// This value is used in combination with a token budget that is applied
151
+ /// to all requests. Even if the budget would allow more requests, this
152
+ /// limit will prevent. Likewise, the budget may prevent retrying up to
153
+ /// `max` times. This setting prevents a single request from consuming
154
+ /// the entire budget.
155
+ ///
156
+ /// Default is currently 2 retries.
157
+ #[inline]
158
+ pub fn max_retries_per_request(mut self, max: u32) -> Self {
159
+ self.max_retries_per_request = max;
160
+ self
161
+ }
162
+
163
+ /// Provide a classifier to determine if a request should be retried.
164
+ ///
165
+ /// # Example
166
+ ///
167
+ /// ```rust
168
+ /// # fn with_policy(policy: wreq::retry::Policy) -> wreq::retry::Policy {
169
+ /// policy.classify_fn(|req_rep| {
170
+ /// match (req_rep.method(), req_rep.status()) {
171
+ /// (&http::Method::GET, Some(http::StatusCode::SERVICE_UNAVAILABLE)) => {
172
+ /// req_rep.retryable()
173
+ /// },
174
+ /// _ => req_rep.success()
175
+ /// }
176
+ /// })
177
+ /// # }
178
+ /// ```
179
+ #[inline]
180
+ pub fn classify_fn<F>(mut self, func: F) -> Self
181
+ where
182
+ F: Fn(ReqRep<'_>) -> Action + Send + Sync + 'static,
183
+ {
184
+ self.classifier = Classifier::Dyn(Arc::new(ClassifyFn(func)));
185
+ self
186
+ }
187
+ }
188
+
189
+ impl Default for Policy {
190
+ fn default() -> Self {
191
+ Self {
192
+ budget: None,
193
+ classifier: Classifier::ProtocolNacks,
194
+ max_retries_per_request: 2,
195
+ scope: Scoped::Unscoped,
196
+ }
197
+ }
198
+ }
@@ -0,0 +1,129 @@
1
+ //! Synchronization primitives: [`Mutex`] and [`RwLock`] that never poison.
2
+ //!
3
+ //! These types expose APIs identical to [`std::sync::Mutex`] and [`std::sync::RwLock`],
4
+ //! but **do not return** [`std::sync::PoisonError`] even if a thread panics while holding the lock.
5
+ //!
6
+ //! This is useful in high-availability systems where panic recovery is done externally,
7
+ //! or poisoning is not meaningful in context.
8
+
9
+ use std::{
10
+ ops::{Deref, DerefMut},
11
+ sync,
12
+ };
13
+
14
+ /// A [`Mutex`] that never poisons and has the same interface as [`std::sync::Mutex`].
15
+ pub struct Mutex<T: ?Sized>(sync::Mutex<T>);
16
+
17
+ impl<T> Mutex<T> {
18
+ /// Like [`std::sync::Mutex::new`].
19
+ #[inline]
20
+ pub fn new(t: T) -> Mutex<T> {
21
+ Mutex(sync::Mutex::new(t))
22
+ }
23
+ }
24
+
25
+ impl<T: ?Sized> Mutex<T> {
26
+ /// Like [`std::sync::Mutex::lock`].
27
+ #[inline]
28
+ pub fn lock(&self) -> MutexGuard<'_, T> {
29
+ MutexGuard(self.0.lock().unwrap_or_else(|e| e.into_inner()))
30
+ }
31
+ }
32
+
33
+ impl<T> Default for Mutex<T>
34
+ where
35
+ T: Default,
36
+ {
37
+ #[inline]
38
+ fn default() -> Self {
39
+ Mutex::new(T::default())
40
+ }
41
+ }
42
+
43
+ /// Like [`std::sync::MutexGuard`].
44
+ #[must_use]
45
+ pub struct MutexGuard<'a, T: ?Sized + 'a>(sync::MutexGuard<'a, T>);
46
+
47
+ impl<T: ?Sized> Deref for MutexGuard<'_, T> {
48
+ type Target = T;
49
+
50
+ #[inline]
51
+ fn deref(&self) -> &T {
52
+ self.0.deref()
53
+ }
54
+ }
55
+
56
+ impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
57
+ #[inline]
58
+ fn deref_mut(&mut self) -> &mut T {
59
+ self.0.deref_mut()
60
+ }
61
+ }
62
+
63
+ /// A [`RwLock`] that never poisons and has the same interface as [`std::sync::RwLock`].
64
+ pub struct RwLock<T: ?Sized>(sync::RwLock<T>);
65
+
66
+ impl<T> RwLock<T> {
67
+ /// Like [`std::sync::RwLock::new`].
68
+ #[inline]
69
+ pub fn new(t: T) -> RwLock<T> {
70
+ RwLock(sync::RwLock::new(t))
71
+ }
72
+ }
73
+
74
+ impl<T: ?Sized> RwLock<T> {
75
+ /// Like [`std::sync::RwLock::read`].
76
+ #[inline]
77
+ pub fn read(&self) -> RwLockReadGuard<'_, T> {
78
+ RwLockReadGuard(self.0.read().unwrap_or_else(|e| e.into_inner()))
79
+ }
80
+
81
+ /// Like [`std::sync::RwLock::write`].
82
+ #[inline]
83
+ pub fn write(&self) -> RwLockWriteGuard<'_, T> {
84
+ RwLockWriteGuard(self.0.write().unwrap_or_else(|e| e.into_inner()))
85
+ }
86
+ }
87
+
88
+ impl<T> Default for RwLock<T>
89
+ where
90
+ T: Default,
91
+ {
92
+ #[inline]
93
+ fn default() -> Self {
94
+ RwLock::new(T::default())
95
+ }
96
+ }
97
+
98
+ /// Like [`std::sync::RwLockReadGuard`].
99
+ #[must_use]
100
+ pub struct RwLockReadGuard<'a, T: ?Sized + 'a>(sync::RwLockReadGuard<'a, T>);
101
+
102
+ impl<T: ?Sized> Deref for RwLockReadGuard<'_, T> {
103
+ type Target = T;
104
+
105
+ #[inline]
106
+ fn deref(&self) -> &T {
107
+ self.0.deref()
108
+ }
109
+ }
110
+
111
+ /// Like [`std::sync::RwLockWriteGuard`].
112
+ #[must_use]
113
+ pub struct RwLockWriteGuard<'a, T: ?Sized + 'a>(sync::RwLockWriteGuard<'a, T>);
114
+
115
+ impl<T: ?Sized> Deref for RwLockWriteGuard<'_, T> {
116
+ type Target = T;
117
+
118
+ #[inline]
119
+ fn deref(&self) -> &T {
120
+ self.0.deref()
121
+ }
122
+ }
123
+
124
+ impl<T: ?Sized> DerefMut for RwLockWriteGuard<'_, T> {
125
+ #[inline]
126
+ fn deref_mut(&mut self) -> &mut T {
127
+ self.0.deref_mut()
128
+ }
129
+ }
@@ -0,0 +1,123 @@
1
+ use std::{
2
+ borrow::Borrow,
3
+ collections::hash_map::Entry,
4
+ hash::{Hash, Hasher},
5
+ };
6
+
7
+ use boring2::ssl::{SslSession, SslSessionRef, SslVersion};
8
+ use schnellru::ByLength;
9
+
10
+ use crate::hash::{HASHER, HashMap, LruMap};
11
+
12
+ /// A typed key for indexing TLS sessions in the cache.
13
+ ///
14
+ /// This wrapper provides type safety and allows different key types
15
+ /// (e.g., hostname, connection parameters) to be used for session lookup.
16
+ #[derive(Hash, PartialEq, Eq, Clone)]
17
+ pub struct SessionKey<T>(pub T);
18
+
19
+ /// A hashable wrapper around `SslSession` for use in hash-based collections.
20
+ ///
21
+ /// Uses the session ID for hashing and equality, enabling efficient
22
+ /// storage and lookup in HashMap/HashSet while maintaining session semantics.
23
+ #[derive(Clone)]
24
+ struct HashSession(SslSession);
25
+
26
+ impl PartialEq for HashSession {
27
+ fn eq(&self, other: &HashSession) -> bool {
28
+ self.0.id() == other.0.id()
29
+ }
30
+ }
31
+
32
+ impl Eq for HashSession {}
33
+
34
+ impl Hash for HashSession {
35
+ fn hash<H>(&self, state: &mut H)
36
+ where
37
+ H: Hasher,
38
+ {
39
+ self.0.id().hash(state);
40
+ }
41
+ }
42
+
43
+ impl Borrow<[u8]> for HashSession {
44
+ fn borrow(&self) -> &[u8] {
45
+ self.0.id()
46
+ }
47
+ }
48
+
49
+ /// A two-level cache for TLS sessions organized by host keys with LRU eviction.
50
+ ///
51
+ /// Maintains both forward (key → sessions) and reverse (session → key) lookups
52
+ /// for efficient session storage, retrieval, and cleanup operations.
53
+ pub struct SessionCache<T> {
54
+ reverse: HashMap<HashSession, SessionKey<T>>,
55
+ per_host_sessions: HashMap<SessionKey<T>, LruMap<HashSession, ()>>,
56
+ per_host_session_capacity: usize,
57
+ }
58
+
59
+ impl<T> SessionCache<T>
60
+ where
61
+ T: Hash + Eq + Clone,
62
+ {
63
+ pub fn with_capacity(per_host_session_capacity: usize) -> SessionCache<T> {
64
+ SessionCache {
65
+ per_host_sessions: HashMap::with_hasher(HASHER),
66
+ reverse: HashMap::with_hasher(HASHER),
67
+ per_host_session_capacity,
68
+ }
69
+ }
70
+
71
+ pub fn insert(&mut self, key: SessionKey<T>, session: SslSession) {
72
+ let per_host_sessions = self
73
+ .per_host_sessions
74
+ .entry(key.clone())
75
+ .or_insert_with(|| {
76
+ LruMap::with_hasher(ByLength::new(self.per_host_session_capacity as _), HASHER)
77
+ });
78
+
79
+ // Enforce per-key capacity limit by evicting the least recently used session
80
+ if per_host_sessions.len() >= self.per_host_session_capacity {
81
+ if let Some((evicted_session, _)) = per_host_sessions.pop_oldest() {
82
+ // Remove from reverse lookup to maintain consistency
83
+ self.reverse.remove(&evicted_session);
84
+ }
85
+ }
86
+
87
+ let session = HashSession(session);
88
+ per_host_sessions.insert(session.clone(), ());
89
+ self.reverse.insert(session, key);
90
+ }
91
+
92
+ pub fn get(&mut self, key: &SessionKey<T>) -> Option<SslSession> {
93
+ let session = {
94
+ let per_host_sessions = self.per_host_sessions.get_mut(key)?;
95
+ per_host_sessions.peek_oldest()?.0.clone().0
96
+ };
97
+
98
+ // https://tools.ietf.org/html/rfc8446#appendix-C.4
99
+ // OpenSSL will remove the session from its cache after the handshake completes anyway, but
100
+ // this ensures that concurrent handshakes don't end up with the same session.
101
+ if session.protocol_version() == SslVersion::TLS1_3 {
102
+ self.remove(&session);
103
+ }
104
+
105
+ Some(session)
106
+ }
107
+
108
+ fn remove(&mut self, session: &SslSessionRef) {
109
+ let key = match self.reverse.remove(session.id()) {
110
+ Some(key) => key,
111
+ None => return,
112
+ };
113
+
114
+ if let Entry::Occupied(mut per_host_sessions) = self.per_host_sessions.entry(key) {
115
+ per_host_sessions
116
+ .get_mut()
117
+ .remove(&HashSession(session.to_owned()));
118
+ if per_host_sessions.get().is_empty() {
119
+ per_host_sessions.remove();
120
+ }
121
+ }
122
+ }
123
+ }
@@ -0,0 +1,125 @@
1
+ use std::io::{self, Read, Result, Write};
2
+
3
+ use boring2::ssl::{CertificateCompressionAlgorithm, CertificateCompressor};
4
+ use brotli::{CompressorWriter, Decompressor};
5
+ use flate2::{Compression, read::ZlibDecoder, write::ZlibEncoder};
6
+ use zstd::stream::{Decoder as ZstdDecoder, Encoder as ZstdEncoder};
7
+
8
+ #[derive(Debug, Clone, Default)]
9
+ #[non_exhaustive]
10
+ pub struct BrotliCertificateCompressor;
11
+
12
+ impl CertificateCompressor for BrotliCertificateCompressor {
13
+ const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::BROTLI;
14
+ const CAN_COMPRESS: bool = true;
15
+ const CAN_DECOMPRESS: bool = true;
16
+
17
+ fn compress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
18
+ where
19
+ W: Write,
20
+ {
21
+ let mut writer = CompressorWriter::new(output, input.len(), 11, 22);
22
+ writer.write_all(input)?;
23
+ writer.flush()?;
24
+ Ok(())
25
+ }
26
+
27
+ fn decompress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
28
+ where
29
+ W: Write,
30
+ {
31
+ let mut reader = Decompressor::new(input, 4096);
32
+ let mut buf = [0u8; 4096];
33
+ loop {
34
+ match reader.read(&mut buf[..]) {
35
+ Err(e) => {
36
+ if let io::ErrorKind::Interrupted = e.kind() {
37
+ continue;
38
+ }
39
+ return Err(e);
40
+ }
41
+ Ok(size) => {
42
+ if size == 0 {
43
+ break;
44
+ }
45
+ output.write_all(&buf[..size])?;
46
+ }
47
+ }
48
+ }
49
+ Ok(())
50
+ }
51
+ }
52
+
53
+ #[derive(Debug, Clone, Default)]
54
+ #[non_exhaustive]
55
+ pub struct ZlibCertificateCompressor;
56
+
57
+ impl CertificateCompressor for ZlibCertificateCompressor {
58
+ const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::ZLIB;
59
+ const CAN_COMPRESS: bool = true;
60
+ const CAN_DECOMPRESS: bool = true;
61
+
62
+ fn compress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
63
+ where
64
+ W: Write,
65
+ {
66
+ let mut encoder = ZlibEncoder::new(output, Compression::default());
67
+ encoder.write_all(input)?;
68
+ encoder.finish()?;
69
+ Ok(())
70
+ }
71
+
72
+ fn decompress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
73
+ where
74
+ W: Write,
75
+ {
76
+ let mut decoder = ZlibDecoder::new(input);
77
+ io::copy(&mut decoder, output)?;
78
+ Ok(())
79
+ }
80
+ }
81
+
82
+ #[derive(Debug, Clone, Default)]
83
+ #[non_exhaustive]
84
+ pub struct ZstdCertificateCompressor;
85
+
86
+ impl CertificateCompressor for ZstdCertificateCompressor {
87
+ const ALGORITHM: CertificateCompressionAlgorithm = CertificateCompressionAlgorithm::ZSTD;
88
+ const CAN_COMPRESS: bool = true;
89
+ const CAN_DECOMPRESS: bool = true;
90
+
91
+ fn compress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
92
+ where
93
+ W: Write,
94
+ {
95
+ let mut writer = ZstdEncoder::new(output, 0)?;
96
+ writer.write_all(input)?;
97
+ writer.flush()?;
98
+ Ok(())
99
+ }
100
+
101
+ fn decompress<W>(&self, input: &[u8], output: &mut W) -> Result<()>
102
+ where
103
+ W: Write,
104
+ {
105
+ let mut reader = ZstdDecoder::new(input)?;
106
+ let mut buf = [0u8; 4096];
107
+ loop {
108
+ match reader.read(&mut buf[..]) {
109
+ Err(e) => {
110
+ if let io::ErrorKind::Interrupted = e.kind() {
111
+ continue;
112
+ }
113
+ return Err(e);
114
+ }
115
+ Ok(size) => {
116
+ if size == 0 {
117
+ break;
118
+ }
119
+ output.write_all(&buf[..size])?;
120
+ }
121
+ }
122
+ }
123
+ Ok(())
124
+ }
125
+ }
@@ -0,0 +1,82 @@
1
+ use boring2::ssl::{SslConnectorBuilder, SslVerifyMode};
2
+
3
+ use crate::{
4
+ Error,
5
+ tls::{
6
+ CertificateCompressionAlgorithm,
7
+ conn::cert_compression::{
8
+ BrotliCertificateCompressor, ZlibCertificateCompressor, ZstdCertificateCompressor,
9
+ },
10
+ x509::CertStore,
11
+ },
12
+ };
13
+
14
+ /// SslConnectorBuilderExt trait for `SslConnectorBuilder`.
15
+ pub trait SslConnectorBuilderExt {
16
+ /// Configure the CertStore for the given `SslConnectorBuilder`.
17
+ fn set_cert_store(self, store: Option<&CertStore>) -> crate::Result<SslConnectorBuilder>;
18
+
19
+ /// Configure the certificate verification for the given `SslConnectorBuilder`.
20
+ fn set_cert_verification(self, enable: bool) -> crate::Result<SslConnectorBuilder>;
21
+
22
+ /// Configure the certificate compression algorithm for the given `SslConnectorBuilder`.
23
+ fn add_certificate_compression_algorithms(
24
+ self,
25
+ algs: Option<&[CertificateCompressionAlgorithm]>,
26
+ ) -> crate::Result<SslConnectorBuilder>;
27
+ }
28
+
29
+ impl SslConnectorBuilderExt for SslConnectorBuilder {
30
+ #[inline]
31
+ fn set_cert_store(mut self, store: Option<&CertStore>) -> crate::Result<SslConnectorBuilder> {
32
+ if let Some(store) = store {
33
+ store.add_to_tls(&mut self);
34
+ } else {
35
+ self.set_default_verify_paths().map_err(Error::tls)?;
36
+ }
37
+
38
+ Ok(self)
39
+ }
40
+
41
+ #[inline]
42
+ fn set_cert_verification(mut self, enable: bool) -> crate::Result<SslConnectorBuilder> {
43
+ if enable {
44
+ self.set_verify(SslVerifyMode::PEER);
45
+ } else {
46
+ self.set_verify(SslVerifyMode::NONE);
47
+ }
48
+ Ok(self)
49
+ }
50
+
51
+ #[inline]
52
+ fn add_certificate_compression_algorithms(
53
+ mut self,
54
+ algs: Option<&[CertificateCompressionAlgorithm]>,
55
+ ) -> crate::Result<SslConnectorBuilder> {
56
+ if let Some(algs) = algs {
57
+ for algorithm in algs.iter() {
58
+ let res =
59
+ match *algorithm {
60
+ CertificateCompressionAlgorithm::ZLIB => self
61
+ .add_certificate_compression_algorithm(
62
+ ZlibCertificateCompressor::default(),
63
+ ),
64
+ CertificateCompressionAlgorithm::BROTLI => self
65
+ .add_certificate_compression_algorithm(
66
+ BrotliCertificateCompressor::default(),
67
+ ),
68
+ CertificateCompressionAlgorithm::ZSTD => self
69
+ .add_certificate_compression_algorithm(
70
+ ZstdCertificateCompressor::default(),
71
+ ),
72
+ _ => continue,
73
+ };
74
+
75
+ if let Err(e) = res {
76
+ return Err(Error::tls(e));
77
+ }
78
+ }
79
+ }
80
+ Ok(self)
81
+ }
82
+ }
@@ -0,0 +1,34 @@
1
+ macro_rules! set_bool {
2
+ ($cfg:expr, $field:ident, $conn:expr, $setter:ident) => {
3
+ if $cfg.$field {
4
+ $conn.$setter();
5
+ }
6
+ };
7
+ ($cfg:expr, !$field:ident, $conn:expr, $setter:ident, $arg:expr) => {
8
+ if !$cfg.$field {
9
+ $conn.$setter($arg);
10
+ }
11
+ };
12
+ }
13
+
14
+ macro_rules! set_option {
15
+ ($cfg:expr, $field:ident, $conn:expr, $setter:ident) => {
16
+ if let Some(val) = $cfg.$field {
17
+ $conn.$setter(val);
18
+ }
19
+ };
20
+ }
21
+
22
+ macro_rules! set_option_ref_try {
23
+ ($cfg:expr, $field:ident, $conn:expr, $setter:ident) => {
24
+ if let Some(val) = $cfg.$field.as_ref() {
25
+ $conn.$setter(val).map_err(Error::tls)?;
26
+ }
27
+ };
28
+ }
29
+
30
+ macro_rules! set_option_inner_try {
31
+ ($field:ident, $conn:expr, $setter:ident) => {
32
+ $conn.$setter($field.map(|v| v.0)).map_err(Error::tls)?;
33
+ };
34
+ }