1 1 | /*
|
2 2 | * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3 3 | * SPDX-License-Identifier: Apache-2.0
|
4 4 | */
|
5 5 |
|
6 6 | //! Types related to connection monitoring and management.
|
7 7 |
|
8 + | mod establishment;
|
9 + |
|
10 + | pub use establishment::{ConnectionEstablishmentMetadata, ConnectionEstablishmentMetadataBuilder};
|
11 + |
|
8 12 | use aws_smithy_types::config_bag::{Storable, StoreReplace};
|
9 13 | use std::fmt;
|
10 14 | use std::net::SocketAddr;
|
11 15 | use std::sync::{Arc, Mutex};
|
12 16 |
|
13 17 | /// Opaque identifier for a physical HTTP connection.
|
14 18 | ///
|
15 19 | /// The assigning HTTP client defines the scope of an ID. IDs from different
|
16 20 | /// clients or pools are not comparable.
|
17 21 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
18 22 | pub struct ConnectionId(u64);
|
19 23 |
|
20 24 | impl ConnectionId {
|
21 25 | /// Creates an ID from a value assigned by an HTTP client.
|
22 26 | pub const fn new(value: u64) -> Self {
|
23 27 | Self(value)
|
24 28 | }
|
25 29 | }
|
26 30 |
|
27 31 | impl fmt::Display for ConnectionId {
|
28 32 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
29 33 | self.0.fmt(f)
|
30 34 | }
|
31 35 | }
|
32 36 |
|
33 37 | /// Metadata that tracks the state of an active connection.
|
34 38 | #[derive(Clone)]
|
35 39 | pub struct ConnectionMetadata {
|
36 40 | is_proxied: bool,
|
37 41 | remote_addr: Option<SocketAddr>,
|
38 42 | local_addr: Option<SocketAddr>,
|
39 43 | poison_fn: Arc<dyn Fn() + Send + Sync>,
|
40 44 | connection_id: Option<ConnectionId>,
|
45 + | establishment: Option<ConnectionEstablishmentMetadata>,
|
41 46 | }
|
42 47 |
|
43 48 | impl ConnectionMetadata {
|
44 49 | /// Poison this connection, ensuring that it won't be reused.
|
45 50 | pub fn poison(&self) {
|
46 51 | tracing::debug!(
|
47 52 | see_for_more_info = "https://smithy-lang.github.io/smithy-rs/design/client/detailed_error_explanations.html",
|
48 53 | "Connection encountered an issue and should not be re-used. Marking it for closure"
|
49 54 | );
|
50 55 | (self.poison_fn)()
|
51 56 | }
|
52 57 |
|
53 58 | /// Create a new [`ConnectionMetadata`].
|
54 59 | #[deprecated(
|
55 60 | since = "1.1.0",
|
56 61 | note = "`ConnectionMetadata::new` is deprecated in favour of `ConnectionMetadata::builder`."
|
57 62 | )]
|
58 63 | pub fn new(
|
59 64 | is_proxied: bool,
|
60 65 | remote_addr: Option<SocketAddr>,
|
61 66 | poison: impl Fn() + Send + Sync + 'static,
|
62 67 | ) -> Self {
|
63 68 | Self {
|
64 69 | is_proxied,
|
65 70 | remote_addr,
|
66 71 | // need to use builder to set this field
|
67 72 | local_addr: None,
|
68 73 | poison_fn: Arc::new(poison),
|
69 74 | connection_id: None,
|
75 + | establishment: None,
|
70 76 | }
|
71 77 | }
|
72 78 |
|
73 79 | /// Builder for this connection metadata
|
74 80 | pub fn builder() -> ConnectionMetadataBuilder {
|
75 81 | ConnectionMetadataBuilder::new()
|
76 82 | }
|
77 83 |
|
78 84 | /// Get the remote address for this connection, if one is set.
|
79 85 | pub fn remote_addr(&self) -> Option<SocketAddr> {
|
80 86 | self.remote_addr
|
81 87 | }
|
82 88 |
|
83 89 | /// Get the local address for this connection, if one is set.
|
84 90 | pub fn local_addr(&self) -> Option<SocketAddr> {
|
85 91 | self.local_addr
|
86 92 | }
|
87 93 |
|
88 94 | /// Get the ID assigned to this connection, if the HTTP client provides one.
|
89 95 | ///
|
90 96 | /// Clients that do not track physical connection identity leave this unset.
|
91 97 | pub fn connection_id(&self) -> Option<ConnectionId> {
|
92 98 | self.connection_id
|
93 99 | }
|
100 + |
|
101 + | /// Get measurements from the establishment that installed this connection.
|
102 + | ///
|
103 + | /// Clients that do not expose successful establishment measurements leave
|
104 + | /// this unset.
|
105 + | pub fn establishment(&self) -> Option<&ConnectionEstablishmentMetadata> {
|
106 + | self.establishment.as_ref()
|
107 + | }
|
94 108 | }
|
95 109 |
|
96 110 | impl fmt::Debug for ConnectionMetadata {
|
97 111 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
98 112 | f.debug_struct("SmithyConnection")
|
99 113 | .field("is_proxied", &self.is_proxied)
|
100 114 | .field("remote_addr", &self.remote_addr)
|
101 115 | .field("local_addr", &self.local_addr)
|
102 116 | .field("connection_id", &self.connection_id)
|
117 + | .field("establishment", &self.establishment)
|
103 118 | .finish()
|
104 119 | }
|
105 120 | }
|
106 121 |
|
107 122 | /// Builder type that is used to construct a [`ConnectionMetadata`] value.
|
108 123 | #[derive(Default)]
|
109 124 | pub struct ConnectionMetadataBuilder {
|
110 125 | is_proxied: Option<bool>,
|
111 126 | remote_addr: Option<SocketAddr>,
|
112 127 | local_addr: Option<SocketAddr>,
|
113 128 | poison_fn: Option<Arc<dyn Fn() + Send + Sync>>,
|
114 129 | connection_id: Option<ConnectionId>,
|
130 + | establishment: Option<ConnectionEstablishmentMetadata>,
|
115 131 | }
|
116 132 |
|
117 133 | impl fmt::Debug for ConnectionMetadataBuilder {
|
118 134 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
119 135 | f.debug_struct("ConnectionMetadataBuilder")
|
120 136 | .field("is_proxied", &self.is_proxied)
|
121 137 | .field("remote_addr", &self.remote_addr)
|
122 138 | .field("local_addr", &self.local_addr)
|
123 139 | .field("connection_id", &self.connection_id)
|
140 + | .field("establishment", &self.establishment)
|
124 141 | .finish()
|
125 142 | }
|
126 143 | }
|
127 144 |
|
128 145 | impl ConnectionMetadataBuilder {
|
129 146 | /// Creates a new builder.
|
130 147 | pub fn new() -> Self {
|
131 148 | Self::default()
|
132 149 | }
|
133 150 |
|
134 151 | /// Set whether or not the associated connection is to an HTTP proxy.
|
135 152 | pub fn proxied(mut self, proxied: bool) -> Self {
|
136 153 | self.set_proxied(Some(proxied));
|
137 154 | self
|
138 155 | }
|
139 156 |
|
140 157 | /// Set whether or not the associated connection is to an HTTP proxy.
|
141 158 | pub fn set_proxied(&mut self, proxied: Option<bool>) -> &mut Self {
|
142 159 | self.is_proxied = proxied;
|
143 160 | self
|
144 161 | }
|
145 162 |
|
146 163 | /// Set the remote address of the connection used.
|
147 164 | pub fn remote_addr(mut self, remote_addr: SocketAddr) -> Self {
|
148 165 | self.set_remote_addr(Some(remote_addr));
|
149 166 | self
|
150 167 | }
|
151 168 |
|
152 169 | /// Set the remote address of the connection used.
|
153 170 | pub fn set_remote_addr(&mut self, remote_addr: Option<SocketAddr>) -> &mut Self {
|
154 171 | self.remote_addr = remote_addr;
|
155 172 | self
|
156 173 | }
|
157 174 |
|
158 175 | /// Set the local address of the connection used.
|
159 176 | pub fn local_addr(mut self, local_addr: SocketAddr) -> Self {
|
160 177 | self.set_local_addr(Some(local_addr));
|
161 178 | self
|
162 179 | }
|
163 180 |
|
164 181 | /// Set the local address of the connection used.
|
165 182 | pub fn set_local_addr(&mut self, local_addr: Option<SocketAddr>) -> &mut Self {
|
166 183 | self.local_addr = local_addr;
|
167 184 | self
|
168 185 | }
|
169 186 |
|
170 187 | /// Set the [`ConnectionId`] assigned by the HTTP client.
|
171 188 | pub fn connection_id(mut self, connection_id: ConnectionId) -> Self {
|
172 189 | self.set_connection_id(Some(connection_id));
|
173 190 | self
|
174 191 | }
|
175 192 |
|
176 193 | /// Set the [`ConnectionId`] assigned by the HTTP client.
|
177 194 | pub fn set_connection_id(&mut self, connection_id: Option<ConnectionId>) -> &mut Self {
|
178 195 | self.connection_id = connection_id;
|
179 196 | self
|
180 197 | }
|
181 198 |
|
199 + | /// Sets measurements from the establishment that installed this connection.
|
200 + | pub fn establishment(mut self, establishment: ConnectionEstablishmentMetadata) -> Self {
|
201 + | self.set_establishment(Some(establishment));
|
202 + | self
|
203 + | }
|
204 + |
|
205 + | /// Sets measurements from the establishment that installed this connection.
|
206 + | pub fn set_establishment(
|
207 + | &mut self,
|
208 + | establishment: Option<ConnectionEstablishmentMetadata>,
|
209 + | ) -> &mut Self {
|
210 + | self.establishment = establishment;
|
211 + | self
|
212 + | }
|
213 + |
|
182 214 | /// Set a closure which will poison the associated connection.
|
183 215 | ///
|
184 216 | /// A poisoned connection will not be reused for subsequent requests by the pool
|
185 217 | pub fn poison_fn(mut self, poison_fn: impl Fn() + Send + Sync + 'static) -> Self {
|
186 218 | self.set_poison_fn(Some(poison_fn));
|
187 219 | self
|
188 220 | }
|
189 221 |
|
190 222 | /// Set a closure which will poison the associated connection.
|
191 223 | ///
|
192 224 | /// A poisoned connection will not be reused for subsequent requests by the pool
|
193 225 | pub fn set_poison_fn(
|
194 226 | &mut self,
|
195 227 | poison_fn: Option<impl Fn() + Send + Sync + 'static>,
|
196 228 | ) -> &mut Self {
|
197 229 | self.poison_fn =
|
198 230 | poison_fn.map(|poison_fn| Arc::new(poison_fn) as Arc<dyn Fn() + Send + Sync>);
|
199 231 | self
|
200 232 | }
|
201 233 |
|
202 234 | /// Build a [`ConnectionMetadata`] value.
|
203 235 | ///
|
204 236 | /// # Panics
|
205 237 | ///
|
206 238 | /// If either the `is_proxied` or `poison_fn` has not been set, then this method will panic
|
207 239 | pub fn build(self) -> ConnectionMetadata {
|
208 240 | ConnectionMetadata {
|
209 241 | is_proxied: self
|
210 242 | .is_proxied
|
211 243 | .expect("is_proxied should be set for ConnectionMetadata"),
|
212 244 | remote_addr: self.remote_addr,
|
213 245 | local_addr: self.local_addr,
|
214 246 | poison_fn: self
|
215 247 | .poison_fn
|
216 248 | .expect("poison_fn should be set for ConnectionMetadata"),
|
217 249 | connection_id: self.connection_id,
|
250 + | establishment: self.establishment,
|
218 251 | }
|
219 252 | }
|
220 253 | }
|
221 254 |
|
222 255 | type LoaderFn = dyn Fn() -> Option<ConnectionMetadata> + Send + Sync;
|
223 256 |
|
224 257 | /// State for a middleware that will monitor and manage connections.
|
225 258 | #[derive(Clone, Default)]
|
226 259 | pub struct CaptureSmithyConnection {
|
227 260 | loader: Arc<Mutex<Option<Box<LoaderFn>>>>,
|
228 261 | }
|
229 262 |
|
230 263 | impl CaptureSmithyConnection {
|
231 264 | /// Create a new connection monitor.
|
232 265 | pub fn new() -> Self {
|
233 266 | Self {
|
234 267 | loader: Default::default(),
|
235 268 | }
|
236 269 | }
|
237 270 |
|
238 271 | /// Set the retriever that will capture the `hyper` connection.
|
239 272 | pub fn set_connection_retriever<F>(&self, f: F)
|
240 273 | where
|
241 274 | F: Fn() -> Option<ConnectionMetadata> + Send + Sync + 'static,
|
242 275 | {
|
243 276 | *self.loader.lock().unwrap() = Some(Box::new(f));
|
244 277 | }
|
245 278 |
|
246 279 | /// Get the associated connection metadata.
|
247 280 | pub fn get(&self) -> Option<ConnectionMetadata> {
|
248 281 | match self.loader.lock().unwrap().as_ref() {
|
249 282 | Some(loader) => loader(),
|
250 283 | None => {
|
251 284 | tracing::debug!("no loader was set on the CaptureSmithyConnection");
|
252 285 | None
|
253 286 | }
|
254 287 | }
|
255 288 | }
|
256 289 | }
|
257 290 |
|
258 291 | impl fmt::Debug for CaptureSmithyConnection {
|
259 292 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
260 293 | write!(f, "CaptureSmithyConnection")
|
261 294 | }
|
262 295 | }
|
263 296 |
|
264 297 | impl Storable for CaptureSmithyConnection {
|
265 298 | type Storer = StoreReplace<Self>;
|
266 299 | }
|
267 300 |
|
268 301 | #[cfg(test)]
|
269 302 | mod tests {
|
270 303 | use std::{
|
271 304 | net::{IpAddr, Ipv6Addr},
|
272 305 | sync::Mutex,
|
306 + | time::Duration,
|
273 307 | };
|
274 308 |
|
275 309 | use super::*;
|
276 310 |
|
277 311 | const TEST_SOCKET_ADDR: SocketAddr = SocketAddr::new(IpAddr::V6(Ipv6Addr::UNSPECIFIED), 100);
|
278 312 |
|
279 313 | #[test]
|
280 314 | #[should_panic]
|
281 315 | fn builder_panic_missing_proxied() {
|
282 316 | ConnectionMetadataBuilder::new()
|
283 317 | .poison_fn(|| {})
|
284 318 | .local_addr(TEST_SOCKET_ADDR)
|
285 319 | .remote_addr(TEST_SOCKET_ADDR)
|
286 320 | .build();
|
287 321 | }
|
288 322 |
|
289 323 | #[test]
|
290 324 | #[should_panic]
|
291 325 | fn builder_panic_missing_poison_fn() {
|
292 326 | ConnectionMetadataBuilder::new()
|
293 327 | .proxied(true)
|
294 328 | .local_addr(TEST_SOCKET_ADDR)
|
295 329 | .remote_addr(TEST_SOCKET_ADDR)
|
296 330 | .build();
|
297 331 | }
|
298 332 |
|
299 333 | #[test]
|
300 334 | fn builder_all_fields_successful() {
|
301 335 | let mutable_flag = Arc::new(Mutex::new(false));
|
336 + | let establishment = ConnectionEstablishmentMetadata::builder()
|
337 + | .total_duration(Duration::from_millis(13))
|
338 + | .transport_duration(Duration::from_millis(8))
|
339 + | .protocol_handshake_duration(Duration::from_millis(5))
|
340 + | .dns_duration(Duration::from_millis(1))
|
341 + | .socket_connect_duration(Duration::from_millis(2))
|
342 + | .proxy_duration(Duration::from_millis(3))
|
343 + | .tls_duration(Duration::from_millis(4))
|
344 + | .build();
|
302 345 |
|
303 346 | let connection_metadata = ConnectionMetadataBuilder::new()
|
304 347 | .proxied(true)
|
305 348 | .local_addr(TEST_SOCKET_ADDR)
|
306 349 | .remote_addr(TEST_SOCKET_ADDR)
|
307 350 | .connection_id(ConnectionId::new(17))
|
351 + | .establishment(establishment.clone())
|
308 352 | .poison_fn({
|
309 353 | let mutable_flag = Arc::clone(&mutable_flag);
|
310 354 | move || {
|
311 355 | let mut guard = mutable_flag.lock().unwrap();
|
312 356 | *guard = !*guard;
|
313 357 | }
|
314 358 | })
|
315 359 | .build();
|
316 360 |
|
317 361 | assert!(connection_metadata.is_proxied);
|
318 362 | assert_eq!(connection_metadata.remote_addr(), Some(TEST_SOCKET_ADDR));
|
319 363 | assert_eq!(connection_metadata.local_addr(), Some(TEST_SOCKET_ADDR));
|
320 364 | assert_eq!(
|
321 365 | connection_metadata.connection_id(),
|
322 366 | Some(ConnectionId::new(17))
|
323 367 | );
|
368 + | assert_eq!(connection_metadata.establishment(), Some(&establishment));
|
369 + | assert_eq!(establishment.total_duration(), Duration::from_millis(13));
|
370 + | assert_eq!(establishment.transport_duration(), Duration::from_millis(8));
|
371 + | assert_eq!(
|
372 + | establishment.protocol_handshake_duration(),
|
373 + | Some(Duration::from_millis(5))
|
374 + | );
|
375 + | assert_eq!(establishment.dns_duration(), Some(Duration::from_millis(1)));
|
376 + | assert_eq!(
|
377 + | establishment.socket_connect_duration(),
|
378 + | Some(Duration::from_millis(2))
|
379 + | );
|
380 + | assert_eq!(
|
381 + | establishment.proxy_duration(),
|
382 + | Some(Duration::from_millis(3))
|
383 + | );
|
384 + | assert_eq!(establishment.tls_duration(), Some(Duration::from_millis(4)));
|
324 385 | assert_eq!("17", ConnectionId::new(17).to_string());
|
325 386 | assert!(!(*mutable_flag.lock().unwrap()));
|
326 387 | connection_metadata.poison();
|
327 388 | assert!(*mutable_flag.lock().unwrap());
|
328 389 | }
|
329 390 |
|
330 391 | #[test]
|
331 392 | fn builder_optional_fields_translate() {
|
332 393 | let metadata1 = ConnectionMetadataBuilder::new()
|
333 394 | .proxied(true)
|
334 395 | .poison_fn(|| {})
|
335 396 | .build();
|
336 397 |
|
337 398 | assert_eq!(metadata1.local_addr(), None);
|
338 399 | assert_eq!(metadata1.remote_addr(), None);
|
339 400 | assert_eq!(metadata1.connection_id(), None);
|
401 + | assert_eq!(metadata1.establishment(), None);
|
340 402 |
|
341 403 | let metadata2 = ConnectionMetadataBuilder::new()
|
342 404 | .proxied(true)
|
343 405 | .poison_fn(|| {})
|
344 406 | .local_addr(TEST_SOCKET_ADDR)
|
345 407 | .build();
|
346 408 |
|
347 409 | assert_eq!(metadata2.local_addr(), Some(TEST_SOCKET_ADDR));
|
348 410 | assert_eq!(metadata2.remote_addr(), None);
|
349 411 |
|