1use std::collections::HashMap;
2use std::sync::{Arc, Mutex};
3
4use apollo_opentelemetry::metrics::Clock;
5use apollo_opentelemetry::tower::ServiceBuilderExt as _;
6use hyper_util::client::legacy::connect::HttpConnector;
7use rustls::ClientConfig;
8use tower::ServiceBuilder;
9use tower::util::BoxCloneSyncService;
10
11use crate::client::{HttpClient, HttpClientDispatch, HttpClientState};
12use crate::config::{HttpClientConfig, Protocol};
13use crate::error::HttpClientError;
14use crate::metrics::HttpMetrics;
15use crate::protocol::h1::H1Connector;
16use crate::protocol::h2::H2Connector;
17use crate::protocol::{HostSender, Origin};
18use crate::spans::HttpSpans;
19use crate::tls::{build_https_connector, build_tls_config, with_protocol_alpn};
20
21pub struct HttpClientBuilder {
25 config: HttpClientConfig,
26 tls_override: Option<Arc<ClientConfig>>,
27}
28
29impl HttpClientBuilder {
30 pub(crate) fn new(config: HttpClientConfig) -> Self {
31 Self {
32 config,
33 tls_override: None,
34 }
35 }
36
37 pub fn with_tls_config(mut self, tls: Arc<ClientConfig>) -> Self {
44 self.tls_override = Some(tls);
45 self
46 }
47
48 pub fn build(self) -> Result<HttpClient, HttpClientError> {
52 self.build_with_clock(Clock::new())
53 }
54
55 pub(crate) fn build_with_clock(self, clock: Clock) -> Result<HttpClient, HttpClientError> {
56 let HttpClientBuilder {
57 config,
58 tls_override,
59 } = self;
60
61 apollo_configuration::validate(config.clone())
67 .map_err(|source| HttpClientError::InvalidConfig { source })?;
68
69 let mut http = HttpConnector::new();
70 http.enforce_http(false);
71 http.set_nodelay(config.tcp.nodelay);
72 http.set_keepalive(Some(*config.tcp.keepalive));
73
74 let tls_config: Arc<ClientConfig> = match tls_override {
75 Some(ref base) => Arc::new(with_protocol_alpn(base, config.protocol)),
76 None => build_tls_config(config.protocol, &config.tls)?,
77 };
78
79 let connect_timeout = *config.connect_timeout;
80
81 let configured_protocol_version = match config.protocol {
82 Protocol::Http2 => http::Version::HTTP_2,
83 Protocol::Http1 | Protocol::Alpn => http::Version::HTTP_11,
84 };
85 let metrics = HttpMetrics::new(
86 clock,
87 config.telemetry.metrics.request_body_size,
88 config.telemetry.metrics.response_body_size,
89 config.telemetry.metrics.url_scheme,
90 );
91 let conn_metrics = metrics.connection_metrics();
92 let h1_max_idle = config.http1.pool_max_idle_per_host.get();
93 let h1_idle_timeout = *config.http1.pool_idle_timeout;
94 let h2_idle_timeout = *config.http2.connection_idle_timeout;
95
96 #[cfg(unix)]
97 let unix_config = Arc::new(crate::protocol::unix::UnixSenderConfig {
98 protocol: config.protocol,
99 connect_timeout,
100 h1_max_idle,
101 h1_idle_timeout,
102 h2_idle_timeout,
103 keep_alive_interval: *config.http2.keep_alive_interval,
104 keep_alive_timeout: *config.http2.keep_alive_timeout,
105 keep_alive_while_idle: config.http2.keep_alive_while_idle,
106 conn_metrics: conn_metrics.clone(),
107 });
108
109 let tcp_make_sender: Box<dyn Fn(Origin) -> HostSender + Send + Sync> =
110 if let Some(proxy) = &config.proxy {
111 let proxy_url = proxy.url.unredact();
112 let proxy_host = proxy_url
113 .host_str()
114 .expect("validate above rejects URLs with no host")
115 .to_string();
116 let proxy_port = proxy_url.port_or_known_default().expect(
117 "validate above rejects non-http(s) schemes, both of which have default ports",
118 );
119 let conn_metrics = conn_metrics.with_peer(proxy_host.as_str(), proxy_port);
122 let proxy_config = Arc::new(crate::proxy::ProxySenderConfig {
123 proxy_host,
124 proxy_port,
125 proxy_auth: crate::proxy::proxy_authorization(proxy_url),
126 tls_config,
127 protocol: config.protocol,
128 connect_timeout,
129 h1_max_idle,
130 h1_idle_timeout,
131 h2_idle_timeout,
132 keep_alive_interval: *config.http2.keep_alive_interval,
133 keep_alive_timeout: *config.http2.keep_alive_timeout,
134 keep_alive_while_idle: config.http2.keep_alive_while_idle,
135 conn_metrics,
136 });
137 Box::new(move |key| crate::proxy::new_sender(&proxy_config, key))
138 } else {
139 let https = build_https_connector(http, tls_config, config.protocol);
140 let h1_connector =
141 H1Connector::new(https.clone(), conn_metrics.clone(), connect_timeout);
142 let h2_connector = H2Connector::new(
143 https.clone(),
144 conn_metrics.clone(),
145 connect_timeout,
146 *config.http2.keep_alive_interval,
147 *config.http2.keep_alive_timeout,
148 config.http2.keep_alive_while_idle,
149 );
150 match config.protocol {
151 Protocol::Http2 => Box::new(move |key| {
152 crate::protocol::h2::new_sender(
153 h2_connector.clone(),
154 h2_idle_timeout,
155 key,
156 |_| {},
157 )
158 }),
159 Protocol::Http1 => Box::new(move |key| {
160 crate::protocol::h1::new_sender(
161 h1_connector.clone(),
162 h1_max_idle,
163 h1_idle_timeout,
164 key,
165 |_req| {},
166 )
167 }),
168 Protocol::Alpn => Box::new(move |key| {
169 crate::protocol::alpn::new_sender(
170 h1_connector.clone(),
171 h1_max_idle,
172 h1_idle_timeout,
173 h2_connector.clone(),
174 h2_idle_timeout,
175 key,
176 )
177 }),
178 }
179 };
180
181 let make_sender: Box<dyn Fn(Origin) -> HostSender + Send + Sync> = Box::new(move |key| {
185 if key.scheme.as_str() == "unix" {
186 #[cfg(unix)]
187 {
188 crate::protocol::unix::new_sender(&unix_config, key)
189 }
190 #[cfg(not(unix))]
191 {
192 crate::client::unsupported_scheme_sender()
193 }
194 } else {
195 tcp_make_sender(key)
196 }
197 });
198
199 let spans = HttpSpans::new(config.telemetry.spans.clone());
200 let dispatch = HttpClientDispatch {
201 state: Arc::new(Mutex::new(HttpClientState {
202 pools: HashMap::new(),
203 make_sender,
204 })),
205 metrics,
206 spans: spans.clone(),
207 configured_protocol_version,
208 };
209 let service = ServiceBuilder::new()
210 .traced(
211 apollo_opentelemetry::default_instrumentation_scope!(),
212 spans,
213 )
214 .http_client_propagation()
215 .service(dispatch);
216 let inner = BoxCloneSyncService::new(service);
217
218 Ok(HttpClient::from_service(inner))
219 }
220}