Skip to content

Commit

Permalink
Add HttpsLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
sfackler committed Oct 1, 2020
1 parent 22eff0a commit 288dc8b
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 32 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ openssl = "0.10.19"
openssl-sys = "0.9.26"
tokio = "0.2"
tokio-openssl = "0.4"
tower-layer = "0.3"

[dev-dependencies]
hyper = "0.13"
Expand Down
101 changes: 71 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use std::sync::Arc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_openssl::SslStream;
use tower_layer::Layer;

mod cache;
#[cfg(test)]
Expand Down Expand Up @@ -74,49 +75,30 @@ impl Inner {
}
}

/// A Connector using OpenSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
/// A layer which wraps services in an `HttpsConnector`.
pub struct HttpsLayer {
inner: Inner,
}

#[cfg(feature = "runtime")]
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
impl HttpsLayer {
/// Creates a new `HttpsLayer` with default settings.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);
/// ALPN is configured to support both HTTP/1 and HTTP/1.1.
pub fn new() -> Result<HttpsLayer, ErrorStack> {
let mut ssl = SslConnector::builder(SslMethod::tls())?;
// avoid unused_mut warnings when building against OpenSSL 1.0.1
ssl = ssl;

#[cfg(ossl102)]
ssl.set_alpn_protos(b"\x02h2\x08http/1.1")?;

HttpsConnector::with_connector(http, ssl)
Self::with_connector(ssl)
}
}

impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = T> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
/// Creates a new `HttpsLayer`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
mut ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
pub fn with_connector(mut ssl: SslConnectorBuilder) -> Result<HttpsLayer, ErrorStack> {
let cache = Arc::new(Mutex::new(SessionCache::new()));

ssl.set_session_cache_mode(SslSessionCacheMode::CLIENT);
Expand All @@ -135,8 +117,7 @@ where
move |_, session| cache.lock().remove(session)
});

Ok(HttpsConnector {
http,
Ok(HttpsLayer {
inner: Inner {
ssl: ssl.build(),
cache,
Expand All @@ -154,6 +135,66 @@ where
}
}

impl<S> Layer<S> for HttpsLayer {
type Service = HttpsConnector<S>;

fn layer(&self, inner: S) -> HttpsConnector<S> {
HttpsConnector {
http: inner,
inner: self.inner.clone(),
}
}
}

/// A Connector using OpenSSL to support `http` and `https` schemes.
#[derive(Clone)]
pub struct HttpsConnector<T> {
http: T,
inner: Inner,
}

#[cfg(feature = "runtime")]
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection. ALPN is configured to support both
/// HTTP/2 and HTTP/1.1.
///
/// Requires the `runtime` Cargo feature.
pub fn new() -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new();
http.enforce_http(false);

HttpsLayer::new().map(|l| l.layer(http))
}
}

impl<S, T> HttpsConnector<S>
where
S: Service<Uri, Response = T> + Send,
S::Error: Into<Box<dyn Error + Send + Sync>>,
S::Future: Unpin + Send + 'static,
T: AsyncRead + AsyncWrite + Connection + Unpin + Debug + Sync + Send + 'static,
{
/// Creates a new `HttpsConnector`.
///
/// The session cache configuration of `ssl` will be overwritten.
pub fn with_connector(
http: S,
ssl: SslConnectorBuilder,
) -> Result<HttpsConnector<S>, ErrorStack> {
HttpsLayer::with_connector(ssl).map(|l| l.layer(http))
}

/// Registers a callback which can customize the configuration of each connection.
pub fn set_callback<F>(&mut self, callback: F)
where
F: Fn(&mut ConnectConfiguration, &Uri) -> Result<(), ErrorStack> + 'static + Sync + Send,
{
self.inner.callback = Some(Arc::new(callback));
}
}

impl<S> Service<Uri> for HttpsConnector<S>
where
S: Service<Uri> + Send,
Expand Down
6 changes: 4 additions & 2 deletions src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use tokio::net::TcpListener;
#[cfg(feature = "runtime")]
async fn google() {
let ssl = HttpsConnector::new().unwrap();
let client = Client::builder().keep_alive(false).build::<_, Body>(ssl);
let client = Client::builder()
.pool_max_idle_per_host(0)
.build::<_, Body>(ssl);

for _ in 0..3 {
let resp = client
Expand Down Expand Up @@ -48,7 +50,7 @@ async fn localhost() {
service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(Body::empty())) });

Http::new()
.keep_alive(false)
.http1_keep_alive(false)
.serve_connection(stream, service)
.await
.unwrap();
Expand Down

0 comments on commit 288dc8b

Please sign in to comment.