24 24 | //! # Responsibilities of a connector
|
25 25 | //!
|
26 26 | //! A connector primarily makes HTTP requests, but is also the place where connect and read timeouts are
|
27 27 | //! implemented. The `HyperConnector` in [`aws-smithy-runtime`] is an example where timeouts are implemented
|
28 28 | //! as part of the connector.
|
29 29 | //!
|
30 30 | //! Connectors are also responsible for DNS lookup, TLS, connection reuse, pooling, and eviction.
|
31 31 | //! The Smithy clients have no knowledge of such concepts.
|
32 32 | //!
|
33 33 | //! # The [`HttpClient`] trait
|
34 34 | //!
|
35 35 | //! Connectors allow us to make requests, but we need a layer on top of connectors so that we can handle
|
36 36 | //! varying connector settings. For example, say we configure some default HTTP connect/read timeouts on
|
37 37 | //! Client, and then configure some override connect/read timeouts for a specific operation. These timeouts
|
38 38 | //! ultimately are part of the connector, so the same connector can't be reused for the two different sets
|
39 39 | //! of timeouts. Thus, the [`HttpClient`] implementation is responsible for managing multiple connectors
|
40 40 | //! with varying config. Some example configs that can impact which connector is used:
|
41 41 | //!
|
42 42 | //! - HTTP protocol versions
|
43 43 | //! - TLS settings
|
44 44 | //! - Timeouts
|
45 45 | //!
|
46 46 | //! Some of these aren't implemented yet, but they will appear in the [`HttpConnectorSettings`] struct
|
47 47 | //! once they are.
|
48 48 | //!
|
49 49 | //! [`hyper`]: https://crates.io/crates/hyper
|
50 50 | //! [`tower`]: https://crates.io/crates/tower
|
51 51 | //! [`aws-smithy-runtime`]: https://crates.io/crates/aws-smithy-runtime
|
52 52 |
|
53 53 | use crate::box_error::BoxError;
|
54 - | use crate::client::connector_metadata::ConnectorMetadata;
|
55 54 | use crate::client::orchestrator::{HttpRequest, HttpResponse};
|
56 55 | use crate::client::result::ConnectorError;
|
57 56 | use crate::client::runtime_components::sealed::ValidateConfig;
|
58 57 | use crate::client::runtime_components::{RuntimeComponents, RuntimeComponentsBuilder};
|
59 58 | use crate::impl_shared_conversions;
|
60 59 | use aws_smithy_types::config_bag::ConfigBag;
|
61 60 | use std::fmt;
|
62 61 | use std::sync::Arc;
|
63 62 | use std::time::Duration;
|
64 63 |
|
65 64 | new_type_future! {
|
66 65 | #[doc = "Future for [`HttpConnector::call`]."]
|
67 66 | pub struct HttpConnectorFuture<'static, HttpResponse, ConnectorError>;
|
68 67 | }
|
69 68 |
|
70 69 | /// Trait with a `call` function that asynchronously converts a request into a response.
|
71 70 | ///
|
72 71 | /// Ordinarily, a connector would use an underlying HTTP library such as [hyper](https://crates.io/crates/hyper),
|
73 72 | /// and any associated HTTPS implementation alongside it to service requests.
|
74 73 | ///
|
75 74 | /// However, it can also be useful to create fake/mock connectors implementing this trait
|
76 75 | /// for testing.
|
77 76 | pub trait HttpConnector: Send + Sync + fmt::Debug {
|
78 77 | /// Asynchronously converts a request into a response.
|
79 78 | fn call(&self, request: HttpRequest) -> HttpConnectorFuture;
|
80 79 | }
|
81 80 |
|
82 81 | /// A shared [`HttpConnector`] implementation.
|
83 82 | #[derive(Clone, Debug)]
|
84 83 | pub struct SharedHttpConnector(Arc<dyn HttpConnector>);
|
139 138 | /// into the connector. The `HttpClient` is responsible for caching
|
140 139 | /// the connector across requests.
|
141 140 | ///
|
142 141 | /// In the future, the settings may have additional parameters added,
|
143 142 | /// such as HTTP version, or TLS certificate paths.
|
144 143 | fn http_connector(
|
145 144 | &self,
|
146 145 | settings: &HttpConnectorSettings,
|
147 146 | components: &RuntimeComponents,
|
148 147 | ) -> SharedHttpConnector;
|
149 148 |
|
150 149 | #[doc = include_str!("../../rustdoc/validate_base_client_config.md")]
|
151 150 | fn validate_base_client_config(
|
152 151 | &self,
|
153 152 | runtime_components: &RuntimeComponentsBuilder,
|
154 153 | cfg: &ConfigBag,
|
155 154 | ) -> Result<(), BoxError> {
|
156 155 | let _ = (runtime_components, cfg);
|
157 156 | Ok(())
|
158 157 | }
|
159 158 |
|
160 159 | #[doc = include_str!("../../rustdoc/validate_final_config.md")]
|
161 160 | fn validate_final_config(
|
162 161 | &self,
|
163 162 | runtime_components: &RuntimeComponents,
|
164 163 | cfg: &ConfigBag,
|
165 164 | ) -> Result<(), BoxError> {
|
166 165 | let _ = (runtime_components, cfg);
|
167 166 | Ok(())
|
168 167 | }
|
169 - |
|
170 - | /// Provide metadata about the crate that this HttpClient uses to make connectors.
|
171 - | ///
|
172 - | /// If this is implemented and returns metadata, interceptors may inspect it
|
173 - | /// for the purpose of inserting that data into the user agent string when
|
174 - | /// making a request with this client.
|
175 - | fn connector_metadata(&self) -> Option<ConnectorMetadata> {
|
176 - | None
|
177 - | }
|
178 168 | }
|
179 169 |
|
180 170 | /// Shared HTTP client for use across multiple clients and requests.
|
181 171 | #[derive(Clone, Debug)]
|
182 172 | pub struct SharedHttpClient {
|
183 173 | selector: Arc<dyn HttpClient>,
|
184 174 | }
|
185 175 |
|
186 176 | impl SharedHttpClient {
|
187 177 | /// Creates a new `SharedHttpClient`
|
188 178 | pub fn new(selector: impl HttpClient + 'static) -> Self {
|
189 179 | Self {
|
190 180 | selector: Arc::new(selector),
|
191 181 | }
|
192 182 | }
|
193 183 | }
|
194 184 |
|
195 185 | impl HttpClient for SharedHttpClient {
|
196 186 | fn http_connector(
|
197 187 | &self,
|
198 188 | settings: &HttpConnectorSettings,
|
199 189 | components: &RuntimeComponents,
|
200 190 | ) -> SharedHttpConnector {
|
201 191 | self.selector.http_connector(settings, components)
|
202 192 | }
|
203 - |
|
204 - | fn connector_metadata(&self) -> Option<ConnectorMetadata> {
|
205 - | self.selector.connector_metadata()
|
206 - | }
|
207 193 | }
|
208 194 |
|
209 195 | impl ValidateConfig for SharedHttpClient {
|
210 196 | fn validate_base_client_config(
|
211 197 | &self,
|
212 - | runtime_components: &RuntimeComponentsBuilder,
|
213 - | cfg: &ConfigBag,
|
214 - | ) -> Result<(), BoxError> {
|
198 + | runtime_components: &super::runtime_components::RuntimeComponentsBuilder,
|
199 + | cfg: &aws_smithy_types::config_bag::ConfigBag,
|
200 + | ) -> Result<(), crate::box_error::BoxError> {
|
215 201 | self.selector
|
216 202 | .validate_base_client_config(runtime_components, cfg)
|
217 203 | }
|
218 204 |
|
219 205 | fn validate_final_config(
|
220 206 | &self,
|
221 207 | runtime_components: &RuntimeComponents,
|
222 - | cfg: &ConfigBag,
|
223 - | ) -> Result<(), BoxError> {
|
208 + | cfg: &aws_smithy_types::config_bag::ConfigBag,
|
209 + | ) -> Result<(), crate::box_error::BoxError> {
|
224 210 | self.selector.validate_final_config(runtime_components, cfg)
|
225 211 | }
|
226 212 | }
|
227 213 |
|
228 214 | impl_shared_conversions!(convert SharedHttpClient from HttpClient using SharedHttpClient::new);
|
229 215 |
|
230 216 | /// Builder for [`HttpConnectorSettings`].
|
231 217 | #[non_exhaustive]
|
232 218 | #[derive(Default, Debug)]
|
233 219 | pub struct HttpConnectorSettingsBuilder {
|
234 220 | connect_timeout: Option<Duration>,
|
235 221 | read_timeout: Option<Duration>,
|
236 222 | }
|
237 223 |
|
238 224 | impl HttpConnectorSettingsBuilder {
|
239 225 | /// Creates a new builder.
|
240 226 | pub fn new() -> Self {
|
241 227 | Default::default()
|
242 228 | }
|
243 229 |
|
244 230 | /// Sets the connect timeout that should be used.
|
245 231 | ///
|
246 232 | /// The connect timeout is a limit on the amount of time it takes to initiate a socket connection.
|
247 233 | pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
|
248 234 | self.connect_timeout = Some(connect_timeout);
|
249 235 | self
|
250 236 | }
|
251 237 |
|
252 238 | /// Sets the connect timeout that should be used.
|
253 239 | ///
|