hyper-openssl 0.10.2

Hyper TLS support via OpenSSL
Documentation
use crate::SslStream;
use hyper::client::conn::http1;
use hyper::{server, service, Request, Response};
use hyper_util::rt::TokioIo;
use openssl::ssl::{Ssl, SslAcceptor, SslConnector, SslFiletype, SslMethod};
use std::io;
use std::pin::Pin;
use tokio::net::{TcpListener, TcpStream};

#[tokio::test]
async fn raw_client_server() {
    let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
    let addr = listener.local_addr().unwrap();

    tokio::spawn(async move {
        let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
        acceptor
            .set_private_key_file("test/key.pem", SslFiletype::PEM)
            .unwrap();
        acceptor
            .set_certificate_chain_file("test/cert.pem")
            .unwrap();
        let acceptor = acceptor.build();

        let ssl = Ssl::new(acceptor.context()).unwrap();
        let stream = listener.accept().await.unwrap().0;
        let mut stream = SslStream::new(ssl, TokioIo::new(stream)).unwrap();

        Pin::new(&mut stream).accept().await.unwrap();

        let service =
            service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(String::new())) });

        server::conn::http1::Builder::new()
            .serve_connection(stream, service)
            .await
            .unwrap();
    });

    let stream = TcpStream::connect(addr).await.unwrap();
    let stream = TokioIo::new(stream);

    let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
    builder.set_ca_file("test/cert.pem").unwrap();
    let ssl = builder
        .build()
        .configure()
        .unwrap()
        .into_ssl("localhost")
        .unwrap();
    let mut stream = SslStream::new(ssl, stream).unwrap();
    Pin::new(&mut stream).connect().await.unwrap();

    let (mut tx, conn) = http1::handshake(stream).await.unwrap();
    tokio::spawn(async move {
        if let Err(err) = conn.await {
            panic!("Connection failed: {:?}", err);
        }
    });

    let req = Request::builder().body(String::new()).unwrap();
    tx.send_request(req).await.unwrap();
}

#[tokio::test]
#[cfg(feature = "client-legacy")]
async fn legacy_client_server() {
    use crate::client::legacy::HttpsConnector;
    use hyper::body::Body;
    use hyper_util::client::legacy::connect::HttpConnector;
    use hyper_util::client::legacy::Client;
    use hyper_util::rt::TokioExecutor;
    use std::future;
    use std::pin::pin;

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

    tokio::spawn(async move {
        let mut acceptor = SslAcceptor::mozilla_intermediate_v5(SslMethod::tls()).unwrap();
        acceptor
            .set_private_key_file("test/key.pem", SslFiletype::PEM)
            .unwrap();
        acceptor
            .set_certificate_chain_file("test/cert.pem")
            .unwrap();
        let acceptor = acceptor.build();

        for _ in 0..3 {
            let ssl = Ssl::new(acceptor.context()).unwrap();
            let stream = listener.accept().await.unwrap().0;
            let mut stream = SslStream::new(ssl, TokioIo::new(stream)).unwrap();

            Pin::new(&mut stream).accept().await.unwrap();

            let service =
                service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(String::new())) });

            server::conn::http1::Builder::new()
                .keep_alive(false)
                .serve_connection(stream, service)
                .await
                .unwrap();
        }
    });

    let mut connector = HttpConnector::new();
    connector.enforce_http(false);
    let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
    ssl.set_ca_file("test/cert.pem").unwrap();

    #[cfg(ossl111)]
    {
        use std::fs::File;
        use std::io::Write;

        let file = File::create("target/keyfile.log").unwrap();
        ssl.set_keylog_callback(move |_, line| {
            let _ = writeln!(&file, "{}", line);
        });
    }

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

    for _ in 0..3 {
        let resp = client
            .get(format!("https://localhost:{port}").parse().unwrap())
            .await
            .unwrap();
        assert!(resp.status().is_success());
        let mut body = pin!(resp.into_body());
        while future::poll_fn(|cx| body.as_mut().poll_frame(cx))
            .await
            .transpose()
            .unwrap()
            .is_some()
        {}
    }
}

#[tokio::test]
#[cfg(all(feature = "client-legacy", ossl102))]
async fn legacy_alpn_h2() {
    use crate::client::legacy::HttpsConnector;
    use hyper::body::Body;
    use hyper_util::client::legacy::connect::HttpConnector;
    use hyper_util::client::legacy::Client;
    use hyper_util::rt::TokioExecutor;
    use openssl::ssl::{self, AlpnError};
    use std::future;
    use std::pin::pin;

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

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

        let stream = listener.accept().await.unwrap().0;
        let ssl = Ssl::new(acceptor.context()).unwrap();
        let mut stream = SslStream::new(ssl, TokioIo::new(stream)).unwrap();

        Pin::new(&mut stream).accept().await.unwrap();
        assert_eq!(stream.ssl().selected_alpn_protocol().unwrap(), b"h2");

        let service =
            service::service_fn(|_| async { Ok::<_, io::Error>(Response::new(String::new())) });

        server::conn::http2::Builder::new(TokioExecutor::new())
            .serve_connection(stream, service)
            .await
            .unwrap();
    };
    tokio::spawn(server);

    let mut connector = HttpConnector::new();
    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(TokioExecutor::new()).build::<_, String>(ssl);

    let resp = client
        .get(format!("https://localhost:{}", port).parse().unwrap())
        .await
        .unwrap();
    assert!(resp.status().is_success(), "{}", resp.status());
    let mut body = pin!(resp.into_body());
    while future::poll_fn(|cx| body.as_mut().poll_frame(cx))
        .await
        .transpose()
        .unwrap()
        .is_some()
    {}
}