1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Copyright 2020 Palantir Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! An HTTP client for use with [Conjure] servers.
//!
//! This crate provides both asynchronous and blocking clients. The clients can either be used in Conjure-generated
//! wrapper types or standalone for use with non-Conjure servers.
//!
//! # Configuration
//!
//! While a `conjure_runtime` client's configuration can be built up programmatically, the more common approach is for
//! the configuration to be deserialized from a service's runtime-reloadable configuration file. The `ServicesConfig`
//! supports configuration for multiple downstream services, as well as allowing for both global and per-service
//! configuration overrides:
//!
//! ```yaml
//! services:
//!   auth-service:
//!     uris:
//!       - https://auth.my-network.com/auth-service
//!   cache-service:
//!     uris:
//!       - https://cache-1.my-network.com/cache-service
//!       - https://cache-2.my-network.com/cache-service
//!     request-timeout: 10s
//! # options set at this level will apply as defaults to all configured services
//! security:
//!   ca-file: var/security/ca.pem
//! ```
//!
//! Once a client is constructed, its configuration can be dynamically reloaded by calling `RefreshHandle::refresh`
//! on a handle obtained via `Client::refresh_handle`. Note that the refresh process will throw away the client's
//! TLS session cache and HTTP connection pool, so it should not be called needlessly often.
//!
//! # Usage
//!
//! First construct a raw `conjure_runtime::Client`:
//!
//! ```
//! use conjure_runtime::{UserAgent, Agent, HostMetricsRegistry, Client};
//! use conjure_runtime::config::{ServiceConfig, SecurityConfig};
//! use std::path::PathBuf;
//! use std::sync::Arc;
//! use witchcraft_metrics::MetricRegistry;
//!
//! # fn foo() -> Result<(), conjure_error::Error> {
//! let user_agent = UserAgent::new(Agent::new("my-user-agent", "1.0.0"));
//! let host_metrics = Arc::new(HostMetricsRegistry::new());
//! let metrics = Arc::new(MetricRegistry::new());
//! let config = ServiceConfig::builder()
//!     .uris(vec!["https://url-to-server:1234/test-service".parse().unwrap()])
//!     .security(
//!         SecurityConfig::builder()
//!             .ca_file(Some(PathBuf::from("path/to/ca_file.pem")))
//!             .build(),
//!     )
//!     .build();
//!
//! let client = Client::new(
//!     "test-service",
//!     user_agent,
//!     &host_metrics,
//!     &metrics,
//!     &config,
//! )?;
//! # Ok(()) }
//! ```
//!
//! The client can then be used with Conjure-generated service interfaces:
//!
//! ```
//! use conjure_codegen::example_types::another::TestServiceAsyncClient;
//! use conjure_object::BearerToken;
//!
//! # async fn foo(client: conjure_runtime::Client) -> Result<(), conjure_error::Error> {
//! let client = TestServiceAsyncClient::new(client);
//!
//! let auth = BearerToken::new("my_auth_token").unwrap();
//! let file_systems = client.get_file_systems(&auth).await?;
//! # Ok(()) }
//! ```
//!
//! Or manually if a service does not provide a Conjure API:
//!
//! ```
//! # async fn foo(client: conjure_runtime::Client) -> Result<(), conjure_error::Error> {
//! let response = client.delete("/widgets/{widgetId}")
//!     .param("widgetId", 12345)
//!     .send()
//!     .await?;
//! # Ok(()) }
//! ```
//!
//! The `blocking::Client`'s API is identical, with the exception that you don't `.await` on methods:
//!
//! ```
//! use conjure_codegen::example_types::another::TestServiceClient;
//! use conjure_object::BearerToken;
//!
//! # fn foo(client: conjure_runtime::blocking::Client) -> Result<(), conjure_error::Error> {
//! let client = TestServiceClient::new(client);
//!
//! let auth = BearerToken::new("my_auth_token").unwrap();
//! let file_systems = client.get_file_systems(&auth)?;
//! # Ok(()) }
//! ```
//!
//! ```
//! # fn foo(client: conjure_runtime::blocking::Client) -> Result<(), conjure_error::Error> {
//! let response = client.delete("/widgets/{widgetId}")
//!     .param("widgetId", 12345)
//!     .send()?;
//! # Ok(()) }
//! ```
//!
//! # Behavior
//!
//! `conjure_runtime` wraps the `hyper` HTTP library with opinionated behavior designed to more effectively communicate
//! between services in a distributed system. It is broadly designed to align with the [`conjure-java-runtime`] Java
//! library, though it does differ in various ways.
//!
//! ## Error Propagation
//!
//! Servers should use the standard Conjure error format to propagate application-specific errors to callers. Non-QoS
//! (see below) errors received from the server are treated as fatal. By default, `conjure_runtime` will return a
//! `conjure_error::Error` that will generate a generic 500 Internal Server Error response. Its cause will be a
//! `RemoteError` object that contains the serialized Conjure error information sent by the server. The
//! `Client::set_propagate_service_errors` method can be used to change that behavior to instead transparently propagate
//! the error received from the server. Rather than producing a generic 500 response, the returned
//! `conjure_error::Error` will produce the same response the client received from the server.
//!
//! ## Call Tracing
//!
//! The client propagates trace information via the [`zipkin`] crate using the traditional `X-B3-*` HTTP headers. It
//! also creates local spans covering various stages of request processing:
//!
//! * `conjure-runtime: get /widget-service/{widgetId}` - The name of this span is built from the request's method and
//!     path pattern.
//!     * `conjure-runtime: attempt 1`
//!         * `conjure-runtime: wait-for-headers` - This span is sent to the server, and lasts until the server sends
//!             the headers of the response.
//!         * `conjure-runtime: wait-for-body` - This span is tracked along with the response body, and lasts until the
//!             `ResponseBody` object is dropped. It is "detached" from the zipkin tracer so new spans created outside
//!             of `conjure-runtime` will not be parented to it, and can outlive the parent `conjure-runtime` spans. It
//!             will not be created if an IO error occurs before headers are received.
//!     * `conjure-runtime: backoff-with-jitter` - If the request encounters an IO error before receiving headers, this
//!         span covers the time spent waiting before the request is retried.
//!     * `conjure-runtime: attempt 2`
//!         * ...
//!
//! ## Quality of Service: Retry, Failover, Throttling, and Backpressure
//!
//! The client treats certain HTTP errors specially. Servers can advertise an overloaded state via the 429 Too Many
//! Requests or 503 Service Unavailable status codes. Unlike other 4xx and 5xx status codes, these responses do *not*
//! cause the request to fail. Instead, `conjure_runtime` will throttle itself and retry the request. Requests are
//! retried a fixed number of times, with an exponentially growing backoff in between attempts. If a 429 response
//! contains a `Retry-After` header, its backoff will be used rather than the default. IO errors also trigger a retry.
//!
//! A 503 response or IO error will also cause that host to be temporarily "blacklisted" so it will not be used by other
//! requests unless there is no other option.
//!
//! Only some requests can be retried. By default, `conjure_runtime` will only retry requests with HTTP methods
//! identified as idempotent - `GET`, `PUT`, `DELETE`, `HEAD`, `TRACE`, and `OPTIONS`. Non-idempotent requests cannot be
//! safely retried to avoid the risk of unexpected behavior if the request ends up being applied twice. The
//! `Client::set_assume_idempotent` method can be used to override this behavior and have the client assume all requests
//! are idempotent. In addition, requests with streaming request bodies can only be retried if the body had either not
//! started to be written when the error occurred or if it was successfully reset for another attempt.
//!
//! ## Metrics
//!
//! Clients record metrics to both a standard `MetricsRegistry` and a `conjure_runtime`-specific `HostMetricsRegistry`.
//!
//! ### Standard Metrics
//!
//! * `client.request (service: <service_name>)` - A `Timer` recording request durations, tagged by service. Note that
//!     the requests timed by this metric are the individual raw HTTP request attempts, *not* `conjure_runtime`-level
//!     requests with retries/backoffs/etc. It only records the time until response headers are received, not until the
//!     entire response body is read.
//! * `client.request.error (service: <service_name>, reason: IOException)` - A `Meter` tracking the rate of IO errors,
//!     tagged by service. Like the `client.request` metric, this tracks the raw HTTP requests. The `reason` tag has a
//!     value of `IOException` to align with [`conjure-java-runtime`]'s metric.
//!
//! ### Host Metrics
//!
//! The `HostMetricsRegistry` contains metrics for every host of every service being actively used by a
//! `conjure_runtime` client.
//!
//! [Conjure]: https://github.com/palantir/conjure
//! [`conjure-java-runtime`]: https://github.com/palantir/conjure-java-runtime
//! [`zipkin`]: https://docs.rs/zipkin
#![doc(html_root_url = "https://docs.rs/conjure-runtime/0.1")]
#![warn(missing_docs, clippy::all)]

pub use crate::body::*;
pub use crate::client::*;
pub use crate::metrics::*;
pub use crate::payload::*;
pub use crate::request::*;
pub use crate::response::*;
pub use crate::user_agent::*;
use hyper::header::HeaderValue;
use once_cell::sync::Lazy;

pub mod blocking;
mod body;
mod client;
mod conjure;
pub mod errors;
mod metrics;
mod node_selector;
mod payload;
mod proxy;
mod request;
mod response;
mod send;
#[cfg(test)]
mod test;
mod user_agent;

/// Client configuration.
///
/// This is just a reexport of the `conjure_runtime_config` crate for convenience.
pub mod config {
    #[doc(inline)]
    pub use conjure_runtime_config::*;
}

static APPLICATION_JSON: Lazy<HeaderValue> =
    Lazy::new(|| HeaderValue::from_static("application/json"));
static APPLICATION_OCTET_STREAM: Lazy<HeaderValue> =
    Lazy::new(|| HeaderValue::from_static("application/octet-stream"));