diff --git a/bindings/rust/s2n-tls-hyper/src/connector.rs b/bindings/rust/s2n-tls-hyper/src/connector.rs index e1a69d6dfb4..707939622ed 100644 --- a/bindings/rust/s2n-tls-hyper/src/connector.rs +++ b/bindings/rust/s2n-tls-hyper/src/connector.rs @@ -21,6 +21,28 @@ use tower_service::Service; type BoxError = Box; + +/// hyper clients use a connector to send and receive HTTP requests over an underlying IO stream. By +/// default, hyper provides `hyper_util::client::legacy::connect::HttpConnector` for this purpose, +/// which sends and receives requests over TCP. +/// +/// The `HttpsConnector` struct wraps an HTTP connector, and uses it to negotiate TLS when the HTTPS +/// scheme is in use. The `HttpsConnector` can be provided to the +/// `hyper_util::client::legacy::Client` builder as follows: +/// ``` +/// use hyper_util::{ +/// client::legacy::Client, +/// rt::TokioExecutor, +/// }; +/// use s2n_tls_hyper::connector::HttpsConnector; +/// use s2n_tls::config::Config; +/// use bytes::Bytes; +/// use http_body_util::Empty; +/// +/// let connector = HttpsConnector::builder(Config::default()).build(); +/// let client: Client<_, Empty> = +/// Client::builder(TokioExecutor::new()).build(connector); +/// ``` #[derive(Clone)] pub struct HttpsConnector where @@ -38,13 +60,19 @@ where { /// Creates a new `Builder` used to create an `HttpsConnector`. /// - /// This builder is created using the default hyper `HttpConnector`. To use a custom HTTP + /// `conn_builder` will be used to produce the s2n-tls Connections used for negotiating HTTPS, + /// which can be an `s2n_tls::config::Config` or other `s2n_tls::connection::Builder`. + /// + /// This builder is created using the default hyper `HttpConnector`. To use an existing HTTP /// connector, use `HttpsConnector::builder_with_http()`. pub fn builder(conn_builder: B) -> Builder { let mut http = HttpConnector::new(); + + // By default, the `HttpConnector` only allows the HTTP URI scheme to be used. To negotiate + // HTTP over TLS via the HTTPS scheme, `enforce_http` must be disabled. http.enforce_http(false); - Builder::new(Self { http, conn_builder }) + Builder::new(http, conn_builder) } } @@ -54,11 +82,36 @@ where ::Output: Unpin, { /// Creates a new `Builder` used to create an `HttpsConnector`. + /// + /// `conn_builder` will be used to produce the s2n-tls Connections used for negotiating HTTPS, + /// which can be an `s2n_tls::config::Config` or other `s2n_tls::connection::Builder`. + /// + /// This API allows a `Builder` to be constructed with an existing HTTP connector, as follows: + /// ``` + /// use s2n_tls_hyper::connector::HttpsConnector; + /// use s2n_tls::config::Config; + /// use hyper_util::client::legacy::connect::HttpConnector; + /// + /// let mut http = HttpConnector::new(); + /// + /// // Ensure that the HTTP connector permits the HTTPS scheme. + /// http.enforce_http(false); + /// + /// let builder = HttpsConnector::builder_with_http(http, Config::default()); + /// ``` + /// + /// `HttpsConnector::builder()` can be used to create a new HTTP connector automatically. pub fn builder_with_http(http: T, conn_builder: B) -> Builder { - Builder::new(Self { http, conn_builder }) + Builder::new(http, conn_builder) } } +// hyper connectors MUST implement `hyper_util::client::legacy::connect::Connect`, which is an alias +// for the `tower_service::Service` trait where `Service` is implemented for `http::uri::Uri`, and +// `Service::Response` implements traits for compatibility with hyper: +// https://docs.rs/hyper-util/latest/hyper_util/client/legacy/connect/trait.Connect.html +// +// The hyper compatibility traits for `Service::Response` are implemented in `MaybeHttpsStream`. impl Service for HttpsConnector where T: Service, @@ -82,6 +135,8 @@ where } fn call(&mut self, req: Uri) -> Self::Future { + // Currently, the only supported stream type is TLS. If the application attempts to + // negotiate HTTP over plain TCP, return an error. if req.scheme() == Some(&http::uri::Scheme::HTTP) { return Box::pin(async move { Err(UnsupportedScheme.into()) }); } @@ -90,6 +145,9 @@ where let host = req.host().unwrap_or("").to_owned(); let call = self.http.call(req); Box::pin(async move { + // `HttpsConnector` wraps an HTTP connector that also implements `Service`. + // `call()` is invoked on the wrapped connector to get the underlying hyper TCP stream, + // which is converted into a tokio-compatible stream with `hyper_util::rt::TokioIo`. let tcp = call.await.map_err(Into::into)?; let tcp = TokioIo::new(tcp); @@ -101,12 +159,21 @@ where } } +/// The `Builder` struct configures and produces a new `HttpsConnector`. A Builder can be retrieved +/// with `HttpsConnector::builder()`, as follows: +/// ``` +/// use s2n_tls_hyper::connector::HttpsConnector; +/// use s2n_tls::config::Config; +/// +/// let builder = HttpsConnector::builder(Config::default()); +/// ``` pub struct Builder where B: connection::Builder, ::Output: Unpin, { - connector: HttpsConnector, + http: T, + conn_builder: B, } impl Builder @@ -114,12 +181,20 @@ where B: connection::Builder, ::Output: Unpin, { - pub fn new(connector: HttpsConnector) -> Self { - Self { connector } + /// Creates a new `Builder` used to create an `HttpsConnector`. + pub fn new(http: T, conn_builder: B) -> Self { + Self { + http, + conn_builder, + } } + /// Creates a new `HttpsConnector` from the specified builder configuration. pub fn build(self) -> HttpsConnector { - self.connector + HttpsConnector { + http: self.http, + conn_builder: self.conn_builder, + } } } diff --git a/bindings/rust/s2n-tls-hyper/src/lib.rs b/bindings/rust/s2n-tls-hyper/src/lib.rs index 26be62015e0..aef976670be 100644 --- a/bindings/rust/s2n-tls-hyper/src/lib.rs +++ b/bindings/rust/s2n-tls-hyper/src/lib.rs @@ -1,5 +1,45 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +#![warn(missing_docs)] + +//! This crate provides compatibility structs for the [hyper](https://hyper.rs/) HTTP library, +//! allowing s2n-tls to be used as the underlying TLS implementation to negotiate HTTPS with hyper +//! clients. +//! +//! `s2n-tls-hyper` provides an `HttpsConnector` struct which is compatible with the +//! `hyper_util::client::legacy::Client` builder, allowing hyper clients to be constructed with +//! configurable s2n-tls connections. The following example demonstrates how to construct a hyper +//! client with s2n-tls: +//! +//! ``` +//! use std::str::FromStr; +//! use hyper_util::{ +//! client::legacy::Client, +//! rt::TokioExecutor, +//! }; +//! use s2n_tls_hyper::connector::HttpsConnector; +//! use s2n_tls::config::Config; +//! use bytes::Bytes; +//! use http_body_util::Empty; +//! use http::uri::Uri; +//! +//! // An `HttpsConnector` is built with a `s2n_tls::connection::Builder`, such as an +//! // `s2n_tls::config::Config`, which allows for the underlying TLS connection to be configured. +//! let config = Config::default(); +//! +//! // The `HttpsConnector` wraps hyper's `HttpConnector`. `HttpsConnector::builder()` will create +//! // a new `HttpConnector` to wrap. `HttpsConnector::builder_with_http()` can be used to wrap an +//! // existing `HttpConnector`. +//! let connector = HttpsConnector::builder(Config::default()).build(); +//! +//! // The `HttpsConnector` can then be provided to the hyper Client builder, which can be used to +//! // send HTTP requests over HTTPS by specifying the HTTPS scheme in the URL. +//! let client: Client<_, Empty> = +//! Client::builder(TokioExecutor::new()).build(connector); +//! let response = client.get(Uri::from_str("https://www.amazon.com").unwrap()); +//! ``` + +/// Provides the `HttpsConnector` struct. pub mod connector; mod stream; diff --git a/bindings/rust/s2n-tls-hyper/src/stream.rs b/bindings/rust/s2n-tls-hyper/src/stream.rs index f144ce7a870..b6526c5f72b 100644 --- a/bindings/rust/s2n-tls-hyper/src/stream.rs +++ b/bindings/rust/s2n-tls-hyper/src/stream.rs @@ -14,12 +14,29 @@ use std::{ task::{Context, Poll}, }; +/// `MaybeHttpsStream` is a wrapper over a hyper TCP stream, T, allowing for TLS to be negotiated +/// over the TCP stream. +/// +/// While not currently implemented, the `MaybeHttpsStream` enum will provide an `Http` type +/// corresponding to the plain TCP stream, allowing for HTTP to be negotiated in addition to HTTPS +/// when the HTTP scheme is used. +/// +/// This struct is used to implement `tower_service::Service` for `HttpsConnector`, and shouldn't +/// need to be used directly. pub enum MaybeHttpsStream where T: Read + Write + Connection + Unpin, B: Builder, ::Output: Unpin, { + // T is the underlying hyper TCP stream, which is wrapped in a `TokioIo` type in order to make + // it compatible with tokio (implementing AsyncRead and AsyncWrite). This allows the TCP stream + // to be provided to the `s2n_tls_tokio::TlsStream`. + // + // `MaybeHttpsStream` MUST implement hyper's `Read` and `Write` traits. So, the `TlsStream` is + // wrapped in an additional `TokioIo` type, which already implements the conversion from hyper's + // traits to tokio's. This allows the `Read` and `Write` implementations for `MaybeHttpsStream` + // to simply call the `TokioIo` `poll` functions. Https(TokioIo, B::Output>>), }