Skip to main content

conjure_runtime_rustls_platform_verifier/
builder.rs

1// Copyright 2020 Palantir Technologies, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//! The client builder.
15#[cfg(not(target_arch = "wasm32"))]
16use crate::blocking;
17use crate::client::ClientState;
18use crate::config::{ProxyConfig, SecurityConfig, ServiceConfig};
19use crate::weak_cache::Cached;
20use crate::{Client, HostMetricsRegistry, UserAgent};
21use arc_swap::ArcSwap;
22use conjure_error::Error;
23use conjure_http::client::ConjureRuntime;
24use rustls::client::ResolvesClientCert;
25use std::borrow::Cow;
26use std::sync::Arc;
27use std::time::Duration;
28#[cfg(not(target_arch = "wasm32"))]
29use tokio::runtime::Handle;
30use url::Url;
31use witchcraft_metrics::MetricRegistry;
32
33const MESH_PREFIX: &str = "mesh-";
34
35/// A builder to construct [`Client`]s and [`blocking::Client`]s.
36pub struct Builder<T = Complete>(T);
37
38/// The service builder stage.
39pub struct ServiceStage(());
40
41/// The user agent builder stage.
42pub struct UserAgentStage {
43    service: String,
44}
45
46#[derive(Clone, PartialEq, Eq, Hash)]
47pub(crate) struct CachedConfig {
48    service: String,
49    user_agent: UserAgent,
50    uris: Vec<Url>,
51    security: SecurityConfig,
52    proxy: ProxyConfig,
53    connect_timeout: Duration,
54    read_timeout: Duration,
55    write_timeout: Duration,
56    backoff_slot_size: Duration,
57    max_num_retries: u32,
58    client_qos: ClientQos,
59    server_qos: ServerQos,
60    service_error: ServiceError,
61    idempotency: Idempotency,
62    node_selection_strategy: NodeSelectionStrategy,
63    override_host_index: Option<usize>,
64}
65
66#[derive(Clone)]
67pub(crate) struct UncachedConfig {
68    pub(crate) metrics: Option<Arc<MetricRegistry>>,
69    pub(crate) host_metrics: Option<Arc<HostMetricsRegistry>>,
70    #[cfg(not(target_arch = "wasm32"))]
71    pub(crate) blocking_handle: Option<Handle>,
72    pub(crate) conjure_runtime: Arc<ConjureRuntime>,
73    pub(crate) client_cert_resolver: Option<Arc<dyn ResolvesClientCert>>,
74}
75
76/// The complete builder stage.
77pub struct Complete {
78    cached: CachedConfig,
79    uncached: UncachedConfig,
80}
81
82impl Default for Builder<ServiceStage> {
83    #[inline]
84    fn default() -> Self {
85        Builder::new()
86    }
87}
88
89impl Builder<ServiceStage> {
90    /// Creates a new builder with default settings.
91    #[inline]
92    pub fn new() -> Self {
93        Builder(ServiceStage(()))
94    }
95
96    /// Sets the name of the service this client will communicate with.
97    ///
98    /// This is used in logging and metrics to allow differentiation between different clients.
99    #[inline]
100    pub fn service(self, service: &str) -> Builder<UserAgentStage> {
101        Builder(UserAgentStage {
102            service: service.to_string(),
103        })
104    }
105}
106
107impl Builder<UserAgentStage> {
108    /// Sets the user agent sent by this client.
109    #[inline]
110    pub fn user_agent(self, user_agent: UserAgent) -> Builder {
111        Builder(Complete {
112            cached: CachedConfig {
113                service: self.0.service,
114                user_agent,
115                uris: vec![],
116                security: SecurityConfig::builder().build(),
117                proxy: ProxyConfig::Direct,
118                connect_timeout: Duration::from_secs(10),
119                read_timeout: Duration::from_secs(5 * 60),
120                write_timeout: Duration::from_secs(5 * 60),
121                backoff_slot_size: Duration::from_millis(250),
122                max_num_retries: 4,
123                client_qos: ClientQos::Enabled,
124                server_qos: ServerQos::AutomaticRetry,
125                service_error: ServiceError::WrapInNewError,
126                idempotency: Idempotency::ByMethod,
127                node_selection_strategy: NodeSelectionStrategy::PinUntilError,
128                override_host_index: None,
129            },
130            uncached: UncachedConfig {
131                metrics: None,
132                host_metrics: None,
133                #[cfg(not(target_arch = "wasm32"))]
134                blocking_handle: None,
135                conjure_runtime: Arc::new(ConjureRuntime::new()),
136                client_cert_resolver: None,
137            },
138        })
139    }
140}
141
142#[cfg(test)]
143impl Builder {
144    pub(crate) fn for_test() -> Self {
145        use crate::Agent;
146
147        Builder::new()
148            .service("test")
149            .user_agent(UserAgent::new(Agent::new("test", "0.0.0")))
150    }
151}
152
153impl Builder<Complete> {
154    pub(crate) fn cached_config(&self) -> &CachedConfig {
155        &self.0.cached
156    }
157
158    /// Applies configuration settings from a `ServiceConfig` to the builder.
159    #[inline]
160    pub fn from_config(mut self, config: &ServiceConfig) -> Self {
161        self = self.uris(config.uris().to_vec());
162
163        if let Some(security) = config.security() {
164            self = self.security(security.clone());
165        }
166
167        if let Some(proxy) = config.proxy() {
168            self = self.proxy(proxy.clone());
169        }
170
171        if let Some(connect_timeout) = config.connect_timeout() {
172            self = self.connect_timeout(connect_timeout);
173        }
174
175        if let Some(read_timeout) = config.read_timeout() {
176            self = self.read_timeout(read_timeout);
177        }
178
179        if let Some(write_timeout) = config.write_timeout() {
180            self = self.write_timeout(write_timeout);
181        }
182
183        if let Some(backoff_slot_size) = config.backoff_slot_size() {
184            self = self.backoff_slot_size(backoff_slot_size);
185        }
186
187        if let Some(max_num_retries) = config.max_num_retries() {
188            self = self.max_num_retries(max_num_retries);
189        }
190
191        self
192    }
193
194    /// Returns the builder's configured service name.
195    #[inline]
196    pub fn get_service(&self) -> &str {
197        &self.0.cached.service
198    }
199
200    /// Returns the builder's configured user agent.
201    #[inline]
202    pub fn get_user_agent(&self) -> &UserAgent {
203        &self.0.cached.user_agent
204    }
205
206    /// Appends a URI to the URIs list.
207    ///
208    /// Defaults to an empty list.
209    #[inline]
210    pub fn uri(mut self, uri: Url) -> Self {
211        self.0.cached.uris.push(uri);
212        self
213    }
214
215    /// Sets the URIs list.
216    ///
217    /// Defaults to an empty list.
218    #[inline]
219    pub fn uris(mut self, uris: Vec<Url>) -> Self {
220        self.0.cached.uris = uris;
221        self
222    }
223
224    /// Returns the builder's configured URIs list.
225    #[inline]
226    pub fn get_uris(&self) -> &[Url] {
227        &self.0.cached.uris
228    }
229
230    /// Sets the security configuration.
231    ///
232    /// Defaults to an empty configuration.
233    #[inline]
234    pub fn security(mut self, security: SecurityConfig) -> Self {
235        self.0.cached.security = security;
236        self
237    }
238
239    /// Returns the builder's configured security configuration.
240    #[inline]
241    pub fn get_security(&self) -> &SecurityConfig {
242        &self.0.cached.security
243    }
244
245    /// Sets the proxy configuration.
246    ///
247    /// Defaults to `ProxyConfig::Direct` (i.e. no proxy).
248    #[inline]
249    pub fn proxy(mut self, proxy: ProxyConfig) -> Self {
250        self.0.cached.proxy = proxy;
251        self
252    }
253
254    /// Returns the builder's configured proxy configuration.
255    #[inline]
256    pub fn get_proxy(&self) -> &ProxyConfig {
257        &self.0.cached.proxy
258    }
259
260    /// Sets the connect timeout.
261    ///
262    /// Defaults to 10 seconds.
263    #[inline]
264    pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
265        self.0.cached.connect_timeout = connect_timeout;
266        self
267    }
268
269    /// Returns the builder's configured connect timeout.
270    #[inline]
271    pub fn get_connect_timeout(&self) -> Duration {
272        self.0.cached.connect_timeout
273    }
274
275    /// Sets the read timeout.
276    ///
277    /// This timeout applies to socket-level read attempts.
278    ///
279    /// Defaults to 5 minutes.
280    #[inline]
281    pub fn read_timeout(mut self, read_timeout: Duration) -> Self {
282        self.0.cached.read_timeout = read_timeout;
283        self
284    }
285
286    /// Returns the builder's configured read timeout.
287    #[inline]
288    pub fn get_read_timeout(&self) -> Duration {
289        self.0.cached.read_timeout
290    }
291
292    /// Sets the write timeout.
293    ///
294    /// This timeout applies to socket-level write attempts.
295    ///
296    /// Defaults to 5 minutes.
297    #[inline]
298    pub fn write_timeout(mut self, write_timeout: Duration) -> Self {
299        self.0.cached.write_timeout = write_timeout;
300        self
301    }
302
303    /// Returns the builder's configured write timeout.
304    #[inline]
305    pub fn get_write_timeout(&self) -> Duration {
306        self.0.cached.write_timeout
307    }
308
309    /// Sets the backoff slot size.
310    ///
311    /// This is the upper bound on the initial delay before retrying a request. It grows exponentially as additional
312    /// attempts are made for a given request.
313    ///
314    /// Defaults to 250 milliseconds.
315    #[inline]
316    pub fn backoff_slot_size(mut self, backoff_slot_size: Duration) -> Self {
317        self.0.cached.backoff_slot_size = backoff_slot_size;
318        self
319    }
320
321    /// Returns the builder's configured backoff slot size.
322    #[inline]
323    pub fn get_backoff_slot_size(&self) -> Duration {
324        self.0.cached.backoff_slot_size
325    }
326
327    /// Sets the maximum number of times a request attempt will be retried before giving up.
328    ///
329    /// Defaults to 4.
330    #[inline]
331    pub fn max_num_retries(mut self, max_num_retries: u32) -> Self {
332        self.0.cached.max_num_retries = max_num_retries;
333        self
334    }
335
336    /// Returns the builder's configured maximum number of retries.
337    #[inline]
338    pub fn get_max_num_retries(&self) -> u32 {
339        self.0.cached.max_num_retries
340    }
341
342    /// Sets the client's internal rate limiting behavior.
343    ///
344    /// Defaults to `ClientQos::Enabled`.
345    #[inline]
346    pub fn client_qos(mut self, client_qos: ClientQos) -> Self {
347        self.0.cached.client_qos = client_qos;
348        self
349    }
350
351    /// Returns the builder's configured internal rate limiting behavior.
352    #[inline]
353    pub fn get_client_qos(&self) -> ClientQos {
354        self.0.cached.client_qos
355    }
356
357    /// Sets the client's behavior in response to a QoS error from the server.
358    ///
359    /// Defaults to `ServerQos::AutomaticRetry`.
360    #[inline]
361    pub fn server_qos(mut self, server_qos: ServerQos) -> Self {
362        self.0.cached.server_qos = server_qos;
363        self
364    }
365
366    /// Returns the builder's configured server QoS behavior.
367    #[inline]
368    pub fn get_server_qos(&self) -> ServerQos {
369        self.0.cached.server_qos
370    }
371
372    /// Sets the client's behavior in response to a service error from the server.
373    ///
374    /// Defaults to `ServiceError::WrapInNewError`.
375    #[inline]
376    pub fn service_error(mut self, service_error: ServiceError) -> Self {
377        self.0.cached.service_error = service_error;
378        self
379    }
380
381    /// Returns the builder's configured service error handling behavior.
382    #[inline]
383    pub fn get_service_error(&self) -> ServiceError {
384        self.0.cached.service_error
385    }
386
387    /// Sets the client's behavior to determine if a request is idempotent or not.
388    ///
389    /// Only idempotent requests will be retried.
390    ///
391    /// Defaults to `Idempotency::ByMethod`.
392    #[inline]
393    pub fn idempotency(mut self, idempotency: Idempotency) -> Self {
394        self.0.cached.idempotency = idempotency;
395        self
396    }
397
398    /// Returns the builder's configured idempotency handling behavior.
399    #[inline]
400    pub fn get_idempotency(&self) -> Idempotency {
401        self.0.cached.idempotency
402    }
403
404    /// Sets the client's strategy for selecting a node for a request.
405    ///
406    /// Defaults to `NodeSelectionStrategy::PinUntilError`.
407    #[inline]
408    pub fn node_selection_strategy(
409        mut self,
410        node_selection_strategy: NodeSelectionStrategy,
411    ) -> Self {
412        self.0.cached.node_selection_strategy = node_selection_strategy;
413        self
414    }
415
416    /// Returns the builder's configured node selection strategy.
417    #[inline]
418    pub fn get_node_selection_strategy(&self) -> NodeSelectionStrategy {
419        self.0.cached.node_selection_strategy
420    }
421
422    /// Sets the metric registry used to register client metrics.
423    ///
424    /// Defaults to no registry.
425    #[inline]
426    pub fn metrics(mut self, metrics: Arc<MetricRegistry>) -> Self {
427        self.0.uncached.metrics = Some(metrics);
428        self
429    }
430
431    /// Returns the builder's configured metric registry.
432    #[inline]
433    pub fn get_metrics(&self) -> Option<&Arc<MetricRegistry>> {
434        self.0.uncached.metrics.as_ref()
435    }
436
437    /// Sets the host metrics registry used to track host performance.
438    ///
439    /// Defaults to no registry.
440    #[inline]
441    pub fn host_metrics(mut self, host_metrics: Arc<HostMetricsRegistry>) -> Self {
442        self.0.uncached.host_metrics = Some(host_metrics);
443        self
444    }
445
446    /// Returns the builder's configured host metrics registry.
447    #[inline]
448    pub fn get_host_metrics(&self) -> Option<&Arc<HostMetricsRegistry>> {
449        self.0.uncached.host_metrics.as_ref()
450    }
451
452    /// Sets the Conjure runtime used to configure request and response encodings.
453    ///
454    /// Defaults to `ConjureRuntime::default()`.
455    #[inline]
456    pub fn conjure_runtime(mut self, conjure_runtime: Arc<ConjureRuntime>) -> Self {
457        self.0.uncached.conjure_runtime = conjure_runtime;
458        self
459    }
460
461    /// Returns the configured Conjure runtime.
462    pub fn get_conjure_runtime(&self) -> &Arc<ConjureRuntime> {
463        &self.0.uncached.conjure_runtime
464    }
465
466    /// Sets the client certificate resolver for mTLS connections.
467    ///
468    /// Mutually exclusive with `cert_file`/`key_file`. Defaults to no client certificate.
469    #[inline]
470    pub fn client_cert_resolver(mut self, resolver: Arc<dyn ResolvesClientCert>) -> Self {
471        self.0.uncached.client_cert_resolver = Some(resolver);
472        self
473    }
474
475    /// Returns the configured client certificate resolver.
476    #[inline]
477    pub fn get_client_cert_resolver(&self) -> Option<&Arc<dyn ResolvesClientCert>> {
478        self.0.uncached.client_cert_resolver.as_ref()
479    }
480
481    /// Overrides the `hostIndex` field included in metrics.
482    #[inline]
483    pub fn override_host_index(mut self, override_host_index: usize) -> Self {
484        self.0.cached.override_host_index = Some(override_host_index);
485        self
486    }
487
488    /// Returns the builder's `hostIndex` override.
489    #[inline]
490    pub fn get_override_host_index(&self) -> Option<usize> {
491        self.0.cached.override_host_index
492    }
493
494    pub(crate) fn mesh_mode(&self) -> bool {
495        self.0
496            .cached
497            .uris
498            .iter()
499            .any(|uri| uri.scheme().starts_with(MESH_PREFIX))
500    }
501
502    pub(crate) fn postprocessed_uris(&self) -> Result<Cow<'_, [Url]>, Error> {
503        if self.mesh_mode() {
504            if self.0.cached.uris.len() != 1 {
505                return Err(Error::internal_safe("mesh mode expects exactly one URI")
506                    .with_safe_param("uris", &self.0.cached.uris));
507            }
508
509            let uri = self.0.cached.uris[0]
510                .as_str()
511                .strip_prefix(MESH_PREFIX)
512                .unwrap()
513                .parse()
514                .unwrap();
515
516            Ok(Cow::Owned(vec![uri]))
517        } else {
518            Ok(Cow::Borrowed(&self.0.cached.uris))
519        }
520    }
521
522    /// Creates a new `Client`.
523    pub fn build(&self) -> Result<Client, Error> {
524        let state = ClientState::new(self)?;
525        Ok(Client::new(
526            Arc::new(ArcSwap::new(Arc::new(Cached::uncached(state)))),
527            None,
528        ))
529    }
530}
531
532#[cfg(not(target_arch = "wasm32"))]
533impl Builder<Complete> {
534    /// Returns the `Handle` to the tokio `Runtime` to be used by blocking clients.
535    ///
536    /// This has no effect on async clients.
537    ///
538    /// Defaults to a `conjure-runtime` internal `Runtime`.
539    #[inline]
540    pub fn blocking_handle(mut self, blocking_handle: Handle) -> Self {
541        self.0.uncached.blocking_handle = Some(blocking_handle);
542        self
543    }
544
545    /// Returns the builder's configured blocking handle.
546    #[inline]
547    pub fn get_blocking_handle(&self) -> Option<&Handle> {
548        self.0.uncached.blocking_handle.as_ref()
549    }
550
551    /// Creates a new `blocking::Client`.
552    pub fn build_blocking(&self) -> Result<blocking::Client, Error> {
553        self.build().map(|client| blocking::Client {
554            client,
555            handle: self.0.uncached.blocking_handle.clone(),
556        })
557    }
558}
559
560/// Specifies the beahavior of client-side sympathetic rate limiting.
561#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
562#[non_exhaustive]
563pub enum ClientQos {
564    /// Enable client side rate limiting.
565    ///
566    /// This is the default behavior.
567    Enabled,
568
569    /// Disables client-side rate limiting.
570    ///
571    /// This should only be used when there are known issues with the interaction between a service's rate limiting
572    /// implementation and the client's.
573    DangerousDisableSympatheticClientQos,
574}
575
576/// Specifies the behavior of a client in response to a `QoS` error from a server.
577///
578/// QoS errors have status codes 429 or 503.
579#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
580#[non_exhaustive]
581pub enum ServerQos {
582    /// The client will automatically retry the request when possible in response to a QoS error.
583    ///
584    /// This is the default behavior.
585    AutomaticRetry,
586
587    /// The client will transparently propagate the QoS error without retrying.
588    ///
589    /// This is designed for use when an upstream service has better context on how to handle a QoS error. Propagating
590    /// the error upstream to that service without retrying allows it to handle retry logic internally.
591    Propagate429And503ToCaller,
592}
593
594/// Specifies the behavior of the client in response to a service error from a server.
595///
596/// Service errors are encoded as responses with a 4xx or 5xx response code and a body containing a `SerializableError`.
597#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
598#[non_exhaustive]
599pub enum ServiceError {
600    /// The service error will be propagated as a new internal service error.
601    ///
602    /// The error's cause will contain the information about the received service error, but the error constructed by
603    /// the client will have a different error instance ID, type, etc.
604    ///
605    /// This is the default behavior.
606    WrapInNewError,
607
608    /// The service error will be transparently propagated without change.
609    ///
610    /// This is designed for use when proxying a request to another node, commonly of the same service. By preserving
611    /// the original error's instance ID, type, etc, the upstream service will be able to process the error properly.
612    PropagateToCaller,
613}
614
615/// Specifies the manner in which the client decides if a request is idempotent or not.
616#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
617#[non_exhaustive]
618pub enum Idempotency {
619    /// All requests are assumed to be idempotent.
620    Always,
621
622    /// Only requests with HTTP methods defined as idempotent (GET, HEAD, OPTIONS, TRACE, PUT, and DELETE) are assumed
623    /// to be idempotent.
624    ///
625    /// This is the default behavior.
626    ByMethod,
627
628    /// No requests are assumed to be idempotent.
629    Never,
630}
631
632/// Specifies the strategy used to select a node of a service to use for a request attempt.
633#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
634#[non_exhaustive]
635pub enum NodeSelectionStrategy {
636    /// Pin to a single host as long as it continues to successfully respond to requests.
637    ///
638    /// If the pinned node fails to successfully respond, the client will rotate through the other nodes until it finds
639    /// one that can successfully respond and then pin to that new node. The pinned node will also be randomly rotated
640    /// periodically to help spread load across the cluster.
641    ///
642    /// This is the default behavior.
643    PinUntilError,
644
645    /// Like `PinUntilError` except that the pinned node is never randomly shuffled.
646    PinUntilErrorWithoutReshuffle,
647
648    /// For each new request, select the "next" node (in some unspecified order).
649    Balanced,
650}