Skip to content

Commit

Permalink
Merge pull request #27 from sfackler/alpn
Browse files Browse the repository at this point in the history
Handle ALPN
  • Loading branch information
sfackler authored Nov 7, 2018
2 parents c423f18 + 6bd7dd0 commit 19ffb49
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 53 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ runtime = ["hyper/runtime"]
[dependencies]
antidote = "1.0.0"
bytes = "0.4"
hyper = { version = "0.12.1", default-features = false }
hyper = { version = "0.12.14", default-features = false }
lazy_static = "1.0"
linked_hash_set = "0.1"
openssl = "0.10.7"
Expand Down
3 changes: 3 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ fn main() {
if let Ok(version) = env::var("DEP_OPENSSL_VERSION_NUMBER") {
let version = u64::from_str_radix(&version, 16).unwrap();

if version >= 0x1_00_02_00_0 {
println!("cargo:rustc-cfg=ossl102");
}
if version >= 0x1_01_01_00_0 {
println!("cargo:rustc-cfg=ossl111");
}
Expand Down
24 changes: 20 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,20 @@ pub struct HttpsConnector<T> {
impl HttpsConnector<HttpConnector> {
/// Creates a a new `HttpsConnector` using default settings.
///
/// The Hyper `HttpConnector` is used to perform the TCP socket connection.
/// 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(threads: usize) -> Result<HttpsConnector<HttpConnector>, ErrorStack> {
let mut http = HttpConnector::new(threads);
http.enforce_http(false);
let ssl = SslConnector::builder(SslMethod::tls())?;
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)
}
}
Expand Down Expand Up @@ -227,10 +234,19 @@ where
},
ConnectState::Handshake {
mut handshake,
connected,
mut connected,
} => match handshake.poll() {
Ok(Async::Ready(stream)) => {
return Ok(Async::Ready((MaybeHttpsStream::Https(stream), connected)))
// avoid unused_mut warnings on OpenSSL 1.0.1
connected = connected;

#[cfg(ossl102)]
{
if let Some(b"h2") = stream.get_ref().ssl().selected_alpn_protocol() {
connected = connected.negotiated_h2();
}
}
return Ok(Async::Ready((MaybeHttpsStream::Https(stream), connected)));
}
Ok(Async::NotReady) => {
self.0 = ConnectState::Handshake {
Expand Down
121 changes: 73 additions & 48 deletions src/test.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use futures::stream::Stream;
use hyper::client::HttpConnector;
use hyper::{Body, Client};
use openssl::ssl::{SslContext, SslFiletype, SslMethod};
use std::net::TcpListener;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
use std::thread;
use tokio::runtime::current_thread::Runtime;

Expand Down Expand Up @@ -43,46 +42,26 @@ fn google() {

#[test]
fn localhost() {
let listener = TcpListener::bind("127.0.0.1:15410").unwrap();
let listener = ::std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();

let mut ctx = SslContext::builder(SslMethod::tls()).unwrap();
let mut ctx = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
ctx.set_session_id_context(b"test").unwrap();
ctx.set_certificate_chain_file("test/cert.pem").unwrap();
ctx.set_private_key_file("test/key.pem", SslFiletype::PEM)
.unwrap();
let ctx = ctx.build();

let thread = thread::spawn(move || {
let stream = listener.accept().unwrap().0;
let ssl = Ssl::new(&ctx).unwrap();
let mut stream = ssl.accept(stream).unwrap();
stream.read_exact(&mut [0]).unwrap();
stream
.write_all(b"HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
.unwrap();
stream.shutdown().unwrap();
drop(stream);

let stream = listener.accept().unwrap().0;
let ssl = Ssl::new(&ctx).unwrap();
let mut stream = ssl.accept(stream).unwrap();
stream.read_exact(&mut [0]).unwrap();
stream
.write_all(b"HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
.unwrap();
stream.shutdown().unwrap();
drop(stream);

let stream = listener.accept().unwrap().0;
let ssl = Ssl::new(&ctx).unwrap();
let mut stream = ssl.accept(stream).unwrap();
stream.read_exact(&mut [0]).unwrap();
stream
.write_all(b"HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
.unwrap();
stream.shutdown().unwrap();
drop(stream);
for _ in 0..3 {
let stream = listener.accept().unwrap().0;
let mut stream = ctx.accept(stream).unwrap();
stream.read_exact(&mut [0]).unwrap();
stream
.write_all(b"HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
.unwrap();
stream.shutdown().unwrap();
}
});

let mut connector = HttpConnector::new(1);
Expand All @@ -106,21 +85,66 @@ fn localhost() {

let mut runtime = Runtime::new().unwrap();

let f = client
.get(format!("https://localhost:{}", port).parse().unwrap())
.and_then(|resp| {
assert!(resp.status().is_success(), "{}", resp.status());
resp.into_body().for_each(|_| Ok(()))
});
runtime.block_on(f).unwrap();
for _ in 0..3 {
let f = client
.get(format!("https://localhost:{}", port).parse().unwrap())
.and_then(|resp| {
assert!(resp.status().is_success(), "{}", resp.status());
resp.into_body().for_each(|_| Ok(()))
});
runtime.block_on(f).unwrap();
}

let f = client
.get(format!("https://localhost:{}", port).parse().unwrap())
.and_then(|resp| {
assert!(resp.status().is_success(), "{}", resp.status());
resp.into_body().for_each(|_| Ok(()))
});
runtime.block_on(f).unwrap();
thread.join().unwrap();
}

#[test]
#[cfg(ossl102)]
fn alpn_h2() {
use futures::future;
use hyper::server::conn::Http;
use hyper::service;
use hyper::Response;
use openssl::ssl::{self, AlpnError};
use tokio::net::TcpListener;
use tokio_openssl::SslAcceptorExt;

let mut listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
let port = listener.local_addr().unwrap().port();

let mut ctx = SslAcceptor::mozilla_modern(SslMethod::tls()).unwrap();
ctx.set_certificate_chain_file("test/cert.pem").unwrap();
ctx.set_private_key_file("test/key.pem", SslFiletype::PEM)
.unwrap();
ctx.set_alpn_select_callback(|_, client| {
ssl::select_next_proto(b"\x02h2", client).ok_or(AlpnError::NOACK)
});
let ctx = ctx.build();

let server = future::poll_fn(move || listener.poll_accept())
.map_err(|e| panic!("tcp accept error: {}", e))
.and_then(move |(stream, _)| ctx.accept_async(stream))
.map_err(|e| panic!("tls accept error: {}", e))
.and_then(|stream| {
assert_eq!(stream.get_ref().ssl().selected_alpn_protocol().unwrap(), b"h2");
Http::new().http2_only(true).serve_connection(
stream,
service::service_fn_ok(|_| Response::new(Body::empty())),
)
}).map(|_| ())
.map_err(|e| panic!("http error: {}", e));

let mut runtime = Runtime::new().unwrap();
runtime.spawn(server);

let mut connector = HttpConnector::new(1);
connector.enforce_http(false);
let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
ssl.set_ca_file("test/cert.pem").unwrap();
ssl.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap();

let ssl = HttpsConnector::with_connector(connector, ssl).unwrap();
let client = Client::builder().build::<_, Body>(ssl);

let f = client
.get(format!("https://localhost:{}", port).parse().unwrap())
Expand All @@ -129,6 +153,7 @@ fn localhost() {
resp.into_body().for_each(|_| Ok(()))
});
runtime.block_on(f).unwrap();
drop(client);

thread.join().unwrap();
runtime.run().unwrap();
}

0 comments on commit 19ffb49

Please sign in to comment.