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