apollo-http-client 0.2.0

HTTP client for Apollo platform
Documentation

HTTP client for Apollo platform services.

[HttpClient] is a Tower Service: HTTP/1.1 and HTTP/2 over TCP and Unix domain sockets, with ALPN version negotiation on TCP. Configure via [HttpClientConfig].

Metrics

Every request emits the following OpenTelemetry metrics:

Metric Description Key attributes
http.client.active_requests In-flight request count method, server.address, server.port
http.client.request.duration Request latency method, server.address, server.port, protocol.version, status or error.type
http.client.request.body.size Bytes sent (opt-in) method, server.address, server.port, protocol.version, status
http.client.response.body.size Bytes received (opt-in) method, server.address, server.port, status
http.client.open_connections Open connection gauge server.address, server.port, protocol.version, http.connection.state
http.client.connection.duration Connection lifetime server.address, server.port, protocol.version

For unix:// requests server.address carries the socket path and server.port is omitted from every metric.

Connection-state metrics (http.client.open_connections, http.client.connection.duration) also include network.peer.address and network.peer.port when a proxy is configured — set to the proxy host and port. They are omitted when no proxy is in the path.

All metrics also include url.scheme when telemetry.metrics.url_scheme is enabled.

Span attributes

Follows the OTel HTTP client span semantic conventions.

Always recorded:

  • http.request.method_OTHER for non-standard methods, with the original verb preserved in http.request.method_original
  • url.full
  • server.address
  • server.port — absent for unix://
  • http.response.status_code
  • network.protocol.version
  • error.type — 4xx/5xx/transport

Opt-in (disabled by default, configure under telemetry.spans):

Attribute Setting
url.scheme url_scheme
http.request.body.size request_body_size
http.response.body.size response_body_size
user_agent.original user_agent
network.transport network_transport (always "tcp")
network.local.address network_local_address (static value, set at startup)
network.local.port network_local_port (static value, set at startup)
http.request.header.<name> request_headers
http.response.header.<name> response_headers

Not supported: url.template, http.request.size, http.response.size, network.peer.address, network.peer.port, user_agent.synthetic.type.

TLS

Configure trust anchors and client identity under tls. The example below trusts a private CA bundle in addition to the OS native store and presents a client certificate for mutual TLS:

tls:
  # PEM bundle of CA certificates to trust. Multiple certificates may be
  # concatenated. Loaded from disk via apollo-configuration file expansion.
  certificate_authorities: ${file:./certs/internal-ca.pem}

  # Trust the OS native certificate store in addition to the supplied CAs.
  # Set false to trust only the supplied CAs (air-gapped environments).
  use_native_certificate_store: true

  client_authentication:
    # PEM chain: leaf certificate followed by intermediates.
    certificate_chain: ${file:./certs/client.pem}
    # PEM-encoded unencrypted private key (PKCS#1, PKCS#8, or SEC1).
    # Encrypted keys are not supported.
    key: ${file:./certs/client.key}

Invalid PEM, empty bundles, encrypted keys, and configurations with no trust anchors all fail at [HttpClient::new] with a dedicated [HttpClientError] variant naming the offending field.

To supply a pre-built [rustls::ClientConfig] instead, for example, a FIPS-compliant config or one backed by a custom certificate store, use [HttpClient::builder]:

use std::sync::Arc;
use apollo_http_client::{HttpClient, HttpClientConfig};

# fn main() -> Result<(), apollo_http_client::HttpClientError> {
let tls: Arc<rustls::ClientConfig> = todo!("build your ClientConfig");
let client = HttpClient::builder(HttpClientConfig::default())
    .with_tls_config(tls)
    .build()?;
# Ok(())
# }

Unix domain sockets

On Unix targets, unix:// URIs dispatch over Unix domain sockets. Build the request URI with [unix_uri]:

# #[cfg(not(unix))]
# fn main() {}
# #[cfg(unix)]
# #[tokio::main]
# async fn main() {
use apollo_http_client::{HttpClient, HttpClientConfig, unix_uri};
use http_body_util::Empty;
use bytes::Bytes;
use tower::ServiceExt;

let client = HttpClient::new(&HttpClientConfig::default()).expect("valid config");

let uri = unix_uri("/var/run/app.sock")
    .expect("valid path")
    .path_and_query("/health")
    .build()
    .expect("uri builds");

let req = http::Request::builder().uri(uri).body(Empty::<Bytes>::new()).unwrap();

let _resp = client.oneshot(req).await.expect("request ok");
# }

HttpClientConfig::protocol selects HTTP/1.1 or HTTP/2 over the socket the same way it does over TCP. ALPN has no TLS to negotiate over and falls back to HTTP/1.1. Proxy configuration applies only to TCP.

On non-Unix targets, [HttpClient::new] still builds; sending a unix:// request returns [HttpClientError::UnsupportedScheme].

Example

use apollo_http_client::{HttpClient, HttpClientConfig};
use apollo_configuration::parse_yaml;
use http_body_util::Full;
use bytes::Bytes;
use tower::ServiceExt;

# #[tokio::main]
# async fn main() {
let config: HttpClientConfig = parse_yaml(
    "protocol: alpn\nconnect_timeout: 5s",
    &Default::default(),
).expect("valid config");
let client = HttpClient::new(&config).expect("valid config");

let req = http::Request::builder()
    .method("POST")
    .uri("https://api.example.com/")
    .body(Full::new(Bytes::from_static(b"payload")))
    .unwrap();

let _resp = client.oneshot(req).await.expect("request ok");
# }