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::another::TestServiceAsyncClient;
71//! use conjure_http::client::AsyncService;
72//! use conjure_object::BearerToken;
73//!
74//! # async fn foo(client: conjure_runtime::Client) -> Result<(), conjure_error::Error> {
75//! let client = TestServiceAsyncClient::new(client);
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::another::TestServiceClient;
86//! use conjure_http::client::Service;
87//! use conjure_object::BearerToken;
88//!
89//! # fn foo(client: conjure_runtime::blocking::Client) -> Result<(), conjure_error::Error> {
90//! let client = TestServiceClient::new(client);
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//! ## Error Propagation
107//!
108//! Servers should use the standard Conjure error format to propagate application-specific errors to callers. Non-QoS
109//! (see below) errors received from the server are treated as fatal. By default, `conjure_runtime` will return a
110//! [`conjure_error::Error`] that will generate a generic 500 Internal Server Error response. Its cause will be a
111//! [`RemoteError`] object that contains the serialized Conjure error information sent by the server. The
112//! [`Builder::service_error()`] and [`ClientFactory::service_error()`] methods can be used to change that behavior to
113//! instead transparently propagate the error received from the server. Rather than producing a generic 500 response,
114//! the returned [`conjure_error::Error`] will produce the same response the client received from the server.
115//!
116//! ## Call Tracing
117//!
118//! The client propagates trace information via the [`zipkin`] crate using the traditional `X-B3-*` HTTP headers. It
119//! also creates local spans covering various stages of request processing:
120//!
121//! * `conjure-runtime: request`
122//! * `conjure-runtime: attempt`
123//! * `conjure-runtime: acquire-permit` - If client QoS is enabled and the node selection strategy is not
124//! [`Balanced`], this span covers the time spent acquiring a concurrency limiter permit.
125//! * `conjure-runtime: balanced-node-selection` - If the node selection strategy is [`Balanced`], this span
126//! covers the time spent selecting a node and (if client QoS is enabled) acquiring a concurrency limiter
127//! permit.
128//! * `conjure-runtime: wait-for-headers` - This span is sent to the server, and lasts until the server sends
129//! the headers of the response.
130//! * `conjure-runtime: wait-for-body` - This span is tracked along with the response body, and lasts until the
131//! [`ResponseBody`] object is dropped. It is "detached" from the zipkin tracer so new spans created outside
132//! of `conjure-runtime` will not be parented to it, and can outlive the parent `conjure-runtime` spans. It
133//! will not be created if an IO error occurs before headers are received.
134//! * `conjure-runtime: backoff-with-jitter` - If the request is retried, this span tracks the time spent waiting
135//! between attempts.
136//! * `conjure-runtime: attempt`
137//! * ...
138//!
139//! ## Quality of Service: Retry, Failover, Throttling, and Backpressure
140//!
141//! The client treats certain HTTP errors specially. Servers can advertise an overloaded state via the 429 Too Many
142//! Requests or 503 Service Unavailable status codes. Unlike other 4xx and 5xx status codes, these responses do *not*
143//! cause the request to fail. Instead, `conjure_runtime` will throttle itself and retry the request. Requests are
144//! retried a fixed number of times, with an exponentially growing backoff in between attempts. If a 429 response
145//! contains a `Retry-After` header, its backoff will be used rather than the default. IO errors also trigger a retry.
146//!
147//! A 503 response or IO error will also cause that host to be temporarily put on "cooldown" so it will not be used by
148//! other requests unless there is no other option.
149//!
150//! Only some requests can be retried. By default, `conjure_runtime` will only retry requests with HTTP methods
151//! identified as idempotent - `GET`, `PUT`, `DELETE`, `HEAD`, `TRACE`, and `OPTIONS`. Non-idempotent requests cannot be
152//! safely retried to avoid the risk of unexpected behavior if the request ends up being applied twice. The
153//! [`Builder::idempotency()`] and [`ClientFactory::idempotency()`] methods can be used to override this behavior and
154//! have the client assume all or no requests are idempotent. In addition, requests with streaming request bodies can
155//! only be retried if the body had either not started to be written when the error occurred or if it was successfully
156//! reset for another attempt.
157//!
158//! ## Metrics
159//!
160//! Clients record metrics to both a standard [`MetricRegistry`] and a `conjure_runtime`-specific
161//! [`HostMetricsRegistry`].
162//!
163//! ### Standard Metrics
164//!
165//! * `client.response (channel-name: <channel_name>, service-name: <service_name>, endpoint: <endpoint>, status:
166//! <status>)` - A `Timer` recording request durations per endpoint. Note that the requests timed by this metric
167//! are the user-percieved request, including any backoffs/retries/etc. It only records the time until response
168//! headers are received, not until the entire response body is read. The `status` tag will be `success` if the
169//! response status was 2xx and will be `failure` otherwise (QoS failure, internal server error, IO error, etc).
170//! * `tls.handshake (context: <service_name>, protocol: <protocol_version>, cipher: <cipher_name>)` - A `Meter`
171//! tracking the rate of TLS handshakes, tagged by the service, TLS protocol version (e.g. `TLSv1.3`), and cipher
172//! name (e.g. `TLS_CHACHA20_POLY1305_SHA256`).
173//! * `conjure-runtime.concurrencylimiter.max (service: <service_name>, hostIndex: <host_index>)` - A `Gauge` reporting
174//! the maximum number of concurrent requests which are currently permitted to be made to a specific host.
175//! * `conjure-runtime.concurrencylimiter.in-flight (service: <service_name>, hostIndex: <host_index>)` - A `Gauge`
176//! reporting the current number of requests being made to a specific host.
177//!
178//! ### Host Metrics
179//!
180//! The [`HostMetricsRegistry`] contains metrics for every host of every service being actively used by a
181//! `conjure_runtime` client.
182//!
183//! [Conjure]: https://github.com/palantir/conjure
184//! [`hyper`]: https://docs.rs/hyper
185//! [`RemoteError`]: errors::RemoteError
186//! [`dialogue`]: https://github.com/palantir/dialogue
187//! [`zipkin`]: https://docs.rs/zipkin
188//! [`Balanced`]: NodeSelectionStrategy::Balanced
189//! [`MetricRegistry`]: witchcraft_metrics::MetricRegistry
190#![warn(missing_docs, clippy::all)]
191
192pub use crate::body::*;
193#[doc(inline)]
194pub use crate::builder::{
195 Builder, ClientQos, Idempotency, NodeSelectionStrategy, ServerQos, ServiceError,
196};
197pub use crate::client::*;
198#[doc(inline)]
199pub use crate::client_factory::ClientFactory;
200pub use crate::host_metrics::*;
201#[doc(inline)]
202pub use crate::per_host_clients::{Host, PerHostClients};
203pub use crate::user_agent::*;
204
205pub mod blocking;
206mod body;
207pub mod builder;
208mod client;
209pub mod client_factory;
210pub mod errors;
211mod host_metrics;
212pub mod per_host_clients;
213pub mod raw;
214mod rng;
215mod service;
216#[cfg(test)]
217mod test;
218mod user_agent;
219mod util;
220mod weak_cache;
221
222/// Client configuration.
223///
224/// This is just a reexport of the `conjure_runtime_config` crate for convenience.
225pub mod config {
226 #[doc(inline)]
227 pub use conjure_runtime_config::*;
228}