Skip to main content

apollo_http_client/
config.rs

1use apollo_configuration::ErrorCollector;
2use apollo_configuration::Validate;
3use apollo_configuration::configuration;
4use apollo_configuration::types::{Duration, HeaderName, Url};
5use apollo_redaction::Redacted;
6use apollo_redaction::strategy::UrlPasswordRedactor;
7use std::num::NonZeroUsize;
8use std::time::Duration as StdDuration;
9
10/// A proxy URL with the password component redacted in `Debug` and `Display` output.
11pub type ProxyUrl = Redacted<Url, UrlPasswordRedactor>;
12
13/// HTTP protocol version to use for connections.
14#[non_exhaustive]
15#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize, schemars::JsonSchema)]
16#[serde(rename_all = "snake_case")]
17pub enum Protocol {
18    /// HTTP/1.1 only (default).
19    #[default]
20    Http1,
21    /// HTTP/2 only.
22    ///
23    /// Over HTTPS, ALPN is configured automatically — the server selects HTTP/2 during
24    /// the TLS handshake if it supports it. Over plain HTTP and `unix://`, prior-knowledge
25    /// HTTP/2 is used: the client speaks HTTP/2 directly without an Upgrade round trip.
26    /// Use this only when you know the server supports HTTP/2 on that transport.
27    Http2,
28    /// Negotiate HTTP/1.1 or HTTP/2 automatically.
29    ///
30    /// Over HTTPS, ALPN is used: the server selects HTTP/1.1 or HTTP/2 during the TLS
31    /// handshake. Over plain HTTP and `unix://`, there is no TLS handshake, so ALPN
32    /// cannot negotiate a protocol and the connection always falls back to HTTP/1.1 —
33    /// use `http2` explicitly for prior-knowledge HTTP/2 on those transports.
34    Alpn,
35}
36
37impl Validate for Protocol {}
38
39/// HTTP/2 connection settings.
40///
41/// These apply when `protocol` is `http2`, and to HTTP/2 connections
42/// negotiated under `protocol: alpn`.
43#[non_exhaustive]
44#[configuration]
45pub struct Http2Config {
46    /// How long an idle HTTP/2 connection is kept before being closed. The
47    /// connection is considered idle when no requests are in flight.
48    #[config(default = Duration::from(StdDuration::from_secs(300)))]
49    pub connection_idle_timeout: Duration,
50
51    /// Interval at which HTTP/2 PING frames are sent to detect dead
52    /// connections and prevent proxies from closing idle connections.
53    #[config(default = Duration::from(StdDuration::from_secs(30)))]
54    pub keep_alive_interval: Duration,
55
56    /// How long to wait for a PING acknowledgement before closing the
57    /// connection.
58    #[config(default = Duration::from(StdDuration::from_secs(20)))]
59    pub keep_alive_timeout: Duration,
60
61    /// Whether to send keep-alive pings when there are no active streams.
62    #[config(default = true)]
63    pub keep_alive_while_idle: bool,
64}
65
66/// TCP socket settings applied to every outbound connection.
67///
68/// These apply regardless of HTTP protocol version (HTTP/1.1, HTTP/2, or
69/// negotiated under `protocol: alpn`).
70#[non_exhaustive]
71#[configuration]
72pub struct TcpConfig {
73    /// Whether to disable Nagle's algorithm (`TCP_NODELAY`).
74    ///
75    /// When `true` (default), small payloads are sent immediately, reducing
76    /// tail latency on small request/response bodies. Set to `false` to enable
77    /// Nagle's algorithm, in which the kernel may briefly buffer small writes
78    /// to coalesce them into fewer segments.
79    #[config(default = true)]
80    pub nodelay: bool,
81
82    /// Idle period before the OS starts sending TCP keep-alive probes.
83    ///
84    /// Defaults to 60 s. Probes detect dead connections before a new request
85    /// is dispatched on them and prevent intermediate proxies (load balancers,
86    /// NATs) from silently dropping long-lived idle connections.
87    #[config(default = Duration::from(StdDuration::from_secs(60)))]
88    pub keepalive: Duration,
89}
90
91/// HTTP/1.1 connection pool settings.
92///
93/// These apply when `protocol` is `http1`, and to HTTP/1.1 connections
94/// negotiated under `protocol: alpn`. They have no effect when `protocol`
95/// is `http2` — HTTP/2 uses a single persistent connection per host.
96#[non_exhaustive]
97#[configuration]
98pub struct Http1Config {
99    /// Maximum idle connections kept open per host.
100    #[config(default = NonZeroUsize::new(10).unwrap())]
101    pub pool_max_idle_per_host: NonZeroUsize,
102
103    /// How long idle connections are kept alive before being closed.
104    #[config(default = Duration::from(StdDuration::from_secs(90)))]
105    pub pool_idle_timeout: Duration,
106}
107
108/// Metrics opt-in settings.
109///
110/// Controls which optional OTel HTTP client metrics are recorded and which opt-in attributes
111/// are included. All are disabled by default, as required by the OTel HTTP semantic conventions.
112#[non_exhaustive]
113#[configuration]
114pub struct MetricsConfig {
115    /// Enable recording of `http.client.request.body.size`.
116    ///
117    /// Opt-In per the OTel HTTP semantic conventions.
118    pub request_body_size: bool,
119
120    /// Enable recording of `http.client.response.body.size`.
121    ///
122    /// Opt-In per the OTel HTTP semantic conventions.
123    pub response_body_size: bool,
124
125    /// Include the `url.scheme` attribute on all HTTP client metrics.
126    ///
127    /// Opt-In per the OTel HTTP semantic conventions.
128    pub url_scheme: bool,
129}
130
131/// Spans opt-in settings.
132#[non_exhaustive]
133#[configuration]
134pub struct SpansConfig {
135    /// Capture `url.scheme` as a span attribute.
136    ///
137    /// Opt-In per the OTel HTTP semantic conventions.
138    pub url_scheme: bool,
139
140    /// Capture `http.request.body.size` as a span attribute (actual bytes sent).
141    ///
142    /// Opt-In per the OTel HTTP semantic conventions.
143    pub request_body_size: bool,
144
145    /// Capture `http.response.body.size` as a span attribute (actual bytes received).
146    ///
147    /// Opt-In per the OTel HTTP semantic conventions.
148    pub response_body_size: bool,
149
150    /// Capture `user_agent.original` from the outgoing `User-Agent` request header.
151    ///
152    /// Opt-In per the OTel HTTP semantic conventions.
153    pub user_agent: bool,
154
155    /// Capture `network.transport` as a span attribute. Always `"tcp"` for HTTP/1.1 and HTTP/2.
156    ///
157    /// Opt-In per the OTel HTTP semantic conventions.
158    pub network_transport: bool,
159
160    /// Static `network.local.address` span attribute: the outbound IP address of this service
161    /// (e.g. `"192.168.1.10"`). Opt-In per the OTel HTTP semantic conventions.
162    pub network_local_address: Option<String>,
163
164    /// Static `network.local.port` span attribute: the source port this service binds to, if fixed.
165    /// Opt-In per the OTel HTTP semantic conventions.
166    pub network_local_port: Option<u16>,
167
168    /// HTTP request header names to capture as span attributes (case-insensitive).
169    ///
170    /// Each header `x-foo` becomes `http.request.header.x-foo` with a string-array value.
171    /// Opt-In per the OTel HTTP semantic conventions.
172    #[config(skip_validate)]
173    pub request_headers: Vec<HeaderName>,
174
175    /// HTTP response header names to capture as span attributes (case-insensitive).
176    ///
177    /// Each header `x-foo` becomes `http.response.header.x-foo` with a string-array value.
178    /// Opt-In per the OTel HTTP semantic conventions.
179    #[config(skip_validate)]
180    pub response_headers: Vec<HeaderName>,
181}
182
183/// Telemetry settings.
184#[non_exhaustive]
185#[configuration]
186pub struct TelemetryConfig {
187    /// Metrics opt-in settings.
188    pub metrics: MetricsConfig,
189
190    /// Spans opt-in settings.
191    pub spans: SpansConfig,
192}
193
194/// Proxy server configuration.
195///
196/// Routes outbound `http://` and `https://` requests through a proxy server.
197/// `unix://` requests bypass the proxy and reach the socket directly.
198///
199/// ```yaml
200/// proxy:
201///   url: "http://user:${PROXY_PASSWORD}@proxy.corp.example.com:3128"
202/// ```
203#[non_exhaustive]
204#[configuration]
205pub struct ProxyConfig {
206    /// Proxy server URL.
207    ///
208    /// Supports Basic authentication via embedded credentials:
209    /// `http://user:password@proxy.corp.example.com:3128`
210    ///
211    /// Use env-var expansion to avoid hardcoding addresses or credentials:
212    /// `url: "${EGRESS_PROXY_URL}"`
213    #[config(required, validate = validate_proxy_url)]
214    pub url: ProxyUrl,
215}
216
217fn validate_proxy_url(url: &ProxyUrl, mut errors: ErrorCollector<'_>) {
218    let url = url.unredact();
219    if url.host_str().is_none_or(str::is_empty) {
220        errors.report_simple("proxy URL must include a host");
221    }
222    match url.scheme() {
223        "http" | "https" => {}
224        other => errors.report_simple(format!(
225            "unsupported proxy URL scheme `{other}`; must be http or https"
226        )),
227    }
228}
229
230/// TLS settings for outbound connections.
231#[non_exhaustive]
232#[configuration]
233pub struct TlsConfig {
234    /// PEM-encoded CA certificate bundle to trust. Multiple certificates may
235    /// be concatenated in a single PEM blob. When `use_native_certificate_store`
236    /// is also enabled (the default), these CAs are added to the OS native
237    /// store; set `use_native_certificate_store: false` to trust only the
238    /// supplied bundle.
239    ///
240    /// Typically loaded from disk with `${file:./certs/ca.pem}`.
241    pub certificate_authorities: Option<Redacted<String>>,
242
243    /// Whether the OS native certificate store is trusted. When `true` (the
244    /// default), any CAs supplied via `certificate_authorities` are layered
245    /// on top. Set to `false` to trust only the supplied bundle — useful in
246    /// air-gapped environments.
247    #[config(default = true)]
248    pub use_native_certificate_store: bool,
249
250    /// Client certificate and key to present during mutual TLS handshakes.
251    /// Omit to disable client authentication.
252    pub client_authentication: Option<ClientAuthentication>,
253
254    /// Skip TLS certificate verification. **Never use in production.** When
255    /// enabled, the client accepts any certificate regardless of issuer or
256    /// validity, making all outbound HTTPS connections vulnerable to
257    /// machine-in-the-middle interception.
258    pub danger_accept_invalid_certs: bool,
259}
260
261/// Client identity used for mutual TLS authentication.
262///
263/// The certificate is offered to servers during the TLS handshake; servers
264/// that do not require client authentication ignore it. Encrypted private
265/// keys are not supported.
266#[non_exhaustive]
267#[configuration]
268pub struct ClientAuthentication {
269    /// PEM-encoded client certificate followed by any intermediate
270    /// certificates needed to complete the chain to a CA the server trusts.
271    ///
272    /// Typically loaded from disk with `${file:./certs/client.pem}`.
273    #[config(required)]
274    pub certificate_chain: Redacted<String>,
275
276    /// PEM-encoded unencrypted private key matching the leaf certificate in
277    /// `certificate_chain`. PKCS#1, PKCS#8, and SEC1 formats are accepted.
278    ///
279    /// Typically loaded from disk with `${file:./certs/client.key}`.
280    #[config(required)]
281    pub key: Redacted<String>,
282}
283
284/// HTTP client configuration.
285///
286/// Controls the protocol, connection pool, timeouts, and opt-in OTel telemetry.
287#[non_exhaustive]
288#[configuration]
289pub struct HttpClientConfig {
290    /// Connection timeout covering both the TCP dial and TLS handshake.
291    #[config(default = Duration::from(StdDuration::from_secs(10)))]
292    pub connect_timeout: Duration,
293
294    /// HTTP/1.1 connection pool settings.
295    pub http1: Http1Config,
296
297    /// HTTP/2 connection settings.
298    pub http2: Http2Config,
299
300    /// HTTP protocol version to use.
301    pub protocol: Protocol,
302
303    /// TCP socket settings applied to every outbound connection.
304    pub tcp: TcpConfig,
305
306    /// Telemetry opt-in settings.
307    pub telemetry: TelemetryConfig,
308
309    /// TLS settings for outbound connections.
310    pub tls: TlsConfig,
311
312    /// Proxy server configuration. When set, outbound `http://` and `https://`
313    /// requests are routed through the specified proxy; `unix://` requests
314    /// always reach the socket directly.
315    ///
316    /// When absent (the default), TCP connections are made directly to the target.
317    pub proxy: Option<ProxyConfig>,
318}