|
| 1 | +use rustls::ClientConfig; |
| 2 | + |
| 3 | +use super::HttpsConnector; |
| 4 | +#[cfg(any(feature = "rustls-native-certs", feature = "webpki-roots"))] |
| 5 | +use crate::config::ConfigBuilderExt; |
| 6 | + |
| 7 | +#[cfg(feature = "tokio-runtime")] |
| 8 | +use hyper::client::HttpConnector; |
| 9 | + |
| 10 | +/// A builder for an [`HttpsConnector`] |
| 11 | +/// |
| 12 | +/// This makes configuration flexible and explicit and ensures connector |
| 13 | +/// features match crate features |
| 14 | +/// |
| 15 | +/// # Examples |
| 16 | +/// |
| 17 | +/// ``` |
| 18 | +/// use hyper_rustls::HttpsConnectorBuilder; |
| 19 | +/// |
| 20 | +/// # #[cfg(all(feature = "webpki-roots", feature = "tokio-runtime", feature = "http1"))] |
| 21 | +/// let https = HttpsConnectorBuilder::new() |
| 22 | +/// .with_webpki_roots() |
| 23 | +/// .https_only() |
| 24 | +/// .enable_http1() |
| 25 | +/// .build(); |
| 26 | +/// ``` |
| 27 | +pub struct ConnectorBuilder<State>(State); |
| 28 | + |
| 29 | +/// State of a builder that needs a TLS client config next |
| 30 | +pub struct WantsTlsConfig(()); |
| 31 | + |
| 32 | +impl ConnectorBuilder<WantsTlsConfig> { |
| 33 | + /// Creates a new [`ConnectorBuilder`] |
| 34 | + pub fn new() -> Self { |
| 35 | + Self(WantsTlsConfig(())) |
| 36 | + } |
| 37 | + |
| 38 | + /// Passes a rustls [`ClientConfig`] to configure the TLS connection |
| 39 | + /// |
| 40 | + /// The [`alpn_protocols`](ClientConfig::alpn_protocols) field will be rewritten to |
| 41 | + /// match the enabled schemes (see |
| 42 | + /// [`enable_http1`](ConnectorBuilder::enable_http1), |
| 43 | + /// [`enable_http2`](ConnectorBuilder::enable_http2)) before the |
| 44 | + /// connector is built. |
| 45 | + pub fn with_tls_config(self, config: ClientConfig) -> ConnectorBuilder<WantsSchemes> { |
| 46 | + ConnectorBuilder(WantsSchemes { tls_config: config }) |
| 47 | + } |
| 48 | + |
| 49 | + /// Shorthand for using rustls' [safe defaults][with_safe_defaults] |
| 50 | + /// and native roots |
| 51 | + /// |
| 52 | + /// See [`ConfigBuilderExt::with_native_roots`] |
| 53 | + /// |
| 54 | + /// [with_safe_defaults]: rustls::ConfigBuilder::with_safe_defaults |
| 55 | + #[cfg(feature = "rustls-native-certs")] |
| 56 | + #[cfg_attr(docsrs, doc(cfg(feature = "rustls-native-certs")))] |
| 57 | + pub fn with_native_roots(self) -> ConnectorBuilder<WantsSchemes> { |
| 58 | + self.with_tls_config( |
| 59 | + ClientConfig::builder() |
| 60 | + .with_safe_defaults() |
| 61 | + .with_native_roots(), |
| 62 | + ) |
| 63 | + } |
| 64 | + |
| 65 | + /// Shorthand for using rustls' [safe defaults][with_safe_defaults] |
| 66 | + /// and Mozilla roots |
| 67 | + /// |
| 68 | + /// See [`ConfigBuilderExt::with_webpki_roots`] |
| 69 | + /// |
| 70 | + /// [with_safe_defaults]: rustls::ConfigBuilder::with_safe_defaults |
| 71 | + #[cfg(feature = "webpki-roots")] |
| 72 | + #[cfg_attr(docsrs, doc(cfg(feature = "webpki-roots")))] |
| 73 | + pub fn with_webpki_roots(self) -> ConnectorBuilder<WantsSchemes> { |
| 74 | + self.with_tls_config( |
| 75 | + ClientConfig::builder() |
| 76 | + .with_safe_defaults() |
| 77 | + .with_webpki_roots(), |
| 78 | + ) |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +impl Default for ConnectorBuilder<WantsTlsConfig> { |
| 83 | + fn default() -> Self { |
| 84 | + Self::new() |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +/// State of a builder that needs schemes (https:// and http://) to be |
| 89 | +/// configured next |
| 90 | +pub struct WantsSchemes { |
| 91 | + tls_config: ClientConfig, |
| 92 | +} |
| 93 | + |
| 94 | +impl ConnectorBuilder<WantsSchemes> { |
| 95 | + /// Enforce the use of HTTPS when connecting |
| 96 | + /// |
| 97 | + /// Only URLs using the HTTPS scheme will be connectable. |
| 98 | + pub fn https_only(self) -> ConnectorBuilder<WantsProtocols1> { |
| 99 | + ConnectorBuilder(WantsProtocols1 { |
| 100 | + tls_config: self.0.tls_config, |
| 101 | + https_only: true, |
| 102 | + }) |
| 103 | + } |
| 104 | + |
| 105 | + /// Allow both HTTPS and HTTP when connecting |
| 106 | + /// |
| 107 | + /// HTTPS URLs will be handled through rustls, |
| 108 | + /// HTTP URLs will be handled by the lower-level connector. |
| 109 | + pub fn https_or_http(self) -> ConnectorBuilder<WantsProtocols1> { |
| 110 | + ConnectorBuilder(WantsProtocols1 { |
| 111 | + tls_config: self.0.tls_config, |
| 112 | + https_only: false, |
| 113 | + }) |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +/// State of a builder that needs to have some protocols (HTTP1 or later) |
| 118 | +/// enabled next |
| 119 | +/// |
| 120 | +/// No protocol has been enabled at this point. |
| 121 | +pub struct WantsProtocols1 { |
| 122 | + tls_config: ClientConfig, |
| 123 | + https_only: bool, |
| 124 | +} |
| 125 | + |
| 126 | +impl WantsProtocols1 { |
| 127 | + fn wrap_connector<H>(mut self, conn: H) -> HttpsConnector<H> { |
| 128 | + self.tls_config.alpn_protocols.clear(); |
| 129 | + HttpsConnector { |
| 130 | + force_https: self.https_only, |
| 131 | + http: conn, |
| 132 | + tls_config: std::sync::Arc::new(self.tls_config), |
| 133 | + } |
| 134 | + } |
| 135 | + |
| 136 | + #[cfg(feature = "tokio-runtime")] |
| 137 | + fn build(self) -> HttpsConnector<HttpConnector> { |
| 138 | + let mut http = HttpConnector::new(); |
| 139 | + // HttpConnector won't enforce scheme, but HttpsConnector will |
| 140 | + http.enforce_http(false); |
| 141 | + self.wrap_connector(http) |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +impl ConnectorBuilder<WantsProtocols1> { |
| 146 | + /// Enable HTTP1 |
| 147 | + /// |
| 148 | + /// This needs to be called explicitly, no protocol is enabled by default |
| 149 | + #[cfg(feature = "http1")] |
| 150 | + pub fn enable_http1(self) -> ConnectorBuilder<WantsProtocols2> { |
| 151 | + ConnectorBuilder(WantsProtocols2 { inner: self.0 }) |
| 152 | + } |
| 153 | + |
| 154 | + /// Enable HTTP2 |
| 155 | + /// |
| 156 | + /// This needs to be called explicitly, no protocol is enabled by default |
| 157 | + #[cfg(feature = "http2")] |
| 158 | + pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> { |
| 159 | + self.0.tls_config.alpn_protocols = vec![b"h2".to_vec()]; |
| 160 | + ConnectorBuilder(WantsProtocols3 { |
| 161 | + inner: self.0, |
| 162 | + enable_http1: false, |
| 163 | + }) |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +/// State of a builder with HTTP1 enabled, that may have some other |
| 168 | +/// protocols (HTTP2 or later) enabled next |
| 169 | +/// |
| 170 | +/// At this point a connector can be built, see |
| 171 | +/// [`build`](ConnectorBuilder<WantsProtocols2>::build) and |
| 172 | +/// [`wrap_connector`](ConnectorBuilder<WantsProtocols2>::wrap_connector). |
| 173 | +pub struct WantsProtocols2 { |
| 174 | + inner: WantsProtocols1, |
| 175 | +} |
| 176 | + |
| 177 | +impl ConnectorBuilder<WantsProtocols2> { |
| 178 | + /// Enable HTTP2 |
| 179 | + /// |
| 180 | + /// This needs to be called explicitly, no protocol is enabled by default |
| 181 | + #[cfg(feature = "http2")] |
| 182 | + pub fn enable_http2(mut self) -> ConnectorBuilder<WantsProtocols3> { |
| 183 | + self.0.inner.tls_config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; |
| 184 | + ConnectorBuilder(WantsProtocols3 { |
| 185 | + inner: self.0.inner, |
| 186 | + enable_http1: true, |
| 187 | + }) |
| 188 | + } |
| 189 | + |
| 190 | + /// This builds an [`HttpsConnector`] built on hyper's default [`HttpConnector`] |
| 191 | + #[cfg(feature = "tokio-runtime")] |
| 192 | + pub fn build(self) -> HttpsConnector<HttpConnector> { |
| 193 | + self.0.inner.build() |
| 194 | + } |
| 195 | + |
| 196 | + /// This wraps an arbitrary low-level connector into an [`HttpsConnector`] |
| 197 | + pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> { |
| 198 | + // HTTP1-only, alpn_protocols stays empty |
| 199 | + // HttpConnector doesn't have a way to say http1-only; |
| 200 | + // its connection pool may still support HTTP2 |
| 201 | + // though it won't be used |
| 202 | + self.0.inner.wrap_connector(conn) |
| 203 | + } |
| 204 | +} |
| 205 | + |
| 206 | +/// State of a builder with HTTP2 (and possibly HTTP1) enabled |
| 207 | +/// |
| 208 | +/// At this point a connector can be built, see |
| 209 | +/// [`build`](ConnectorBuilder<WantsProtocols3>::build) and |
| 210 | +/// [`wrap_connector`](ConnectorBuilder<WantsProtocols3>::wrap_connector). |
| 211 | +#[cfg(feature = "http2")] |
| 212 | +pub struct WantsProtocols3 { |
| 213 | + inner: WantsProtocols1, |
| 214 | + // ALPN is built piecemeal without the need to read back this field |
| 215 | + #[allow(dead_code)] |
| 216 | + enable_http1: bool, |
| 217 | +} |
| 218 | + |
| 219 | +#[cfg(feature = "http2")] |
| 220 | +impl ConnectorBuilder<WantsProtocols3> { |
| 221 | + /// This builds an [`HttpsConnector`] built on hyper's default [`HttpConnector`] |
| 222 | + #[cfg(feature = "tokio-runtime")] |
| 223 | + pub fn build(self) -> HttpsConnector<HttpConnector> { |
| 224 | + self.0.inner.build() |
| 225 | + } |
| 226 | + |
| 227 | + /// This wraps an arbitrary low-level connector into an [`HttpsConnector`] |
| 228 | + pub fn wrap_connector<H>(self, conn: H) -> HttpsConnector<H> { |
| 229 | + // If HTTP1 is disabled, we can set http2_only |
| 230 | + // on the Client (a higher-level object that uses the connector) |
| 231 | + // client.http2_only(!self.0.enable_http1); |
| 232 | + self.0.inner.wrap_connector(conn) |
| 233 | + } |
| 234 | +} |
0 commit comments