oxihttp 0.1.4

OxiHTTP Pure-Rust HTTP facade for the COOLJAPAN ecosystem.
Documentation
//! Integration tests for `ServerBuilder::with_alpn` and HTTP/2 ALPN negotiation.
//!
//! facade:36 — HTTP/2 via ALPN roundtrip using the new `with_alpn` builder method.

#[cfg(all(feature = "tls", feature = "server", feature = "client"))]
mod tests {
    use oxitls::rcgen_bridge::generate_self_signed_ed25519;
    use rustls_pki_types::{CertificateDer, PrivateKeyDer, PrivatePkcs8KeyDer};

    /// Build PEM from the DER cert/key generated by rcgen.
    fn to_pem(ck: &oxitls::rcgen_bridge::CertifiedKey) -> (Vec<u8>, Vec<u8>) {
        let cert_pem = ck.cert_pem.as_bytes().to_vec();
        let key_pem = ck.key_pem().into_bytes();
        (cert_pem, key_pem)
    }

    /// Test that `with_alpn` bakes ALPN into the TLS config via `with_tls_from_pem`.
    ///
    /// A client advertising h2 ALPN connects to a server configured with
    /// `with_alpn(["h2", "http/1.1"])`. The auto::Builder on the server
    /// accepts both h1 and h2, so the response should be HTTP/2.
    #[tokio::test]
    async fn test_with_alpn_h2_roundtrip() {
        let ck = generate_self_signed_ed25519(&["localhost"]).expect("cert gen");
        let (cert_pem, key_pem) = to_pem(&ck);

        let router = oxihttp::Router::new().get("/h2test", |_req| async {
            oxihttp::response::text_response("h2-ok")
        });

        let (addr, _handle) = oxihttp::Server::bind("127.0.0.1:0")
            .with_alpn(["h2", "http/1.1"])
            .with_tls_from_pem(&cert_pem, &key_pem)
            .expect("with_tls_from_pem")
            .serve_with_addr(router)
            .await
            .expect("serve_with_addr");

        // Client also advertises h2 via ALPN — both sides must agree for HTTP/2 to be negotiated.
        let client = oxihttp::Client::builder()
            .with_trusted_cert_der(ck.cert_der.clone())
            .with_alpn(&["h2", "http/1.1"])
            .build_https()
            .expect("build_https");

        let url = format!("https://localhost:{}/h2test", addr.port());
        let resp = client.get(&url).expect("GET").send().await.expect("send");

        assert_eq!(resp.status(), oxihttp::StatusCode::OK);
        // Read version before consuming the body (body_text takes ownership).
        let version = resp.version();
        let body = resp.body_text().await.expect("body_text");
        assert_eq!(body, "h2-ok");
        // Verify HTTP/2 was negotiated via ALPN on both sides.
        assert_eq!(
            version,
            http::Version::HTTP_2,
            "expected HTTP/2 via ALPN negotiation, got {version:?}"
        );
    }

    /// Test that `with_alpn` via `with_tls_from_der` also works.
    #[tokio::test]
    async fn test_with_alpn_from_der_h2_roundtrip() {
        let ck = generate_self_signed_ed25519(&["localhost"]).expect("cert gen");

        let certs = vec![CertificateDer::from(ck.cert_der.clone())];
        let key = PrivateKeyDer::Pkcs8(PrivatePkcs8KeyDer::from(ck.pkcs8_der.clone()));

        let router = oxihttp::Router::new().get("/dertest", |_req| async {
            oxihttp::response::text_response("der-ok")
        });

        let (addr, _handle) = oxihttp::Server::bind("127.0.0.1:0")
            .with_alpn(["h2", "http/1.1"])
            .with_tls_from_der(certs, key)
            .expect("with_tls_from_der")
            .serve_with_addr(router)
            .await
            .expect("serve_with_addr");

        let client = oxihttp::Client::builder()
            .with_trusted_cert_der(ck.cert_der.clone())
            .with_alpn(&["h2", "http/1.1"])
            .build_https()
            .expect("build_https");

        let url = format!("https://localhost:{}/dertest", addr.port());
        let resp = client.get(&url).expect("GET").send().await.expect("send");

        assert_eq!(resp.status(), oxihttp::StatusCode::OK);
        let version = resp.version();
        let body = resp.body_text().await.expect("body_text");
        assert_eq!(body, "der-ok");
        assert_eq!(
            version,
            http::Version::HTTP_2,
            "expected HTTP/2 via ALPN negotiation, got {version:?}"
        );
    }

    /// Test that `with_alpn` with only http/1.1 results in HTTP/1.1 (not HTTP/2).
    #[tokio::test]
    async fn test_with_alpn_http1_only() {
        let ck = generate_self_signed_ed25519(&["localhost"]).expect("cert gen");
        let (cert_pem, key_pem) = to_pem(&ck);

        let router = oxihttp::Router::new().get("/h1test", |_req| async {
            oxihttp::response::text_response("h1-ok")
        });

        let (addr, _handle) = oxihttp::Server::bind("127.0.0.1:0")
            .with_alpn(["http/1.1"])
            .with_tls_from_pem(&cert_pem, &key_pem)
            .expect("with_tls_from_pem")
            .serve_with_addr(router)
            .await
            .expect("serve_with_addr");

        // Client advertises both, but server only offers http/1.1.
        let client = oxihttp::Client::builder()
            .with_trusted_cert_der(ck.cert_der.clone())
            .with_alpn(&["h2", "http/1.1"])
            .build_https()
            .expect("build_https");

        let url = format!("https://localhost:{}/h1test", addr.port());
        let resp = client.get(&url).expect("GET").send().await.expect("send");

        assert_eq!(resp.status(), oxihttp::StatusCode::OK);
        let version = resp.version();
        let body = resp.body_text().await.expect("body_text");
        assert_eq!(body, "h1-ok");
        // With only http/1.1 ALPN on the server, h2 should NOT be negotiated.
        assert_eq!(
            version,
            http::Version::HTTP_11,
            "expected HTTP/1.1 when server ALPN only includes http/1.1, got {version:?}"
        );
    }
}