mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Add proxy-aware blocking OpenTelemetry HTTP transport
This commit is contained in:
@@ -11,7 +11,7 @@ futures = { workspace = true }
|
||||
http = { workspace = true }
|
||||
native-tls = "0.2"
|
||||
opentelemetry = { workspace = true }
|
||||
opentelemetry-http = { workspace = true, features = ["reqwest"] }
|
||||
opentelemetry-http = { workspace = true, features = ["reqwest", "reqwest-blocking"] }
|
||||
reqwest = { workspace = true, features = ["blocking", "json", "rustls-tls-native-roots", "stream"] }
|
||||
rustls = { workspace = true }
|
||||
rustls-native-certs = { workspace = true }
|
||||
@@ -39,6 +39,7 @@ workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
codex-utils-cargo-bin = { workspace = true }
|
||||
futures = { workspace = true, features = ["executor"] }
|
||||
opentelemetry_sdk = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
rcgen = { workspace = true }
|
||||
|
||||
@@ -182,6 +182,13 @@ pub fn build_reqwest_client_with_custom_ca(
|
||||
build_reqwest_client_with_env(&ProcessEnv, builder)
|
||||
}
|
||||
|
||||
/// Applies the same custom-CA policy to blocking exporter transports.
|
||||
pub(crate) fn build_blocking_reqwest_client_with_custom_ca(
|
||||
builder: reqwest::blocking::ClientBuilder,
|
||||
) -> Result<reqwest::blocking::Client, BuildCustomCaTransportError> {
|
||||
build_reqwest_client_with_env(&ProcessEnv, builder)
|
||||
}
|
||||
|
||||
/// Builds a rustls client config when a Codex custom CA bundle is configured.
|
||||
///
|
||||
/// This is the websocket-facing sibling of [`build_reqwest_client_with_custom_ca`]. When
|
||||
|
||||
@@ -4,7 +4,9 @@ use crate::BuildRouteAwareHttpClientError;
|
||||
use crate::ClientRouteClass;
|
||||
use crate::HttpClientFactory;
|
||||
use crate::OutboundProxyPolicy;
|
||||
use crate::OutboundProxyRoute;
|
||||
use crate::custom_ca::BuildCustomCaTransportError;
|
||||
use crate::custom_ca::build_blocking_reqwest_client_with_custom_ca;
|
||||
use opentelemetry_http::HttpClient;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
@@ -91,6 +93,46 @@ impl HttpClientFactory {
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Builds a blocking exporter client for one fixed collector endpoint.
|
||||
///
|
||||
/// Callers inside a Tokio runtime must construct this on a blocking-capable
|
||||
/// thread, just as they would for any other blocking reqwest client.
|
||||
pub fn build_blocking_telemetry_client(
|
||||
&self,
|
||||
endpoint: &str,
|
||||
timeout: Duration,
|
||||
tls: &TelemetryClientTlsConfig,
|
||||
) -> Result<impl HttpClient + 'static + use<>, BuildTelemetryHttpClientError> {
|
||||
let tls = prepare_tls_config(tls)?;
|
||||
let mut builder = reqwest::blocking::Client::builder().timeout(timeout);
|
||||
if let Some(certificate) = tls.root_certificate {
|
||||
builder = builder
|
||||
.tls_built_in_root_certs(false)
|
||||
.add_root_certificate(certificate);
|
||||
}
|
||||
if let Some(identity) = tls.client_identity {
|
||||
builder = builder.identity(identity).https_only(true);
|
||||
}
|
||||
if self.outbound_proxy_policy() == OutboundProxyPolicy::RespectSystemProxy {
|
||||
builder = builder.redirect(reqwest::redirect::Policy::none());
|
||||
}
|
||||
|
||||
builder = match self.resolve_proxy_route(endpoint) {
|
||||
OutboundProxyRoute::TransportDefault => builder,
|
||||
OutboundProxyRoute::Direct => builder.no_proxy(),
|
||||
OutboundProxyRoute::Proxy { url, no_proxy } => {
|
||||
let proxy = reqwest::Proxy::all(&url).map_err(|_| {
|
||||
BuildRouteAwareHttpClientError::InvalidProxyConfig {
|
||||
route_class: ClientRouteClass::Other,
|
||||
}
|
||||
})?;
|
||||
let no_proxy = no_proxy.as_deref().and_then(reqwest::NoProxy::from_string);
|
||||
builder.proxy(proxy.no_proxy(no_proxy))
|
||||
}
|
||||
};
|
||||
|
||||
build_blocking_reqwest_client_with_custom_ca(builder).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_tls_config(
|
||||
|
||||
@@ -41,6 +41,30 @@ async fn async_telemetry_client_uses_resolved_system_proxy() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_telemetry_client_uses_resolved_system_proxy() {
|
||||
let (proxy_address, proxy) = spawn_proxy();
|
||||
let endpoint = "http://blocking-telemetry-proxy.test/v1/metrics";
|
||||
cache_system_proxy_route_for_test(endpoint, format!("http://{proxy_address}"));
|
||||
let client = HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy)
|
||||
.build_blocking_telemetry_client(
|
||||
endpoint,
|
||||
Duration::from_secs(2),
|
||||
&TelemetryClientTlsConfig::default(),
|
||||
)
|
||||
.expect("blocking telemetry client should build");
|
||||
|
||||
let response = futures::executor::block_on(client.send_bytes(telemetry_request(endpoint)))
|
||||
.expect("telemetry request should use proxy");
|
||||
let request = proxy.join().expect("proxy should complete");
|
||||
|
||||
assert_eq!(response.status(), http::StatusCode::OK);
|
||||
assert_eq!(
|
||||
request.lines().next(),
|
||||
Some("POST http://blocking-telemetry-proxy.test/v1/metrics HTTP/1.1")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn async_system_proxy_telemetry_client_does_not_follow_redirects() {
|
||||
let (proxy_address, proxy) = spawn_proxy_with_response(
|
||||
@@ -65,6 +89,28 @@ async fn async_system_proxy_telemetry_client_does_not_follow_redirects() {
|
||||
assert_eq!(response.status(), http::StatusCode::TEMPORARY_REDIRECT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blocking_system_proxy_telemetry_client_does_not_follow_redirects() {
|
||||
let (proxy_address, proxy) = spawn_proxy_with_response(
|
||||
"HTTP/1.1 307 Temporary Redirect\r\nLocation: http://different-route.test/v1/metrics\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
|
||||
);
|
||||
let endpoint = "http://blocking-telemetry-redirect.test/v1/metrics";
|
||||
cache_system_proxy_route_for_test(endpoint, format!("http://{proxy_address}"));
|
||||
let client = HttpClientFactory::new(OutboundProxyPolicy::RespectSystemProxy)
|
||||
.build_blocking_telemetry_client(
|
||||
endpoint,
|
||||
Duration::from_secs(2),
|
||||
&TelemetryClientTlsConfig::default(),
|
||||
)
|
||||
.expect("blocking telemetry client should build");
|
||||
|
||||
let response = futures::executor::block_on(client.send_bytes(telemetry_request(endpoint)))
|
||||
.expect("redirect should be returned to the exporter");
|
||||
proxy.join().expect("proxy should complete");
|
||||
|
||||
assert_eq!(response.status(), http::StatusCode::TEMPORARY_REDIRECT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn telemetry_client_rejects_incomplete_client_identity() {
|
||||
let result = HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault)
|
||||
|
||||
Reference in New Issue
Block a user