Skip to main content

net/
connector.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::RefCell;
6use std::collections::hash_map::HashMap;
7use std::convert::TryFrom;
8use std::error::Error;
9use std::sync::{Arc, LazyLock};
10use std::time::Duration;
11use std::{fmt, io};
12
13use futures::task::{Context, Poll};
14use futures::{Future, TryFutureExt};
15use http::uri::{Authority, Uri as Destination};
16use http_body_util::combinators::BoxBody;
17use hyper::body::Bytes;
18use hyper::rt::Executor;
19use hyper_rustls::{HttpsConnector as HyperRustlsHttpsConnector, MaybeHttpsStream};
20use hyper_util::client::legacy::Client;
21use hyper_util::client::legacy::connect::proxy::Tunnel;
22use hyper_util::client::legacy::connect::{
23    Connected, Connection, HttpConnector as HyperHttpConnector,
24};
25use hyper_util::rt::{TokioIo, TokioTimer};
26use log::warn;
27use parking_lot::Mutex;
28use rustls::client::danger::ServerCertVerifier;
29use rustls::client::{ClientConnection, EchStatus};
30use rustls::crypto::{CryptoProvider, aws_lc_rs};
31use rustls::{CipherSuite, ClientConfig, NamedGroup, ProtocolVersion};
32use rustls_pki_types::{CertificateDer, ServerName, UnixTime};
33use servo_config::pref;
34use tokio::net::TcpStream;
35use tower::Service;
36
37use crate::async_runtime::spawn_task;
38use crate::hosts::replace_host;
39
40pub const BUF_SIZE: usize = 32768;
41
42/// ALPN identifier for HTTP/2 (RFC 7540 ยง3.1).
43pub const ALPN_H2: &str = "h2";
44
45#[derive(Clone)]
46pub struct ServoHttpConnector {
47    inner: HyperHttpConnector,
48}
49
50impl ServoHttpConnector {
51    fn new() -> ServoHttpConnector {
52        let mut inner = HyperHttpConnector::new();
53        inner.enforce_http(false);
54        inner.set_happy_eyeballs_timeout(None);
55        inner.set_connect_timeout(Some(Duration::from_secs(pref!(network_connection_timeout))));
56        ServoHttpConnector { inner }
57    }
58}
59
60impl Service<Destination> for ServoHttpConnector {
61    type Response = TokioIo<TcpStream>;
62    type Error = ConnectionError;
63    type Future =
64        std::pin::Pin<Box<dyn Future<Output = Result<TokioIo<TcpStream>, ConnectionError>> + Send>>;
65
66    fn call(&mut self, dest: Destination) -> Self::Future {
67        // Perform host replacement when making the actual TCP connection.
68        let mut new_dest = dest.clone();
69        let mut parts = dest.into_parts();
70
71        if let Some(auth) = parts.authority {
72            let host = auth.host();
73            let host = replace_host(host);
74
75            let authority = if let Some(port) = auth.port() {
76                format!("{}:{}", host, port.as_str())
77            } else {
78                (*host).to_string()
79            };
80
81            if let Ok(authority) = Authority::from_maybe_shared(authority) {
82                parts.authority = Some(authority);
83                if let Ok(dest) = Destination::from_parts(parts) {
84                    new_dest = dest
85                }
86            }
87        }
88
89        Box::pin(
90            self.inner
91                .call(new_dest)
92                .map_err(|error| ConnectionError::HttpError(BoxError::from(error))),
93        )
94    }
95
96    fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
97        Ok(()).into()
98    }
99}
100
101type BoxError = Box<dyn std::error::Error + Send + Sync>;
102
103#[derive(Clone)]
104pub struct InstrumentedConnector<T> {
105    inner: HyperRustlsHttpsConnector<T>,
106}
107
108impl<T> InstrumentedConnector<T> {
109    fn new(inner: HyperRustlsHttpsConnector<T>) -> Self {
110        Self { inner }
111    }
112}
113
114impl<T> From<HyperRustlsHttpsConnector<T>> for InstrumentedConnector<T> {
115    fn from(inner: HyperRustlsHttpsConnector<T>) -> Self {
116        Self::new(inner)
117    }
118}
119
120pub struct InstrumentedStream<T> {
121    inner: MaybeHttpsStream<T>,
122    tls_info: RefCell<Option<TlsHandshakeInfo>>,
123}
124
125impl<T: Unpin> Unpin for InstrumentedStream<T> {}
126
127impl<T> fmt::Debug for InstrumentedStream<T> {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        f.debug_struct("InstrumentedStream")
130            .field("tls_info", &self.tls_info)
131            .finish()
132    }
133}
134
135#[derive(Clone, Debug)]
136pub struct TlsHandshakeInfo {
137    pub protocol_version: Option<ProtocolVersion>,
138    pub cipher_suite: Option<CipherSuite>,
139    pub kea_group_name: Option<NamedGroup>,
140    pub signature_scheme_name: Option<String>,
141    pub alpn_protocol: Option<String>,
142    pub certificate_chain_der: Vec<Vec<u8>>,
143    pub used_ech: bool,
144}
145
146impl TlsHandshakeInfo {
147    fn from_connection(conn: &ClientConnection) -> Self {
148        let protocol_version = conn.protocol_version();
149        let cipher_suite = conn.negotiated_cipher_suite().map(|suite| suite.suite());
150        let kea_group_name = conn
151            .negotiated_key_exchange_group()
152            .map(|group| group.name());
153        let certificate_chain_der = conn
154            .peer_certificates()
155            .map(|certs| certs.iter().map(|cert| cert.as_ref().to_vec()).collect())
156            .unwrap_or_default();
157        let alpn_protocol = conn
158            .alpn_protocol()
159            .map(|proto| String::from_utf8_lossy(proto).into_owned());
160        let used_ech = matches!(conn.ech_status(), EchStatus::Accepted);
161
162        Self {
163            protocol_version,
164            cipher_suite,
165            kea_group_name,
166            signature_scheme_name: None,
167            alpn_protocol,
168            certificate_chain_der,
169            used_ech,
170        }
171    }
172}
173
174impl<T> InstrumentedStream<T>
175where
176    T: Connection + hyper::rt::Read + hyper::rt::Write + Unpin,
177{
178    fn from_maybe_https_stream(stream: MaybeHttpsStream<T>) -> Self {
179        match stream {
180            MaybeHttpsStream::Http(inner) => Self {
181                inner: MaybeHttpsStream::Http(inner),
182                tls_info: RefCell::new(None),
183            },
184            MaybeHttpsStream::Https(tls_stream) => {
185                let (_tcp, tls) = tls_stream.inner().get_ref();
186                let tls_info = TlsHandshakeInfo::from_connection(tls);
187
188                Self {
189                    inner: MaybeHttpsStream::Https(tls_stream),
190                    tls_info: RefCell::new(Some(tls_info)),
191                }
192            },
193        }
194    }
195}
196
197impl<T> Connection for InstrumentedStream<T>
198where
199    T: Connection + hyper::rt::Read + hyper::rt::Write + Unpin,
200{
201    fn connected(&self) -> Connected {
202        let connected = match &self.inner {
203            MaybeHttpsStream::Http(stream) => stream.connected(),
204            MaybeHttpsStream::Https(stream) => {
205                let (tcp, tls) = stream.inner().get_ref();
206                if tls.alpn_protocol() == Some(ALPN_H2.as_bytes()) {
207                    tcp.inner().connected().negotiated_h2()
208                } else {
209                    tcp.inner().connected()
210                }
211            },
212        };
213        if let Some(info) = self.tls_info.borrow_mut().take() {
214            connected.extra(info)
215        } else {
216            connected
217        }
218    }
219}
220
221impl<T> hyper::rt::Read for InstrumentedStream<T>
222where
223    T: Connection + hyper::rt::Read + hyper::rt::Write + Unpin,
224{
225    fn poll_read(
226        self: std::pin::Pin<&mut Self>,
227        cx: &mut Context<'_>,
228        buf: hyper::rt::ReadBufCursor<'_>,
229    ) -> Poll<Result<(), io::Error>> {
230        std::pin::Pin::new(&mut self.get_mut().inner).poll_read(cx, buf)
231    }
232}
233
234impl<T> hyper::rt::Write for InstrumentedStream<T>
235where
236    T: Connection + hyper::rt::Read + hyper::rt::Write + Unpin,
237{
238    fn poll_write(
239        self: std::pin::Pin<&mut Self>,
240        cx: &mut Context<'_>,
241        buf: &[u8],
242    ) -> Poll<Result<usize, io::Error>> {
243        std::pin::Pin::new(&mut self.get_mut().inner).poll_write(cx, buf)
244    }
245
246    fn poll_flush(
247        self: std::pin::Pin<&mut Self>,
248        cx: &mut Context<'_>,
249    ) -> Poll<Result<(), io::Error>> {
250        std::pin::Pin::new(&mut self.get_mut().inner).poll_flush(cx)
251    }
252
253    fn poll_shutdown(
254        self: std::pin::Pin<&mut Self>,
255        cx: &mut Context<'_>,
256    ) -> Poll<Result<(), io::Error>> {
257        std::pin::Pin::new(&mut self.get_mut().inner).poll_shutdown(cx)
258    }
259
260    fn is_write_vectored(&self) -> bool {
261        self.inner.is_write_vectored()
262    }
263
264    fn poll_write_vectored(
265        self: std::pin::Pin<&mut Self>,
266        cx: &mut Context<'_>,
267        bufs: &[io::IoSlice<'_>],
268    ) -> Poll<Result<usize, io::Error>> {
269        std::pin::Pin::new(&mut self.get_mut().inner).poll_write_vectored(cx, bufs)
270    }
271}
272
273impl<T> Service<Destination> for InstrumentedConnector<T>
274where
275    T: Service<Destination>,
276    T::Response: Connection + hyper::rt::Read + hyper::rt::Write + Send + Unpin + 'static,
277    T::Future: Send + 'static,
278    T::Error: Into<BoxError>,
279{
280    type Response = InstrumentedStream<T::Response>;
281    type Error = BoxError;
282    type Future = std::pin::Pin<
283        Box<dyn Future<Output = Result<InstrumentedStream<T::Response>, BoxError>> + Send>,
284    >;
285
286    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
287        self.inner.poll_ready(cx).map_err(Into::into)
288    }
289
290    fn call(&mut self, dst: Destination) -> Self::Future {
291        let future = self.inner.call(dst);
292        Box::pin(async move {
293            let stream = future.await.map_err(|error| -> BoxError { error })?;
294            Ok(InstrumentedStream::from_maybe_https_stream(stream))
295        })
296    }
297}
298
299pub type Connector = InstrumentedConnector<ServoHttpConnector>;
300pub type TlsConfig = ClientConfig;
301
302#[derive(Clone, Debug, Default)]
303struct CertificateErrorOverrideManagerInternal {
304    /// A mapping of certificates and their hosts, which have seen certificate errors.
305    /// This is used to later create an override in this [CertificateErrorOverrideManager].
306    certificates_failing_to_verify: HashMap<ServerName<'static>, CertificateDer<'static>>,
307    /// A list of certificates that should be accepted despite encountering verification
308    /// errors.
309    overrides: Vec<CertificateDer<'static>>,
310}
311
312/// This data structure is used to track certificate verification errors and overrides.
313/// It tracks:
314///  - A list of [Certificate]s with verification errors mapped by their [ServerName]
315///  - A list of [Certificate]s for which to ignore verification errors.
316#[derive(Clone, Debug, Default)]
317pub struct CertificateErrorOverrideManager(Arc<Mutex<CertificateErrorOverrideManagerInternal>>);
318
319impl CertificateErrorOverrideManager {
320    pub fn new() -> Self {
321        Self(Default::default())
322    }
323
324    /// Add a certificate to this manager's list of certificates for which to ignore
325    /// validation errors.
326    pub fn add_override(&self, certificate: &CertificateDer<'static>) {
327        self.0.lock().overrides.push(certificate.clone());
328    }
329
330    /// Given the a string representation of a sever host name, remove information about
331    /// a [Certificate] with verification errors. If a certificate with
332    /// verification errors was found, return it, otherwise None.
333    pub(crate) fn remove_certificate_failing_verification(
334        &self,
335        host: &str,
336    ) -> Option<CertificateDer<'static>> {
337        let server_name = match ServerName::try_from(host) {
338            Ok(name) => name.to_owned(),
339            Err(error) => {
340                warn!("Could not convert host string into RustTLS ServerName: {error:?}");
341                return None;
342            },
343        };
344        self.0
345            .lock()
346            .certificates_failing_to_verify
347            .remove(&server_name)
348    }
349}
350
351#[derive(Clone, Debug, Default)]
352pub enum CACertificates<'de> {
353    #[default]
354    Default,
355    Override(Vec<CertificateDer<'de>>),
356}
357
358/// Create a [TlsConfig] to use for managing a HTTP connection. This currently creates
359/// a rustls [ClientConfig].
360///
361/// FIXME: The `ignore_certificate_errors` argument ignores all certificate errors. This
362/// is used when running the WPT tests, because rustls currently rejects the WPT certificiate.
363/// See <https://github.com/servo/servo/issues/30080>
364#[servo_tracing::instrument(skip_all)]
365pub fn create_tls_config(
366    ca_certificates: CACertificates<'static>,
367    ignore_certificate_errors: bool,
368    override_manager: CertificateErrorOverrideManager,
369) -> TlsConfig {
370    let verifier = CertificateVerificationOverrideVerifier::new(
371        ca_certificates,
372        ignore_certificate_errors,
373        override_manager,
374    );
375    // TODO: After <https://github.com/rustls/rustls-platform-verifier/pull/204> is merged,
376    // `dangerous` can be removed.
377    rustls::ClientConfig::builder()
378        .dangerous()
379        .with_custom_certificate_verifier(Arc::new(verifier))
380        .with_no_client_auth()
381}
382
383#[derive(Clone)]
384struct TokioExecutor {}
385
386impl<F> Executor<F> for TokioExecutor
387where
388    F: Future<Output = ()> + 'static + std::marker::Send,
389{
390    fn execute(&self, fut: F) {
391        spawn_task(fut);
392    }
393}
394
395static CRYPTO_PROVIDER_CACHE: LazyLock<Arc<CryptoProvider>> = LazyLock::new(|| {
396    CryptoProvider::get_default()
397        .cloned()
398        // The embedder should have initialized the default crypto provider before
399        // initializing servo, so this should never fail.
400        .unwrap_or_else(|| {
401            warn!("Default crypto provider not initialized before first access in connector.");
402            Arc::new(aws_lc_rs::default_provider())
403        })
404});
405
406/// A cache for the default rustls platform verifier.
407///
408/// Instantiating a new verifier can be expensive, since it can read through all certificates:
409/// <https://github.com/rustls/rustls-platform-verifier/blob/996b1c903491641b17b3c9afb65d1352f6fc6b76/rustls-platform-verifier/src/verification/others.rs#L92>
410static RUSTLS_PLATFORM_VERIFIER_CACHE: LazyLock<Arc<rustls_platform_verifier::Verifier>> =
411    LazyLock::new(|| {
412        Arc::new(
413            rustls_platform_verifier::Verifier::new(CRYPTO_PROVIDER_CACHE.clone())
414                .expect("Could not initialize platform certificate verifier"),
415        )
416    });
417
418/// Prewarm the TLS stack to speed up the first connection
419///
420/// Currently, this force-seeds the crypto provider (from aws_lc_rs),
421/// which on my system takes around 30-50ms according to samply, spent in
422/// `tree_jitter_initialize_once`. If we don't call this function, then
423/// the initialization will happen much later, on a tokio runtime thread.
424#[inline]
425pub fn prewarm_tls() {
426    #[servo_tracing::instrument]
427    fn prewarm_tls_impl() {
428        let mut sink = [0u8; 32];
429        // The first access can be slow, if the provider needs to gather entropy.
430        let _ = CRYPTO_PROVIDER_CACHE.secure_random.fill(&mut sink);
431        // Note: We don't need to explicitly force initialize RUSTLS_PLATFORM_VERIFIER_CACHE,
432        // since the resource manager thread will do that during startup.
433    }
434
435    if let Err(error) = std::thread::Builder::new()
436        .name("Net-TLS-prewarm".into())
437        .spawn(prewarm_tls_impl)
438    {
439        warn!("Failed to spawn thread to prewarm TLS: {error:?}");
440    }
441}
442
443#[derive(Debug)]
444struct CertificateVerificationOverrideVerifier {
445    main_verifier: Arc<dyn ServerCertVerifier>,
446    ignore_certificate_errors: bool,
447    override_manager: CertificateErrorOverrideManager,
448}
449
450impl CertificateVerificationOverrideVerifier {
451    fn new(
452        ca_certficates: CACertificates<'static>,
453        ignore_certificate_errors: bool,
454        override_manager: CertificateErrorOverrideManager,
455    ) -> Self {
456        // From <https://github.com/rustls/rustls-platform-verifier/blob/main/README.md>:
457        // > Some manual setup is required, outside of cargo, to use this crate on
458        // > Android. In order to use Android's certificate verifier, the crate needs to
459        // > call into the JVM. A small Kotlin component must be included in your app's
460        // > build to support rustls-platform-verifier.
461        //
462        // Since we cannot count on embedders to do this setup, just stick with webpki roots
463        // on Android.
464        let use_webpki_roots = cfg!(target_os = "android") || pref!(network_use_webpki_roots);
465        let main_verifier = if !use_webpki_roots {
466            let verifier = match ca_certficates {
467                CACertificates::Default => RUSTLS_PLATFORM_VERIFIER_CACHE.clone(),
468                // Android doesn't support `Verifier::new_with_extra_roots`, but currently Android
469                // never uses the platform verifier at all.
470                CACertificates::Override(_certificates) => {
471                    #[cfg(target_os = "android")]
472                    unreachable!("Android should always use the WebPKI verifier.");
473                    #[cfg(not(target_os = "android"))]
474                    {
475                        let verifier = rustls_platform_verifier::Verifier::new_with_extra_roots(
476                            _certificates,
477                            CRYPTO_PROVIDER_CACHE.clone(),
478                        )
479                        .expect("Could not initialize platform certificate verifier");
480                        Arc::new(verifier)
481                    }
482                },
483            };
484            verifier as Arc<dyn ServerCertVerifier>
485        } else {
486            let mut root_store =
487                rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
488            match ca_certficates {
489                CACertificates::Default => {},
490                CACertificates::Override(certificates) => {
491                    for certificate in certificates {
492                        if root_store.add(certificate).is_err() {
493                            log::error!("Could not add an override certificate.");
494                        }
495                    }
496                },
497            }
498            rustls::client::WebPkiServerVerifier::builder(root_store.into())
499                .build()
500                .expect("Could not initialize platform certificate verifier.")
501                as Arc<dyn ServerCertVerifier>
502        };
503
504        Self {
505            main_verifier,
506            ignore_certificate_errors,
507            override_manager,
508        }
509    }
510}
511
512impl rustls::client::danger::ServerCertVerifier for CertificateVerificationOverrideVerifier {
513    fn verify_tls12_signature(
514        &self,
515        message: &[u8],
516        cert: &CertificateDer<'_>,
517        dss: &rustls::DigitallySignedStruct,
518    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
519        self.main_verifier
520            .verify_tls12_signature(message, cert, dss)
521    }
522
523    fn verify_tls13_signature(
524        &self,
525        message: &[u8],
526        cert: &CertificateDer<'_>,
527        dss: &rustls::DigitallySignedStruct,
528    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
529        self.main_verifier
530            .verify_tls13_signature(message, cert, dss)
531    }
532
533    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
534        self.main_verifier.supported_verify_schemes()
535    }
536
537    fn verify_server_cert(
538        &self,
539        end_entity: &CertificateDer<'_>,
540        intermediates: &[CertificateDer<'_>],
541        server_name: &ServerName<'_>,
542        ocsp_response: &[u8],
543        now: UnixTime,
544    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
545        let error = match self.main_verifier.verify_server_cert(
546            end_entity,
547            intermediates,
548            server_name,
549            ocsp_response,
550            now,
551        ) {
552            Ok(result) => return Ok(result),
553            Err(error) => error,
554        };
555
556        if self.ignore_certificate_errors {
557            warn!("Ignoring certficate error: {error:?}");
558            return Ok(rustls::client::danger::ServerCertVerified::assertion());
559        }
560
561        // If there's an override for this certificate, just accept it.
562        for cert_with_exception in &*self.override_manager.0.lock().overrides {
563            if *end_entity == *cert_with_exception {
564                return Ok(rustls::client::danger::ServerCertVerified::assertion());
565            }
566        }
567        self.override_manager
568            .0
569            .lock()
570            .certificates_failing_to_verify
571            .insert(server_name.to_owned(), end_entity.clone().into_owned());
572        Err(error)
573    }
574}
575
576pub type BoxedBody = BoxBody<Bytes, hyper::Error>;
577
578#[derive(Debug)]
579/// The error type for the MaybeProxyConnector
580pub enum ConnectionError {
581    HttpError(BoxError),
582    // It looks like currently the type is not exported.
583    ProxyError(BoxError),
584}
585
586impl std::fmt::Display for ConnectionError {
587    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588        write!(f, "{self:?}")
589    }
590}
591
592impl std::error::Error for ConnectionError {
593    fn source(&self) -> Option<&(dyn Error + 'static)> {
594        match self {
595            ConnectionError::HttpError(error) => error.source(),
596            ConnectionError::ProxyError(error) => error.source(),
597        }
598    }
599}
600
601#[derive(Clone)]
602/// A proxy connector. This will automatically open a proxy connection if the uri matches the proxy uri.
603/// Also respects 'no_proxy'.
604pub struct ProxyConnector {
605    /// A client without proxy for `no_proxy` matches.
606    client: ServoHttpConnector,
607    /// Matcher to see if we should forward to the proxy or not.
608    matcher: std::sync::Arc<hyper_util::client::proxy::matcher::Matcher>,
609}
610
611impl ProxyConnector {
612    fn new() -> Self {
613        let matcher_builder = hyper_util::client::proxy::matcher::Matcher::builder()
614            .http(servo_config::pref!(network_http_proxy_uri))
615            .https(servo_config::pref!(network_https_proxy_uri))
616            .no(servo_config::pref!(network_http_no_proxy));
617        ProxyConnector {
618            client: ServoHttpConnector::new(),
619            matcher: std::sync::Arc::new(matcher_builder.build()),
620        }
621    }
622}
623
624// Just forward everything to the inner type except that we modify the errors returned.
625impl Service<Destination> for ProxyConnector {
626    type Response = TokioIo<TcpStream>;
627    type Error = ConnectionError;
628    type Future =
629        std::pin::Pin<Box<dyn Future<Output = Result<TokioIo<TcpStream>, ConnectionError>> + Send>>;
630
631    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
632        self.client
633            .poll_ready(cx)
634            .map_err(|error| ConnectionError::ProxyError(BoxError::from(error)))
635    }
636
637    fn call(&mut self, req: Destination) -> Self::Future {
638        match self.matcher.intercept(&req) {
639            Some(intercept) => {
640                let mut tunnel = Tunnel::new(intercept.uri().clone(), self.client.clone());
641                let final_tunnel = if let Some(auth) = intercept.basic_auth() {
642                    tunnel.with_auth(auth.clone())
643                } else {
644                    tunnel
645                }
646                .call(req)
647                .map_err(|error| ConnectionError::ProxyError(BoxError::from(error)));
648                Box::pin(final_tunnel)
649            },
650            None => Box::pin(
651                self.client
652                    .call(req)
653                    .map_err(|error| ConnectionError::ProxyError(BoxError::from(error))),
654            ),
655        }
656    }
657}
658
659pub type ServoClient = Client<InstrumentedConnector<ProxyConnector>, BoxedBody>;
660
661pub fn create_http_client(tls_config: TlsConfig) -> ServoClient {
662    let connector = hyper_rustls::HttpsConnectorBuilder::new()
663        .with_tls_config(tls_config)
664        .https_or_http()
665        .enable_http1()
666        .enable_http2()
667        .wrap_connector(ProxyConnector::new());
668
669    Client::builder(TokioExecutor {})
670        .http1_title_case_headers(true)
671        // This is necessary to allow hyper to perform various background tasks for HTTP/2.
672        .timer(TokioTimer::new())
673        // This is necessary for hyper to reap unused idle keep-alive connections.
674        .pool_timer(TokioTimer::new())
675        // Other browsers control the maximum connections per host, tyipcally set to 6, but hyper
676        // does not allow controlling this directly. Until we have code to manually do that, this
677        // limit controls the maximum idle connections per host, which we still don't want to grow
678        // without bound.
679        .pool_max_idle_per_host(6)
680        .build(InstrumentedConnector::from(connector))
681}