From f7e2fd280380e3b922bf126114844e74994fc457 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 30 Jun 2026 10:46:53 +0200 Subject: [PATCH 1/3] Record the HTTP version in the HTTP client trace As per the OTEL semantic conventions: https://opentelemetry.io/docs/specs/semconv/http/http-spans/ --- crates/http/src/reqwest.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/http/src/reqwest.rs b/crates/http/src/reqwest.rs index faea34828..2efb25856 100644 --- a/crates/http/src/reqwest.rs +++ b/crates/http/src/reqwest.rs @@ -12,6 +12,7 @@ use std::{ use futures_util::FutureExt as _; use headers::{ContentLength, HeaderMapExt as _, UserAgent}; +use http::Version; use hyper_util::client::legacy::connect::{ HttpInfo, dns::{GaiResolver, Name}, @@ -26,10 +27,12 @@ use opentelemetry_semantic_conventions::{ metric::{HTTP_CLIENT_ACTIVE_REQUESTS, HTTP_CLIENT_REQUEST_DURATION}, trace::{ ERROR_TYPE, HTTP_REQUEST_METHOD, HTTP_RESPONSE_STATUS_CODE, NETWORK_LOCAL_ADDRESS, - NETWORK_LOCAL_PORT, NETWORK_PEER_ADDRESS, NETWORK_PEER_PORT, NETWORK_TRANSPORT, - NETWORK_TYPE, SERVER_ADDRESS, SERVER_PORT, URL_FULL, URL_SCHEME, USER_AGENT_ORIGINAL, + NETWORK_LOCAL_PORT, NETWORK_PEER_ADDRESS, NETWORK_PEER_PORT, NETWORK_PROTOCOL_NAME, + NETWORK_PROTOCOL_VERSION, NETWORK_TRANSPORT, NETWORK_TYPE, SERVER_ADDRESS, SERVER_PORT, + URL_FULL, URL_SCHEME, USER_AGENT_ORIGINAL, }, }; +use reqwest::Response; use rustls_platform_verifier::ConfigVerifierExt; use tokio::time::Instant; use tower::{BoxError, Service as _}; @@ -84,6 +87,18 @@ impl reqwest::dns::Resolve for TracingResolver { } } +#[inline] +fn otel_net_protocol_version(response: &Response) -> &'static str { + match response.version() { + Version::HTTP_09 => "0.9", + Version::HTTP_10 => "1.0", + Version::HTTP_11 => "1.1", + Version::HTTP_2 => "2.0", + Version::HTTP_3 => "3.0", + _other => "_OTHER", + } +} + /// Create a new [`reqwest::Client`] with sane parameters /// /// # Panics @@ -144,6 +159,8 @@ async fn send_traced( { NETWORK_LOCAL_PORT } = tracing::field::Empty, { NETWORK_PEER_ADDRESS } = tracing::field::Empty, { NETWORK_PEER_PORT } = tracing::field::Empty, + { NETWORK_PROTOCOL_NAME } = "http", + { NETWORK_PROTOCOL_VERSION } = tracing::field::Empty, { USER_AGENT_ORIGINAL } = user_agent, "rust.error" = tracing::field::Empty, ); @@ -183,6 +200,10 @@ async fn send_traced( Ok(response) => { span.record("otel.status_code", "OK"); span.record(HTTP_RESPONSE_STATUS_CODE, response.status().as_u16()); + span.record( + NETWORK_PROTOCOL_VERSION, + otel_net_protocol_version(&response), + ); if let Some(ContentLength(content_length)) = response.headers().typed_get() { span.record(HTTP_RESPONSE_BODY_SIZE, content_length); From 938c6aee671f24d5155360998ab2f00eabebbe61 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 30 Jun 2026 10:30:28 +0200 Subject: [PATCH 2/3] Do not customize the TLS configuration of the HTTP client This fixes a bug where we were not sending the ALPN extension, which caused the HTTP client to always use HTTP/1.1 instead of HTTP/2. Fixes #5757 --- Cargo.lock | 1 - Cargo.toml | 4 ---- crates/http/Cargo.toml | 1 - crates/http/src/reqwest.rs | 10 +--------- 4 files changed, 1 insertion(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3072d6122..e3f5dcc84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3399,7 +3399,6 @@ dependencies = [ "opentelemetry-semantic-conventions", "reqwest", "rustls", - "rustls-platform-verifier", "tokio", "tower", "tower-http", diff --git a/Cargo.toml b/Cargo.toml index a103e97bf..bb6cd6183 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -541,10 +541,6 @@ version = "0.23.35" [workspace.dependencies.rustls-pki-types] version = "1.13.0" -# Use platform-specific verifier for TLS -[workspace.dependencies.rustls-platform-verifier] -version = "0.7.0" - # systemd service status notification [workspace.dependencies.sd-notify] version = "0.4.5" diff --git a/crates/http/Cargo.toml b/crates/http/Cargo.toml index 66eaac607..8b03dd2b5 100644 --- a/crates/http/Cargo.toml +++ b/crates/http/Cargo.toml @@ -27,7 +27,6 @@ opentelemetry-semantic-conventions.workspace = true opentelemetry.workspace = true reqwest.workspace = true rustls.workspace = true -rustls-platform-verifier.workspace = true tokio.workspace = true tower.workspace = true tower-http.workspace = true diff --git a/crates/http/src/reqwest.rs b/crates/http/src/reqwest.rs index 2efb25856..b24cfd2e0 100644 --- a/crates/http/src/reqwest.rs +++ b/crates/http/src/reqwest.rs @@ -33,7 +33,6 @@ use opentelemetry_semantic_conventions::{ }, }; use reqwest::Response; -use rustls_platform_verifier::ConfigVerifierExt; use tokio::time::Instant; use tower::{BoxError, Service as _}; use tracing::Instrument; @@ -107,16 +106,9 @@ fn otel_net_protocol_version(response: &Response) -> &'static str { #[must_use] pub fn client() -> reqwest::Client { // TODO: can/should we limit in-flight requests? - - // The explicit typing here is because `use_preconfigured_tls` accepts - // `Any`, but wants a `ClientConfig` under the hood. This helps us detect - // breaking changes in the rustls-platform-verifier API. - let tls_config: rustls::ClientConfig = - rustls::ClientConfig::with_platform_verifier().expect("failed to create TLS config"); - reqwest::Client::builder() .dns_resolver(Arc::new(TracingResolver::new())) - .use_preconfigured_tls(tls_config) + .tls_backend_rustls() .user_agent(USER_AGENT) .timeout(Duration::from_mins(1)) .connect_timeout(Duration::from_secs(30)) From b74ac420183d74edd39149cf110f9a6a13bd48ee Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 30 Jun 2026 12:06:34 +0200 Subject: [PATCH 3/3] Install the rustls crypto provider in the handlers test setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Building the HTTP client now relies on a process-level rustls crypto provider being installed (we no longer pass a pre-configured TLS config that bundled one). Tests going through TestState that didn't call setup() — e.g. the login_sso_redirect tests — panicked when building the client. Call setup() from from_pool_with_site_config so every TestState installs the provider. --- crates/handlers/src/test_utils.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/handlers/src/test_utils.rs b/crates/handlers/src/test_utils.rs index b24a22889..7f118ad41 100644 --- a/crates/handlers/src/test_utils.rs +++ b/crates/handlers/src/test_utils.rs @@ -170,6 +170,11 @@ impl TestState { pool: PgPool, site_config: SiteConfig, ) -> Result { + // Make sure the rustls crypto provider and tracing are set up, as + // building the HTTP client below requires a process-level provider to + // be installed. + setup(); + let workspace_root = workspace_root(); let task_tracker = TaskTracker::new();