Skip to content

Commit

Permalink
add documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
goatgoose committed Jun 21, 2024
1 parent 977ca2b commit 0f3f72a
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 7 deletions.
89 changes: 82 additions & 7 deletions bindings/rust/s2n-tls-hyper/src/connector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ use tower_service::Service;

type BoxError = Box<dyn std::error::Error + Send + Sync>;


/// 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<Bytes>> =
/// Client::builder(TokioExecutor::new()).build(connector);
/// ```
#[derive(Clone)]
pub struct HttpsConnector<T, B = Config>
where
Expand All @@ -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<HttpConnector, B> {
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)
}
}

Expand All @@ -54,11 +82,36 @@ where
<B as connection::Builder>::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<T, B> {
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<T, B> Service<Uri> for HttpsConnector<T, B>
where
T: Service<Uri>,
Expand All @@ -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()) });
}
Expand All @@ -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<Uri>`.
// `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);

Expand All @@ -101,25 +159,42 @@ 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<T, B>
where
B: connection::Builder,
<B as connection::Builder>::Output: Unpin,
{
connector: HttpsConnector<T, B>,
http: T,
conn_builder: B,
}

impl<T, B> Builder<T, B>
where
B: connection::Builder,
<B as connection::Builder>::Output: Unpin,
{
pub fn new(connector: HttpsConnector<T, B>) -> 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<T, B> {
self.connector
HttpsConnector {
http: self.http,
conn_builder: self.conn_builder,
}
}
}

Expand Down
40 changes: 40 additions & 0 deletions bindings/rust/s2n-tls-hyper/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Bytes>> =
//! 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;
17 changes: 17 additions & 0 deletions bindings/rust/s2n-tls-hyper/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T, B>
where
T: Read + Write + Connection + Unpin,
B: Builder,
<B as 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<TlsStream<TokioIo<T>, B::Output>>),
}

Expand Down

0 comments on commit 0f3f72a

Please sign in to comment.