Skip to main content

conjure_runtime_rustls_platform_verifier/
client_factory.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 factory.
15#[cfg(not(target_arch = "wasm32"))]
16use crate::blocking;
17use crate::builder::{CachedConfig, UncachedConfig};
18use crate::config::{ServiceConfig, ServicesConfig};
19use crate::weak_cache::{Cached, WeakCache};
20use crate::{Builder, ClientState, Host, PerHostClients};
21use crate::{
22    Client, ClientQos, HostMetricsRegistry, Idempotency, NodeSelectionStrategy, ServerQos,
23    ServiceError, UserAgent,
24};
25use arc_swap::ArcSwap;
26use conjure_error::Error;
27#[cfg(not(target_arch = "wasm32"))]
28use conjure_http::client::Service;
29use conjure_http::client::{AsyncService, ConjureRuntime};
30use conjure_runtime_config::service_config;
31use refreshable::{RefreshHandle, Refreshable};
32use std::borrow::Borrow;
33use std::collections::HashMap;
34use std::sync::Arc;
35#[cfg(not(target_arch = "wasm32"))]
36use tokio::runtime::Handle;
37use witchcraft_log::warn;
38use witchcraft_metrics::MetricRegistry;
39
40const STATE_CACHE_CAPACITY: usize = 10_000;
41
42/// A factory type which can create clients that will live-reload in response to configuration updates.
43#[derive(Clone)]
44pub struct ClientFactory<T = Complete>(T);
45
46/// The config builder stage.
47pub struct ConfigStage(());
48
49/// The user agent builder stage.
50pub struct UserAgentStage {
51    config: Arc<Refreshable<ServicesConfig, Error>>,
52}
53
54#[derive(Clone)]
55struct CacheManager {
56    uncached_inner: UncachedConfig,
57    cache: WeakCache<CachedConfig, ClientState>,
58}
59
60impl CacheManager {
61    fn uncached(&self) -> &UncachedConfig {
62        &self.uncached_inner
63    }
64
65    fn uncached_mut(&mut self) -> &mut UncachedConfig {
66        self.cache = WeakCache::new(STATE_CACHE_CAPACITY);
67        &mut self.uncached_inner
68    }
69}
70
71/// The complete builder stage.
72#[derive(Clone)]
73pub struct Complete {
74    config: Arc<Refreshable<ServicesConfig, Error>>,
75    user_agent: UserAgent,
76    client_qos: ClientQos,
77    server_qos: ServerQos,
78    service_error: ServiceError,
79    idempotency: Idempotency,
80    node_selection_strategy: NodeSelectionStrategy,
81    cache_manager: CacheManager,
82}
83
84impl Default for ClientFactory<ConfigStage> {
85    #[inline]
86    fn default() -> Self {
87        ClientFactory::builder()
88    }
89}
90
91impl ClientFactory<ConfigStage> {
92    /// Creates a new builder to construct a client factory.
93    #[inline]
94    pub fn builder() -> Self {
95        ClientFactory(ConfigStage(()))
96    }
97
98    /// Sets the refreshable configuration used for service clients.
99    #[inline]
100    pub fn config(
101        self,
102        config: Refreshable<ServicesConfig, Error>,
103    ) -> ClientFactory<UserAgentStage> {
104        ClientFactory(UserAgentStage {
105            config: Arc::new(config),
106        })
107    }
108}
109
110impl ClientFactory<UserAgentStage> {
111    /// Sets the user agent sent by clients.
112    #[inline]
113    pub fn user_agent(self, user_agent: UserAgent) -> ClientFactory {
114        ClientFactory(Complete {
115            config: self.0.config,
116            user_agent,
117            client_qos: ClientQos::Enabled,
118            server_qos: ServerQos::AutomaticRetry,
119            service_error: ServiceError::WrapInNewError,
120            idempotency: Idempotency::ByMethod,
121            node_selection_strategy: NodeSelectionStrategy::PinUntilError,
122            cache_manager: CacheManager {
123                uncached_inner: UncachedConfig {
124                    metrics: None,
125                    host_metrics: None,
126                    #[cfg(not(target_arch = "wasm32"))]
127                    blocking_handle: None,
128                    conjure_runtime: Arc::new(ConjureRuntime::new()),
129                    client_cert_resolver: None,
130                },
131                cache: WeakCache::new(STATE_CACHE_CAPACITY),
132            },
133        })
134    }
135}
136
137impl ClientFactory {
138    /// Sets the user agent sent by clients.
139    #[inline]
140    pub fn user_agent(mut self, user_agent: UserAgent) -> Self {
141        self.0.user_agent = user_agent;
142        self
143    }
144
145    /// Returns the configured user agent.
146    #[inline]
147    pub fn get_user_agent(&self) -> &UserAgent {
148        &self.0.user_agent
149    }
150
151    /// Sets clients' rate limiting behavior.
152    ///
153    /// Defaults to `ClientQos::Enabled`.
154    #[inline]
155    pub fn client_qos(mut self, client_qos: ClientQos) -> Self {
156        self.0.client_qos = client_qos;
157        self
158    }
159
160    /// Returns the configured rate limiting behavior
161    #[inline]
162    pub fn get_client_qos(&self) -> ClientQos {
163        self.0.client_qos
164    }
165
166    /// Sets clients' behavior in response to a QoS error from the server.
167    ///
168    /// Defaults to `ServerQos::AutomaticRetry`.
169    #[inline]
170    pub fn server_qos(mut self, server_qos: ServerQos) -> Self {
171        self.0.server_qos = server_qos;
172        self
173    }
174
175    /// Returns the configured QoS behavior.
176    #[inline]
177    pub fn get_server_qos(&self) -> ServerQos {
178        self.0.server_qos
179    }
180
181    /// Sets clients' behavior in response to a service error from the server.
182    ///
183    /// Defaults to `ServiceError::WrapInNewError`.
184    #[inline]
185    pub fn service_error(mut self, service_error: ServiceError) -> Self {
186        self.0.service_error = service_error;
187        self
188    }
189
190    /// Returns the configured service error behavior.
191    #[inline]
192    pub fn get_service_error(&self) -> ServiceError {
193        self.0.service_error
194    }
195
196    /// Sets clients' behavior to determine if a request is idempotent or not.
197    ///
198    /// Only idempotent requests will be retried.
199    ///
200    /// Defaults to `Idempotency::ByMethod`.
201    #[inline]
202    pub fn idempotency(mut self, idempotency: Idempotency) -> Self {
203        self.0.idempotency = idempotency;
204        self
205    }
206
207    /// Returns the configured idempotency behavior.
208    #[inline]
209    pub fn get_idempotency(&self) -> Idempotency {
210        self.0.idempotency
211    }
212
213    /// Sets the clients' strategy for selecting a node for a request.
214    ///
215    /// Defaults to `NodeSelectionStrategy::PinUntilError`.
216    #[inline]
217    pub fn node_selection_strategy(
218        mut self,
219        node_selection_strategy: NodeSelectionStrategy,
220    ) -> Self {
221        self.0.node_selection_strategy = node_selection_strategy;
222        self
223    }
224
225    /// Returns the configured node selection strategy.
226    #[inline]
227    pub fn get_node_selection_strategy(&self) -> NodeSelectionStrategy {
228        self.0.node_selection_strategy
229    }
230
231    /// Sets the metric registry used to register client metrics.
232    ///
233    /// Defaults to no registry.
234    #[inline]
235    pub fn metrics(mut self, metrics: Arc<MetricRegistry>) -> Self {
236        self.0.cache_manager.uncached_mut().metrics = Some(metrics);
237        self
238    }
239
240    /// Returns the configured metrics registry.
241    #[inline]
242    pub fn get_metrics(&self) -> Option<&Arc<MetricRegistry>> {
243        self.0.cache_manager.uncached().metrics.as_ref()
244    }
245
246    /// Sets the host metrics registry used to track host performance.
247    ///
248    /// Defaults to no registry.
249    #[inline]
250    pub fn host_metrics(mut self, host_metrics: Arc<HostMetricsRegistry>) -> Self {
251        self.0.cache_manager.uncached_mut().host_metrics = Some(host_metrics);
252        self
253    }
254
255    /// Returns the configured host metrics registry.
256    #[inline]
257    pub fn get_host_metrics(&self) -> Option<&Arc<HostMetricsRegistry>> {
258        self.0.cache_manager.uncached().host_metrics.as_ref()
259    }
260
261    /// Sets the Conjure runtime used to configure request and response encodings.
262    ///
263    /// Defaults to `ConjureRuntime::default()`.
264    #[inline]
265    pub fn conjure_runtime(mut self, conjure_runtime: Arc<ConjureRuntime>) -> Self {
266        self.0.cache_manager.uncached_mut().conjure_runtime = conjure_runtime;
267        self
268    }
269
270    /// Returns the configured Conjure runtime.
271    pub fn get_conjure_runtime(&self) -> &Arc<ConjureRuntime> {
272        &self.0.cache_manager.uncached().conjure_runtime
273    }
274
275    fn state_builder(&self, service: &str) -> StateBuilder {
276        StateBuilder {
277            service: service.to_string(),
278            user_agent: self.0.user_agent.clone(),
279            client_qos: self.0.client_qos,
280            server_qos: self.0.server_qos,
281            service_error: self.0.service_error,
282            idempotency: self.0.idempotency,
283            node_selection_strategy: self.0.node_selection_strategy,
284            cache_manager: self.0.cache_manager.clone(),
285        }
286    }
287
288    /// Creates a new client for the specified service.
289    ///
290    /// The client's configuration will automatically refresh to track changes in the factory's [`ServicesConfig`].
291    ///
292    /// If no configuration is present for the specified service in the [`ServicesConfig`], the client will
293    /// immediately return an error for all requests.
294    ///
295    /// The method can return any type implementing the `conjure-http` [`AsyncService`] trait. This notably includes all
296    /// Conjure-generated client types as well as the `conjure-runtime` [`Client`] itself.
297    pub fn client<T>(&self, service: &str) -> Result<T, Error>
298    where
299        T: AsyncService<Client>,
300    {
301        self.client_inner(service)
302            .map(|c| T::new(c, &self.0.cache_manager.uncached().conjure_runtime))
303    }
304
305    fn client_inner(&self, service: &str) -> Result<Client, Error> {
306        let service_config = self.0.config.map({
307            let service = service.to_string();
308            move |c| c.merged_service(&service).unwrap_or_default()
309        });
310        let state_builder = self.state_builder(service);
311        Self::raw_client(state_builder, service_config, None)
312    }
313
314    fn raw_client<T>(
315        state_builder: T,
316        service_config: Refreshable<ServiceConfig, Error>,
317        override_host_index: Option<usize>,
318    ) -> Result<Client, Error>
319    where
320        T: Borrow<StateBuilder> + 'static + Sync + Send,
321    {
322        let state = state_builder
323            .borrow()
324            .build(&service_config.get(), override_host_index)?;
325        let state = Arc::new(ArcSwap::new(state));
326
327        let subscription = service_config.try_subscribe({
328            let state = state.clone();
329            move |config| {
330                let new_state = state_builder.borrow().build(config, override_host_index)?;
331                state.store(new_state);
332                Ok(())
333            }
334        })?;
335
336        Ok(Client::new(state, Some(subscription)))
337    }
338
339    /// Creates a refreshable collection of clients, each corresponding to a separate replica of the
340    /// service.
341    ///
342    /// # Note
343    ///
344    /// The client type `T` is assumed to be stateless - each instance will be recreated on every
345    /// refresh.
346    pub fn per_host_clients<T>(
347        &self,
348        service: &str,
349    ) -> Result<Refreshable<PerHostClients<T>, Error>, Error>
350    where
351        T: AsyncService<Client> + 'static + Sync + Send,
352    {
353        self.per_host_clients_inner(service, T::new)
354    }
355
356    fn per_host_clients_inner<T>(
357        &self,
358        service: &str,
359        make_client: impl Fn(Client, &Arc<ConjureRuntime>) -> T + 'static + Sync + Send,
360    ) -> Result<Refreshable<PerHostClients<T>, Error>, Error>
361    where
362        T: 'static + Sync + Send,
363    {
364        let state_builder = Arc::new(self.state_builder(service));
365
366        self.0
367            .config
368            .map({
369                let service = service.to_string();
370                move |c| c.merged_service(&service).unwrap_or_default()
371            })
372            .try_map({
373                let mut client_handles = HashMap::<Host, ClientHandle>::new();
374                move |c| {
375                    let mut new_client_handles = HashMap::new();
376                    let mut clients = HashMap::new();
377                    let mut errors = vec![];
378
379                    for (i, uri) in c.uris().iter().enumerate() {
380                        let host = Host { uri: uri.clone() };
381                        let host_config = service_config::Builder::from(c.clone())
382                            .uris([uri.clone()])
383                            .build();
384
385                        let client_handle = match client_handles.remove(&host) {
386                            Some(mut client_handle) => {
387                                if let Err(es) = client_handle.handle.refresh(host_config) {
388                                    errors.extend(es);
389                                }
390                                client_handle
391                            }
392                            None => {
393                                let (host_config, handle) = Refreshable::new(host_config);
394                                let client = match Self::raw_client(
395                                    state_builder.clone(),
396                                    host_config,
397                                    Some(i),
398                                ) {
399                                    Ok(state) => state,
400                                    Err(e) => {
401                                        errors.push(e);
402                                        continue;
403                                    }
404                                };
405                                ClientHandle { client, handle }
406                            }
407                        };
408
409                        clients.insert(
410                            host.clone(),
411                            make_client(
412                                client_handle.client.clone(),
413                                &state_builder.cache_manager.uncached().conjure_runtime,
414                            ),
415                        );
416                        new_client_handles.insert(host, client_handle);
417                    }
418
419                    client_handles = new_client_handles;
420                    match errors.pop() {
421                        Some(e) => {
422                            for e2 in errors {
423                                warn!("error reloading per-host clients", error: e2);
424                            }
425                            Err(e)
426                        }
427                        None => Ok(PerHostClients { clients }),
428                    }
429                }
430            })
431    }
432}
433
434#[cfg(not(target_arch = "wasm32"))]
435impl ClientFactory {
436    /// Returns the `Handle` to the tokio `Runtime` to be used by blocking clients.
437    ///
438    /// This has no effect on async clients.
439    ///
440    /// Defaults to a `conjure-runtime` internal `Runtime`.
441    #[inline]
442    pub fn blocking_handle(mut self, blocking_handle: Handle) -> Self {
443        self.0.cache_manager.uncached_mut().blocking_handle = Some(blocking_handle);
444        self
445    }
446
447    /// Returns the configured blocking handle.
448    #[inline]
449    pub fn get_blocking_handle(&self) -> Option<&Handle> {
450        self.0.cache_manager.uncached().blocking_handle.as_ref()
451    }
452
453    /// Creates a new blocking client for the specified service.
454    ///
455    /// The client's configuration will automatically refresh to track changes in the factory's [`ServicesConfig`].
456    ///
457    /// If no configuration is present for the specified service in the [`ServicesConfig`], the client will
458    /// immediately return an error for all requests.
459    ///
460    /// The method can return any type implementing the `conjure-http` [`Service`] trait. This notably includes all
461    /// Conjure-generated client types as well as the `conjure-runtime` [`blocking::Client`] itself.
462    pub fn blocking_client<T>(&self, service: &str) -> Result<T, Error>
463    where
464        T: Service<blocking::Client>,
465    {
466        self.blocking_client_inner(service)
467            .map(|c| T::new(c, &self.0.cache_manager.uncached().conjure_runtime))
468    }
469
470    fn blocking_client_inner(&self, service: &str) -> Result<blocking::Client, Error> {
471        self.client_inner(service).map(|client| blocking::Client {
472            client,
473            handle: self.0.cache_manager.uncached().blocking_handle.clone(),
474        })
475    }
476
477    /// Creates a refreshable collection of blocking clients, each corresponding to a separate
478    /// replica of the service.
479    ///
480    /// # Note
481    ///
482    /// The client type `T` is assumed to be stateless - each instance will be recreated on every
483    /// refresh.
484    pub fn blocking_per_host_clients<T>(
485        &self,
486        service: &str,
487    ) -> Result<Refreshable<PerHostClients<T>, Error>, Error>
488    where
489        T: Service<blocking::Client> + 'static + Sync + Send,
490    {
491        self.per_host_clients_inner(service, {
492            let handle = self.0.cache_manager.uncached().blocking_handle.clone();
493            move |client, runtime| {
494                T::new(
495                    blocking::Client {
496                        client,
497                        handle: handle.clone(),
498                    },
499                    runtime,
500                )
501            }
502        })
503    }
504}
505
506struct ClientHandle {
507    client: Client,
508    handle: RefreshHandle<ServiceConfig, Error>,
509}
510
511struct StateBuilder {
512    service: String,
513    user_agent: UserAgent,
514    client_qos: ClientQos,
515    server_qos: ServerQos,
516    service_error: ServiceError,
517    idempotency: Idempotency,
518    node_selection_strategy: NodeSelectionStrategy,
519    cache_manager: CacheManager,
520}
521
522impl StateBuilder {
523    fn build(
524        &self,
525        config: &ServiceConfig,
526        override_host_index: Option<usize>,
527    ) -> Result<Arc<Cached<CachedConfig, ClientState>>, Error> {
528        let mut builder = Client::builder()
529            .service(&self.service)
530            .user_agent(self.user_agent.clone())
531            .from_config(config)
532            .client_qos(self.client_qos)
533            .server_qos(self.server_qos)
534            .service_error(self.service_error)
535            .idempotency(self.idempotency)
536            .node_selection_strategy(self.node_selection_strategy)
537            .conjure_runtime(self.cache_manager.uncached().conjure_runtime.clone());
538
539        if let Some(metrics) = self.cache_manager.uncached().metrics.clone() {
540            builder = builder.metrics(metrics);
541        }
542
543        if let Some(host_metrics) = self.cache_manager.uncached().host_metrics.clone() {
544            builder = builder.host_metrics(host_metrics);
545        }
546
547        if let Some(override_host_index) = override_host_index {
548            builder = builder.override_host_index(override_host_index);
549        }
550
551        self.cache_manager
552            .cache
553            .get(&builder, Builder::cached_config, ClientState::new)
554    }
555}