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.
- checksums.yaml +7 -0
- data/Cargo.lock +2688 -0
- data/Cargo.toml +6 -0
- data/README.md +179 -0
- data/ext/wreq_rb/Cargo.toml +39 -0
- data/ext/wreq_rb/extconf.rb +22 -0
- data/ext/wreq_rb/src/client.rs +565 -0
- data/ext/wreq_rb/src/error.rs +25 -0
- data/ext/wreq_rb/src/lib.rs +20 -0
- data/ext/wreq_rb/src/response.rs +132 -0
- data/lib/wreq-rb/version.rb +5 -0
- data/lib/wreq-rb.rb +17 -0
- data/patches/0001-add-transfer-size-tracking.patch +292 -0
- data/vendor/wreq/Cargo.toml +306 -0
- data/vendor/wreq/LICENSE +202 -0
- data/vendor/wreq/README.md +122 -0
- data/vendor/wreq/examples/cert_store.rs +77 -0
- data/vendor/wreq/examples/connect_via_lower_priority_tokio_runtime.rs +258 -0
- data/vendor/wreq/examples/emulation.rs +118 -0
- data/vendor/wreq/examples/form.rs +14 -0
- data/vendor/wreq/examples/http1_websocket.rs +37 -0
- data/vendor/wreq/examples/http2_websocket.rs +45 -0
- data/vendor/wreq/examples/json_dynamic.rs +41 -0
- data/vendor/wreq/examples/json_typed.rs +47 -0
- data/vendor/wreq/examples/keylog.rs +16 -0
- data/vendor/wreq/examples/request_with_emulation.rs +115 -0
- data/vendor/wreq/examples/request_with_interface.rs +37 -0
- data/vendor/wreq/examples/request_with_local_address.rs +16 -0
- data/vendor/wreq/examples/request_with_proxy.rs +13 -0
- data/vendor/wreq/examples/request_with_redirect.rs +22 -0
- data/vendor/wreq/examples/request_with_version.rs +15 -0
- data/vendor/wreq/examples/tor_socks.rs +24 -0
- data/vendor/wreq/examples/unix_socket.rs +33 -0
- data/vendor/wreq/src/client/body.rs +304 -0
- data/vendor/wreq/src/client/conn/conn.rs +231 -0
- data/vendor/wreq/src/client/conn/connector.rs +549 -0
- data/vendor/wreq/src/client/conn/http.rs +1023 -0
- data/vendor/wreq/src/client/conn/proxy/socks.rs +233 -0
- data/vendor/wreq/src/client/conn/proxy/tunnel.rs +260 -0
- data/vendor/wreq/src/client/conn/proxy.rs +39 -0
- data/vendor/wreq/src/client/conn/tls_info.rs +98 -0
- data/vendor/wreq/src/client/conn/uds.rs +44 -0
- data/vendor/wreq/src/client/conn/verbose.rs +149 -0
- data/vendor/wreq/src/client/conn.rs +323 -0
- data/vendor/wreq/src/client/core/body/incoming.rs +485 -0
- data/vendor/wreq/src/client/core/body/length.rs +118 -0
- data/vendor/wreq/src/client/core/body.rs +34 -0
- data/vendor/wreq/src/client/core/common/buf.rs +149 -0
- data/vendor/wreq/src/client/core/common/rewind.rs +141 -0
- data/vendor/wreq/src/client/core/common/watch.rs +76 -0
- data/vendor/wreq/src/client/core/common.rs +3 -0
- data/vendor/wreq/src/client/core/conn/http1.rs +342 -0
- data/vendor/wreq/src/client/core/conn/http2.rs +307 -0
- data/vendor/wreq/src/client/core/conn.rs +11 -0
- data/vendor/wreq/src/client/core/dispatch.rs +299 -0
- data/vendor/wreq/src/client/core/error.rs +435 -0
- data/vendor/wreq/src/client/core/ext.rs +201 -0
- data/vendor/wreq/src/client/core/http1.rs +178 -0
- data/vendor/wreq/src/client/core/http2.rs +483 -0
- data/vendor/wreq/src/client/core/proto/h1/conn.rs +988 -0
- data/vendor/wreq/src/client/core/proto/h1/decode.rs +1170 -0
- data/vendor/wreq/src/client/core/proto/h1/dispatch.rs +684 -0
- data/vendor/wreq/src/client/core/proto/h1/encode.rs +580 -0
- data/vendor/wreq/src/client/core/proto/h1/io.rs +879 -0
- data/vendor/wreq/src/client/core/proto/h1/role.rs +694 -0
- data/vendor/wreq/src/client/core/proto/h1.rs +104 -0
- data/vendor/wreq/src/client/core/proto/h2/client.rs +650 -0
- data/vendor/wreq/src/client/core/proto/h2/ping.rs +539 -0
- data/vendor/wreq/src/client/core/proto/h2.rs +379 -0
- data/vendor/wreq/src/client/core/proto/headers.rs +138 -0
- data/vendor/wreq/src/client/core/proto.rs +58 -0
- data/vendor/wreq/src/client/core/rt/bounds.rs +57 -0
- data/vendor/wreq/src/client/core/rt/timer.rs +150 -0
- data/vendor/wreq/src/client/core/rt/tokio.rs +99 -0
- data/vendor/wreq/src/client/core/rt.rs +25 -0
- data/vendor/wreq/src/client/core/upgrade.rs +267 -0
- data/vendor/wreq/src/client/core.rs +16 -0
- data/vendor/wreq/src/client/emulation.rs +161 -0
- data/vendor/wreq/src/client/http/client/error.rs +142 -0
- data/vendor/wreq/src/client/http/client/exec.rs +29 -0
- data/vendor/wreq/src/client/http/client/extra.rs +77 -0
- data/vendor/wreq/src/client/http/client/lazy.rs +79 -0
- data/vendor/wreq/src/client/http/client/pool.rs +1105 -0
- data/vendor/wreq/src/client/http/client/util.rs +104 -0
- data/vendor/wreq/src/client/http/client.rs +1003 -0
- data/vendor/wreq/src/client/http/future.rs +99 -0
- data/vendor/wreq/src/client/http.rs +1629 -0
- data/vendor/wreq/src/client/layer/config/options.rs +156 -0
- data/vendor/wreq/src/client/layer/config.rs +116 -0
- data/vendor/wreq/src/client/layer/cookie.rs +161 -0
- data/vendor/wreq/src/client/layer/decoder.rs +139 -0
- data/vendor/wreq/src/client/layer/redirect/future.rs +270 -0
- data/vendor/wreq/src/client/layer/redirect/policy.rs +63 -0
- data/vendor/wreq/src/client/layer/redirect.rs +145 -0
- data/vendor/wreq/src/client/layer/retry/classify.rs +105 -0
- data/vendor/wreq/src/client/layer/retry/scope.rs +51 -0
- data/vendor/wreq/src/client/layer/retry.rs +151 -0
- data/vendor/wreq/src/client/layer/timeout/body.rs +233 -0
- data/vendor/wreq/src/client/layer/timeout/future.rs +90 -0
- data/vendor/wreq/src/client/layer/timeout.rs +177 -0
- data/vendor/wreq/src/client/layer.rs +15 -0
- data/vendor/wreq/src/client/multipart.rs +717 -0
- data/vendor/wreq/src/client/request.rs +818 -0
- data/vendor/wreq/src/client/response.rs +534 -0
- data/vendor/wreq/src/client/ws/json.rs +99 -0
- data/vendor/wreq/src/client/ws/message.rs +453 -0
- data/vendor/wreq/src/client/ws.rs +714 -0
- data/vendor/wreq/src/client.rs +27 -0
- data/vendor/wreq/src/config.rs +140 -0
- data/vendor/wreq/src/cookie.rs +579 -0
- data/vendor/wreq/src/dns/gai.rs +249 -0
- data/vendor/wreq/src/dns/hickory.rs +78 -0
- data/vendor/wreq/src/dns/resolve.rs +180 -0
- data/vendor/wreq/src/dns.rs +69 -0
- data/vendor/wreq/src/error.rs +502 -0
- data/vendor/wreq/src/ext.rs +398 -0
- data/vendor/wreq/src/hash.rs +143 -0
- data/vendor/wreq/src/header.rs +506 -0
- data/vendor/wreq/src/into_uri.rs +187 -0
- data/vendor/wreq/src/lib.rs +586 -0
- data/vendor/wreq/src/proxy/mac.rs +82 -0
- data/vendor/wreq/src/proxy/matcher.rs +806 -0
- data/vendor/wreq/src/proxy/uds.rs +66 -0
- data/vendor/wreq/src/proxy/win.rs +31 -0
- data/vendor/wreq/src/proxy.rs +569 -0
- data/vendor/wreq/src/redirect.rs +575 -0
- data/vendor/wreq/src/retry.rs +198 -0
- data/vendor/wreq/src/sync.rs +129 -0
- data/vendor/wreq/src/tls/conn/cache.rs +123 -0
- data/vendor/wreq/src/tls/conn/cert_compression.rs +125 -0
- data/vendor/wreq/src/tls/conn/ext.rs +82 -0
- data/vendor/wreq/src/tls/conn/macros.rs +34 -0
- data/vendor/wreq/src/tls/conn/service.rs +138 -0
- data/vendor/wreq/src/tls/conn.rs +681 -0
- data/vendor/wreq/src/tls/keylog/handle.rs +64 -0
- data/vendor/wreq/src/tls/keylog.rs +99 -0
- data/vendor/wreq/src/tls/options.rs +464 -0
- data/vendor/wreq/src/tls/x509/identity.rs +122 -0
- data/vendor/wreq/src/tls/x509/parser.rs +71 -0
- data/vendor/wreq/src/tls/x509/store.rs +228 -0
- data/vendor/wreq/src/tls/x509.rs +68 -0
- data/vendor/wreq/src/tls.rs +154 -0
- data/vendor/wreq/src/trace.rs +55 -0
- data/vendor/wreq/src/util.rs +122 -0
- data/vendor/wreq/tests/badssl.rs +228 -0
- data/vendor/wreq/tests/brotli.rs +350 -0
- data/vendor/wreq/tests/client.rs +1098 -0
- data/vendor/wreq/tests/connector_layers.rs +227 -0
- data/vendor/wreq/tests/cookie.rs +306 -0
- data/vendor/wreq/tests/deflate.rs +347 -0
- data/vendor/wreq/tests/emulation.rs +260 -0
- data/vendor/wreq/tests/gzip.rs +347 -0
- data/vendor/wreq/tests/layers.rs +261 -0
- data/vendor/wreq/tests/multipart.rs +165 -0
- data/vendor/wreq/tests/proxy.rs +438 -0
- data/vendor/wreq/tests/redirect.rs +629 -0
- data/vendor/wreq/tests/retry.rs +135 -0
- data/vendor/wreq/tests/support/delay_server.rs +117 -0
- data/vendor/wreq/tests/support/error.rs +16 -0
- data/vendor/wreq/tests/support/layer.rs +183 -0
- data/vendor/wreq/tests/support/mod.rs +9 -0
- data/vendor/wreq/tests/support/server.rs +232 -0
- data/vendor/wreq/tests/timeouts.rs +281 -0
- data/vendor/wreq/tests/unix_socket.rs +135 -0
- data/vendor/wreq/tests/upgrade.rs +98 -0
- data/vendor/wreq/tests/zstd.rs +559 -0
- 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
|
+
}
|