hyper_rustls/connector/
builder.rs
1use rustls::ClientConfig;
2
3use super::HttpsConnector;
4#[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))]
5use crate::config::ConfigBuilderExt;
6
7#[cfg(feature = "tokio-runtime")]
8use hyper::client::HttpConnector;
9
10pub struct ConnectorBuilder<State>(State);
28
29pub struct WantsTlsConfig(());
31
32impl ConnectorBuilder<WantsTlsConfig> {
33 pub fn new() -> Self {
35 Self(WantsTlsConfig(()))
36 }
37
38 pub fn with_tls_config(self, config: ClientConfig) -> ConnectorBuilder<WantsSchemes> {
47 assert!(
48 config.alpn_protocols.is_empty(),
49 "ALPN protocols should not be pre-defined"
50 );
51 ConnectorBuilder(WantsSchemes { tls_config: config })
52 }
53
54 #[cfg(feature = "rustls-native-certs")]
61 #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))]
62 pub fn with_native_roots(self) -> ConnectorBuilder<WantsSchemes> {
63 self.with_tls_config(
64 ClientConfig::builder()
65 .with_safe_defaults()
66 .with_native_roots()
67 .with_no_client_auth(),
68 )
69 }
70
71 #[cfg(feature = "webpki-roots")]
78 #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))]
79 pub fn with_webpki_roots(self) -> ConnectorBuilder<WantsSchemes> {
80 self.with_tls_config(
81 ClientConfig::builder()
82 .with_safe_defaults()
83 .with_webpki_roots()
84 .with_no_client_auth(),
85 )
86 }
87}
88
89impl Default for ConnectorBuilder<WantsTlsConfig> {
90 fn default() -> Self {
91 Self::new()
92 }
93}
94
95pub struct WantsSchemes {
98 tls_config: ClientConfig,
99}
100
101impl ConnectorBuilder<WantsSchemes> {
102 pub fn https_only(self) -> ConnectorBuilder<WantsProtocols1> {
106 ConnectorBuilder(WantsProtocols1 {
107 tls_config: self.0.tls_config,
108 https_only: true,
109 override_server_name: None,
110 })
111 }
112
113 pub fn https_or_http(self) -> ConnectorBuilder<WantsProtocols1> {
118 ConnectorBuilder(WantsProtocols1 {
119 tls_config: self.0.tls_config,
120 https_only: false,
121 override_server_name: None,
122 })
123 }
124}
125
126pub struct WantsProtocols1 {
131 tls_config: ClientConfig,
132 https_only: bool,
133 override_server_name: Option<String>,
134}
135
136impl WantsProtocols1 {
137 fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
138 HttpsConnector {
139 force_https: self.https_only,
140 http: conn,
141 tls_config: std::sync::Arc::new(self.tls_config),
142 override_server_name: self.override_server_name,
143 }
144 }
145
146 #[cfg(feature = "tokio-runtime")]
147 fn build(self) -> HttpsConnector<HttpConnector> {
148 let mut http = HttpConnector::new();
149 http.enforce_http(false);
151 self.wrap_connector(http)
152 }
153}
154
155impl ConnectorBuilder<WantsProtocols1> {
156 #[cfg(feature = "http1")]
160 pub fn enable_http1(self) -> ConnectorBuilder<WantsProtocols2> {
161 ConnectorBuilder(WantsProtocols2 { inner: self.0 })
162 }
163
164 #[cfg(feature = "http2")]
168 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
169 pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> {
170 self.0.tls_config.alpn_protocols = vec![b"h2".to_vec()];
171 ConnectorBuilder(WantsProtocols3 {
172 inner: self.0,
173 enable_http1: false,
174 })
175 }
176
177 #[cfg(feature = "http2")]
182 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
183 pub fn enable_all_versions(mut self) -> ConnectorBuilder<WantsProtocols3> {
184 #[cfg(feature = "http1")]
185 let alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
186 #[cfg(not(feature = "http1"))]
187 let alpn_protocols = vec![b"h2".to_vec()];
188
189 self.0.tls_config.alpn_protocols = alpn_protocols;
190 ConnectorBuilder(WantsProtocols3 {
191 inner: self.0,
192 enable_http1: cfg!(feature = "http1"),
193 })
194 }
195
196 pub fn with_server_name(mut self, override_server_name: String) -> Self {
206 self.0.override_server_name = Some(override_server_name);
207 self
208 }
209}
210
211pub struct WantsProtocols2 {
218 inner: WantsProtocols1,
219}
220
221impl ConnectorBuilder<WantsProtocols2> {
222 #[cfg(feature = "http2")]
226 #[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
227 pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> {
228 self.0.inner.tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
229 ConnectorBuilder(WantsProtocols3 {
230 inner: self.0.inner,
231 enable_http1: true,
232 })
233 }
234
235 #[cfg(feature = "tokio-runtime")]
237 pub fn build(self) -> HttpsConnector<HttpConnector> {
238 self.0.inner.build()
239 }
240
241 pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
243 self.0.inner.wrap_connector(conn)
248 }
249}
250
251#[cfg(feature = "http2")]
257pub struct WantsProtocols3 {
258 inner: WantsProtocols1,
259 #[allow(dead_code)]
261 enable_http1: bool,
262}
263
264#[cfg(feature = "http2")]
265impl ConnectorBuilder<WantsProtocols3> {
266 #[cfg(feature = "tokio-runtime")]
268 pub fn build(self) -> HttpsConnector<HttpConnector> {
269 self.0.inner.build()
270 }
271
272 pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> {
274 self.0.inner.wrap_connector(conn)
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 #[test]
285 #[cfg(all(feature = "webpki-roots", feature = "http1"))]
286 fn test_builder() {
287 let _connector = super::ConnectorBuilder::new()
288 .with_webpki_roots()
289 .https_only()
290 .enable_http1()
291 .build();
292 }
293
294 #[test]
295 #[cfg(feature = "http1")]
296 #[should_panic(expected = "ALPN protocols should not be pre-defined")]
297 fn test_reject_predefined_alpn() {
298 let roots = rustls::RootCertStore::empty();
299 let mut config_with_alpn = rustls::ClientConfig::builder()
300 .with_safe_defaults()
301 .with_root_certificates(roots)
302 .with_no_client_auth();
303 config_with_alpn.alpn_protocols = vec![b"fancyprotocol".to_vec()];
304 let _connector = super::ConnectorBuilder::new()
305 .with_tls_config(config_with_alpn)
306 .https_only()
307 .enable_http1()
308 .build();
309 }
310
311 #[test]
312 #[cfg(all(feature = "http1", feature = "http2"))]
313 fn test_alpn() {
314 let roots = rustls::RootCertStore::empty();
315 let tls_config = rustls::ClientConfig::builder()
316 .with_safe_defaults()
317 .with_root_certificates(roots)
318 .with_no_client_auth();
319 let connector = super::ConnectorBuilder::new()
320 .with_tls_config(tls_config.clone())
321 .https_only()
322 .enable_http1()
323 .build();
324 assert!(connector
325 .tls_config
326 .alpn_protocols
327 .is_empty());
328 let connector = super::ConnectorBuilder::new()
329 .with_tls_config(tls_config.clone())
330 .https_only()
331 .enable_http2()
332 .build();
333 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
334 let connector = super::ConnectorBuilder::new()
335 .with_tls_config(tls_config.clone())
336 .https_only()
337 .enable_http1()
338 .enable_http2()
339 .build();
340 assert_eq!(
341 &connector.tls_config.alpn_protocols,
342 &[b"h2".to_vec(), b"http/1.1".to_vec()]
343 );
344 let connector = super::ConnectorBuilder::new()
345 .with_tls_config(tls_config)
346 .https_only()
347 .enable_all_versions()
348 .build();
349 assert_eq!(
350 &connector.tls_config.alpn_protocols,
351 &[b"h2".to_vec(), b"http/1.1".to_vec()]
352 );
353 }
354
355 #[test]
356 #[cfg(all(not(feature = "http1"), feature = "http2"))]
357 fn test_alpn_http2() {
358 let roots = rustls::RootCertStore::empty();
359 let tls_config = rustls::ClientConfig::builder()
360 .with_safe_defaults()
361 .with_root_certificates(roots)
362 .with_no_client_auth();
363 let connector = super::ConnectorBuilder::new()
364 .with_tls_config(tls_config.clone())
365 .https_only()
366 .enable_http2()
367 .build();
368 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
369 let connector = super::ConnectorBuilder::new()
370 .with_tls_config(tls_config)
371 .https_only()
372 .enable_all_versions()
373 .build();
374 assert_eq!(&connector.tls_config.alpn_protocols, &[b"h2".to_vec()]);
375 }
376}