fregate 0.17.1

Framework for services creation
Documentation
#[cfg(feature = "tls")]
mod tls {
    use fregate::{error::Error, AppConfig, Application, Empty};
    use hyper::{client::HttpConnector, Client, StatusCode, Uri};
    use hyper_rustls::{ConfigBuilderExt, HttpsConnector, HttpsConnectorBuilder};
    use rustls::{
        client::{ServerCertVerified, ServerCertVerifier},
        Certificate, ClientConfig, ServerName,
    };
    use std::{
        io::ErrorKind,
        str::FromStr,
        sync::Arc,
        time::{Duration, SystemTime},
    };
    use tokio::time::timeout;

    const ROOTLES_PORT: u16 = 1025;
    const MAX_PORT: u16 = u16::MAX;

    const TLS_KEY_FULL_PATH: &str = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/examples/examples_resources/certs/tls.key"
    );
    const TLS_CERTIFICATE_FULL_PATH: &str = concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/examples/examples_resources/certs/tls.cert"
    );

    async fn start_server() -> (u16, Duration) {
        std::env::set_var("TEST_SERVER_TLS_KEY_PATH", TLS_KEY_FULL_PATH);
        std::env::set_var("TEST_SERVER_TLS_CERT_PATH", TLS_CERTIFICATE_FULL_PATH);

        let mut config = AppConfig::<Empty>::builder()
            .add_env_prefixed("TEST")
            .add_default()
            .build()
            .unwrap();

        let mut free_port = None;
        let tls_timeout = config.tls.handshake_timeout;

        for port in ROOTLES_PORT..MAX_PORT {
            config.port = port;

            let next_config = config.clone();
            let application_handle = timeout(
                Duration::from_secs(1),
                tokio::task::spawn(async move { Application::new(next_config).serve_tls().await }),
            )
            .await;

            match application_handle {
                Err(_elapsed) => {
                    free_port = Some(port);
                    break;
                }
                Ok(Err(err)) => {
                    panic!("Unexpected error: `{err}`.");
                }
                Ok(Ok(Err(Error::IoError(err)))) => {
                    if err.kind() == ErrorKind::AddrInUse {
                        continue;
                    } else {
                        panic!("Unexpected error: `{err}`.");
                    }
                }
                Ok(Ok(Err(err))) => {
                    panic!("Unexpected error: `{err}`.");
                }
                Ok(Ok(Ok(()))) => unreachable!("impossible"),
            }
        }

        tokio::time::sleep(Duration::from_millis(200)).await;

        (free_port.expect("No free ports are available"), tls_timeout)
    }

    fn build_client() -> Client<HttpsConnector<HttpConnector>> {
        struct DummyServerCertVerifier;
        impl ServerCertVerifier for DummyServerCertVerifier {
            fn verify_server_cert(
                &self,
                _: &Certificate,
                _: &[Certificate],
                _: &ServerName,
                _: &mut dyn Iterator<Item = &[u8]>,
                _: &[u8],
                _: SystemTime,
            ) -> Result<ServerCertVerified, rustls::Error> {
                Ok(ServerCertVerified::assertion())
            }
        }

        let mut tls = ClientConfig::builder()
            .with_safe_defaults()
            .with_native_roots()
            .with_no_client_auth();
        tls.dangerous()
            .set_certificate_verifier(Arc::new(DummyServerCertVerifier));

        let https = HttpsConnectorBuilder::new()
            .with_tls_config(tls)
            .https_only()
            .enable_http1()
            .build();

        Client::builder().http2_only(true).build(https)
    }

    #[ignore]
    #[tokio::test]
    async fn test_https_request() {
        let (port, _) = start_server().await;

        let hyper = build_client();

        let timeout = Duration::from_secs(2);
        let fut = hyper.get(Uri::from_str(&format!("https://localhost:{port}/health")).unwrap());
        let response = tokio::time::timeout(timeout, fut).await.unwrap().unwrap();

        let status = response.status();
        let body = hyper::body::to_bytes(response.into_body()).await.unwrap();

        assert_eq!(StatusCode::OK, status);
        assert_eq!(body.as_ref(), b"OK");
    }

    #[ignore]
    #[tokio::test]
    async fn test_http_request() {
        let (port, tls_timeout) = start_server().await;

        let hyper = build_client();

        let timeout = tls_timeout + Duration::from_secs(2);
        let fut = hyper.get(Uri::from_str(&format!("http://localhost:{port}/health")).unwrap());
        let response = tokio::time::timeout(timeout, fut).await.unwrap();

        assert!(response.is_err());
    }
}