1use urly::Url;
2
3use crate::connect::{self, Connect as TcpConnect, Connector as TcpConnector};
4use crate::service::{Identity, IntoService, Middleware, Pipeline, Service, Stack, apply_fn};
5use crate::{SharedCfg, error::Error, io::IoBoxed};
6
7use super::connector::Connector;
8use super::error::{ClientError, ConnectError};
9use super::pool::ConnectionPool;
10use super::service::{ServiceRequest, ServiceResponse};
11use super::{Client, ClientConfig, Connect, ConnectorPipeline, sender::Sender};
12
13#[cfg(feature = "openssl")]
14use tls_openssl::ssl::SslConnector as OpensslConnector;
15
16#[cfg(feature = "rustls")]
17use tls_rustls::ClientConfig as RustlsClientConfig;
18
19#[derive(Debug)]
24pub struct ClientBuilder<M = Identity> {
25 middleware: M,
26 svc: ConnectorPipeline,
27 secure_svc: Option<ConnectorPipeline>,
28}
29
30impl Default for ClientBuilder<Identity> {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl ClientBuilder<Identity> {
37 #[must_use]
38 pub fn new() -> Self {
40 let svc = ConnectorPipeline::new(
41 apply_fn(TcpConnector::new(), async move |msg: Connect, svc| {
42 svc.call(TcpConnect::new(msg.uri).set_addr(msg.addr)).await
43 })
44 .map(IoBoxed::from)
45 .map_err(|e| e.map(ConnectError::from)),
46 );
47
48 let builder = ClientBuilder {
49 svc,
50 secure_svc: None,
51 middleware: Identity,
52 };
53
54 #[cfg(feature = "openssl")]
55 {
56 use tls_openssl::ssl::SslMethod;
57
58 let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap();
59 let _ = ssl
60 .set_alpn_protos(b"\x02h2\x08http/1.1")
61 .map_err(|e| log::error!("Cannot set ALPN protocol: {e:?}"));
62 ssl.set_verify(tls_openssl::ssl::SslVerifyMode::NONE);
63
64 builder.openssl(ssl.build())
65 }
66 #[cfg(all(not(feature = "openssl"), feature = "rustls"))]
67 {
68 use tls_rustls::RootCertStore;
69
70 let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
71 let cert_store = webpki_roots::TLS_SERVER_ROOTS
72 .iter()
73 .cloned()
74 .collect::<RootCertStore>();
75 let mut config = RustlsClientConfig::builder()
76 .with_root_certificates(cert_store)
77 .with_no_client_auth();
78 config.alpn_protocols = protos;
79 builder.rustls(config)
80 }
81 #[cfg(not(any(feature = "openssl", feature = "rustls")))]
82 {
83 builder
84 }
85 }
86}
87
88impl<M> ClientBuilder<M> {
89 #[must_use]
90 #[cfg(feature = "openssl")]
91 pub fn openssl(self, config: OpensslConnector) -> Self {
93 use crate::connect::openssl::SslConnector;
94
95 self.secure_connector(SslConnector::new(config))
96 }
97
98 #[must_use]
99 #[cfg(feature = "rustls")]
100 pub fn rustls(self, config: RustlsClientConfig) -> Self {
102 use crate::connect::rustls::TlsConnector;
103
104 self.secure_connector(TlsConnector::new(config))
105 }
106
107 #[must_use]
108 pub fn connector<T>(mut self, f: impl IntoService<T, SharedCfg, TcpConnect<Url>>) -> Self
110 where
111 T: Service<SharedCfg, TcpConnect<Url>, Error = Error<connect::ConnectError>> + 'static,
112 IoBoxed: From<T::Res>,
113 {
114 self.svc = ConnectorPipeline::new(
115 apply_fn(f.into_service(), async move |msg: Connect, svc| {
116 svc.call(TcpConnect::new(msg.uri).set_addr(msg.addr)).await
117 })
118 .map(IoBoxed::from)
119 .map_err(|e| e.map(ConnectError::from)),
120 );
121 self
122 }
123
124 #[must_use]
125 pub fn secure_connector<T>(mut self, f: impl IntoService<T, SharedCfg, TcpConnect<Url>>) -> Self
127 where
128 T: Service<SharedCfg, TcpConnect<Url>, Error = Error<connect::ConnectError>> + 'static,
129 IoBoxed: From<T::Res>,
130 {
131 self.secure_svc = Some(ConnectorPipeline::new(
132 apply_fn(f.into_service(), async move |msg: Connect, svc| {
133 svc.call(TcpConnect::new(msg.uri).set_addr(msg.addr)).await
134 })
135 .map(IoBoxed::from)
136 .map_err(|e| e.map(ConnectError::from)),
137 ));
138 self
139 }
140
141 #[must_use]
142 pub fn middleware<U>(self, mw: U) -> ClientBuilder<Stack<U, M>> {
163 ClientBuilder {
164 middleware: Stack::new(mw, self.middleware),
165 svc: self.svc,
166 secure_svc: self.secure_svc,
167 }
168 }
169
170 pub fn build(self, cfg: impl Into<SharedCfg>) -> Client
193 where
194 M: Middleware<Sender, SharedCfg>,
195 M::Service: Service<SharedCfg, ServiceRequest, Res = ServiceResponse, Error = Error<ClientError>>
196 + 'static,
197 {
198 let cfg = cfg.into();
199 let config = cfg.get::<ClientConfig>();
200
201 let connector = Connector {
202 tcp_pool: ConnectionPool::new(self.svc, config.clone()),
203 ssl_pool: self
204 .secure_svc
205 .map(|svc| ConnectionPool::new(svc, config.clone())),
206 };
207 let svc = self.middleware.create(&cfg, Sender::new(connector));
208
209 Client::with_service(config, Pipeline::new(cfg, svc))
210 }
211}