conjure_runtime/
lib.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//! An HTTP client for use with [Conjure] servers.
15//!
16//! This crate provides both asynchronous and blocking clients. The clients can either be used in Conjure-generated
17//! wrapper types or standalone for use with non-Conjure servers.
18//!
19//! # Configuration
20//!
21//! While a `conjure_runtime` client can be built up programmatically, the more common approach is for the configuration
22//! to be deserialized from a service's runtime-reloadable configuration file. The
23//! [`ServicesConfig`](config::ServicesConfig) supports configuration for multiple downstream services, as well as
24//! allowing for both global and per-service configuration overrides:
25//!
26//! ```yaml
27//! services:
28//!   auth-service:
29//!     uris:
30//!       - https://auth.my-network.com/auth-service
31//!   cache-service:
32//!     uris:
33//!       - https://cache-1.my-network.com/cache-service
34//!       - https://cache-2.my-network.com/cache-service
35//!     request-timeout: 10s
36//! # options set at this level will apply as defaults to all configured services
37//! security:
38//!   ca-file: var/security/ca.pem
39//! ```
40//!
41//! Using the [`refreshable`] crate, a live-updating config file can be used with the [`ClientFactory`] type to create
42//! live-updating clients for the configured services.
43//!
44//! # Usage
45//!
46//! First construct a raw [`Client`]:
47//!
48//! ```
49//! use conjure_runtime::{UserAgent, Agent, Client};
50//! use conjure_runtime::config::SecurityConfig;
51//! use std::path::PathBuf;
52//!
53//! # fn foo() -> Result<(), conjure_error::Error> {
54//! let client = Client::builder()
55//!     .service("test-service")
56//!     .user_agent(UserAgent::new(Agent::new("my-user-agent", "1.0.0")))
57//!     .uri("https://url-to-server:1234/test-service".parse().unwrap())
58//!     .security(
59//!         SecurityConfig::builder()
60//!             .ca_file(Some(PathBuf::from("path/to/ca_file.pem")))
61//!             .build(),
62//!     )
63//!     .build()?;
64//! # Ok(()) }
65//! ```
66//!
67//! The client can then be used with Conjure-generated service interfaces:
68//!
69//! ```
70//! use conjure_codegen::example_types::clients::another::{AsyncTestService, AsyncTestServiceClient};
71//! use conjure_http::client::AsyncService;
72//! use conjure_object::BearerToken;
73//!
74//! # async fn foo(client: conjure_runtime::Client, runtime: &std::sync::Arc<conjure_http::client::ConjureRuntime>) -> Result<(), conjure_error::Error> {
75//! let client = AsyncTestServiceClient::new(client, runtime);
76//!
77//! let auth = BearerToken::new("my_auth_token").unwrap();
78//! let file_systems = client.get_file_systems(&auth).await?;
79//! # Ok(()) }
80//! ```
81//!
82//! The [`blocking::Client`]'s API is identical, with the exception that you don't `.await` on methods:
83//!
84//! ```
85//! use conjure_codegen::example_types::clients::another::{TestService, TestServiceClient};
86//! use conjure_http::client::Service;
87//! use conjure_object::BearerToken;
88//!
89//! # fn foo(client: conjure_runtime::blocking::Client, runtime: &std::sync::Arc<conjure_http::client::ConjureRuntime>) -> Result<(), conjure_error::Error> {
90//! let client = TestServiceClient::new(client, runtime);
91//!
92//! let auth = BearerToken::new("my_auth_token").unwrap();
93//! let file_systems = client.get_file_systems(&auth)?;
94//! # Ok(()) }
95//! ```
96//!
97//! The client can be used to communicate with non-Conjure APIs by directly using the low level
98//! [`Client`](conjure_http::client::Client) and [`AsyncClient`](conjure_http::client::AsyncClient) traits.
99//!
100//! # Behavior
101//!
102//! `conjure_runtime` wraps the [`hyper`] HTTP library with opinionated behavior designed to more effectively
103//! communicate between services in a distributed system. It is broadly designed to align with the [`dialogue`] Java
104//! library, though it does differ in various ways.
105//!
106//! ## WASM
107//!
108//! The client supports WASM targets running in a JavaScript environment using the `fetch` API as the underlying HTTP
109//! client instead of `hyper`. The `js` Cargo feature must be enabled to opt-in. Many low-level connection configuration
110//! options (HTTP proxies, socket IO timeouts, etc) are not supported. Since JavaScript APIs are not thread-safe, the
111//! client only implements the [`LocalAsyncClient`] trait. The blocking client implementation is not available.
112//!
113//! ## Error Propagation
114//!
115//! Servers should use the standard Conjure error format to propagate application-specific errors to callers. Non-QoS
116//! (see below) errors received from the server are treated as fatal. By default, `conjure_runtime` will return a
117//! [`conjure_error::Error`] that will generate a generic 500 Internal Server Error response. Its cause will be a
118//! [`RemoteError`] object that contains the serialized Conjure error information sent by the server. The
119//! [`Builder::service_error()`] and [`ClientFactory::service_error()`] methods can be used to change that behavior to
120//! instead transparently propagate the error received from the server. Rather than producing a generic 500 response,
121//! the returned [`conjure_error::Error`] will produce the same response the client received from the server.
122//!
123//! ## Call Tracing
124//!
125//! The client propagates trace information via the [`zipkin`] crate using the traditional `X-B3-*` HTTP headers. It
126//! also creates local spans covering various stages of request processing:
127//!
128//! * `conjure-runtime: request`
129//!     * `conjure-runtime: attempt`
130//!         * `conjure-runtime: acquire-permit` - If client QoS is enabled and the node selection strategy is not
131//!           [`Balanced`], this span covers the time spent acquiring a concurrency limiter permit.
132//!         * `conjure-runtime: balanced-node-selection` - If the node selection strategy is [`Balanced`], this span
133//!           covers the time spent selecting a node and (if client QoS is enabled) acquiring a concurrency limiter
134//!           permit.
135//!         * `conjure-runtime: wait-for-headers` - This span is sent to the server, and lasts until the server sends
136//!           the headers of the response.
137//!         * `conjure-runtime: wait-for-body` - This span is tracked along with the response body, and lasts until the
138//!           [`ResponseBody`] object is dropped. It is "detached" from the zipkin tracer so new spans created outside
139//!           of `conjure-runtime` will not be parented to it, and can outlive the parent `conjure-runtime` spans. It
140//!           will not be created if an IO error occurs before headers are received.
141//!     * `conjure-runtime: backoff-with-jitter` - If the request is retried, this span tracks the time spent waiting
142//!       between attempts.
143//!     * `conjure-runtime: attempt`
144//!         * ...
145//!
146//! ## Quality of Service: Retry, Failover, Throttling, and Backpressure
147//!
148//! The client treats certain HTTP errors specially. Servers can advertise an overloaded state via the 429 Too Many
149//! Requests or 503 Service Unavailable status codes. Unlike other 4xx and 5xx status codes, these responses do *not*
150//! cause the request to fail. Instead, `conjure_runtime` will throttle itself and retry the request. Requests are
151//! retried a fixed number of times, with an exponentially growing backoff in between attempts. If a 429 response
152//! contains a `Retry-After` header, its backoff will be used rather than the default. IO errors also trigger a retry.
153//!
154//! A 503 response or IO error will also cause that host to be temporarily put on "cooldown" so it will not be used by
155//! other requests unless there is no other option.
156//!
157//! Only some requests can be retried. By default, `conjure_runtime` will only retry requests with HTTP methods
158//! identified as idempotent - `GET`, `PUT`, `DELETE`, `HEAD`, `TRACE`, and `OPTIONS`. Non-idempotent requests cannot be
159//! safely retried to avoid the risk of unexpected behavior if the request ends up being applied twice. The
160//! [`Builder::idempotency()`] and [`ClientFactory::idempotency()`] methods can be used to override this behavior and
161//! have the client assume all or no requests are idempotent. In addition, requests with streaming request bodies can
162//! only be retried if the body had either not started to be written when the error occurred or if it was successfully
163//! reset for another attempt.
164//!
165//! ## Metrics
166//!
167//! Clients record metrics to both a standard [`MetricRegistry`] and a `conjure_runtime`-specific
168//! [`HostMetricsRegistry`].
169//!
170//! ### Standard Metrics
171//!
172//! * `client.response (channel-name: <channel_name>, service-name: <service_name>, endpoint: <endpoint>, status:
173//!   <status>)` - A `Timer` recording request durations per endpoint. Note that the requests timed by this metric
174//!   are the user-percieved request, including any backoffs/retries/etc. It only records the time until response
175//!   headers are received, not until the entire response body is read. The `status` tag will be `success` if the
176//!   response status was 2xx and will be `failure` otherwise (QoS failure, internal server error, IO error, etc).
177//! * `tls.handshake (context: <service_name>, protocol: <protocol_version>, cipher: <cipher_name>)` - A `Meter`
178//!   tracking the rate of TLS handshakes, tagged by the service, TLS protocol version (e.g. `TLSv1.3`), and cipher
179//!   name (e.g. `TLS_CHACHA20_POLY1305_SHA256`).
180//! * `conjure-runtime.concurrencylimiter.max (service: <service_name>, hostIndex: <host_index>)` - A `Gauge` reporting
181//!   the maximum number of concurrent requests which are currently permitted to be made to a specific host.
182//! * `conjure-runtime.concurrencylimiter.in-flight (service: <service_name>, hostIndex: <host_index>)` - A `Gauge`
183//!   reporting the current number of requests being made to a specific host.
184//!
185//! ### Host Metrics
186//!
187//! The [`HostMetricsRegistry`] contains metrics for every host of every service being actively used by a
188//! `conjure_runtime` client.
189//!
190//! [Conjure]: https://github.com/palantir/conjure
191//! [`hyper`]: https://docs.rs/hyper
192//! [`RemoteError`]: errors::RemoteError
193//! [`dialogue`]: https://github.com/palantir/dialogue
194//! [`zipkin`]: https://docs.rs/zipkin
195//! [`Balanced`]: NodeSelectionStrategy::Balanced
196//! [`MetricRegistry`]: witchcraft_metrics::MetricRegistry
197//! [`LocalAsyncClient`]: conjure_http::client::LocalAsyncClient
198#![warn(missing_docs, clippy::all)]
199
200pub use crate::body::*;
201#[doc(inline)]
202pub use crate::builder::{
203    Builder, ClientQos, Idempotency, NodeSelectionStrategy, ServerQos, ServiceError,
204};
205pub use crate::client::*;
206#[doc(inline)]
207pub use crate::client_factory::ClientFactory;
208pub use crate::host_metrics::*;
209#[doc(inline)]
210pub use crate::per_host_clients::{Host, PerHostClients};
211pub use crate::user_agent::*;
212
213#[cfg(not(target_arch = "wasm32"))]
214pub mod blocking;
215mod body;
216pub mod builder;
217mod client;
218pub mod client_factory;
219pub mod errors;
220mod host_metrics;
221pub mod per_host_clients;
222mod rt;
223mod service;
224#[cfg(test)]
225mod test;
226mod user_agent;
227mod util;
228mod weak_cache;
229
230/// Client configuration.
231///
232/// This is just a reexport of the `conjure_runtime_config` crate for convenience.
233pub mod config {
234    #[doc(inline)]
235    pub use conjure_runtime_config::*;
236}