h3x 0.2.0

High-performance zero-copy DHTTP/3 implementation
Documentation
mod common;
use std::sync::Arc;

use common::*;
use dquic::prelude::handy::{ToCertificate, ToPrivateKey};
use h3x::{
    dquic::H3Servers,
    error::Code,
    quic,
    server::{self, Router},
    varint::VarInt,
};
use tokio_util::task::AbortOnDropHandle;

async fn hello_world_service(_: &mut server::Request, response: &mut server::Response) {
    response
        .set_status(http::StatusCode::OK)
        .set_body(&b"Hello, World!"[..]);
}

#[test]
fn hello_world() {
    run("hello_world", async move {
        let mut server = test_server(Router::new().get("/hello_world", hello_world_service)).await;
        let host = get_server_authority(&server);
        let _serve = AbortOnDropHandle::new(tokio::spawn(async move { server.run().await }));

        let client = test_client();
        let (_, mut response) = client
            .new_request()
            .get(format!("https://{host}/hello_world").parse().unwrap())
            .await
            .expect("failed to send request");

        assert_eq!(response.status(), http::StatusCode::OK);
        let response = response
            .read_to_string()
            .await
            .expect("failed to read response body");
        assert_eq!(response, "Hello, World!");
    })
}

async fn streaming_echo_service(request: &mut server::Request, response: &mut server::Response) {
    response.set_status(http::StatusCode::OK);
    response.flush().await.expect("failed to flush response");

    while let Some(chunk) = request
        .read()
        .await
        .transpose()
        .expect("failed to read request body")
    {
        response
            .write(chunk)
            .await
            .expect("failed to write response body");
    }
}

#[test]
fn streaming_echo() {
    run("streaming_echo", async move {
        let mut server = test_server(Router::new().post("/echo", streaming_echo_service)).await;
        let host = get_server_authority(&server);
        let _serve = AbortOnDropHandle::new(tokio::spawn(async move { server.run().await }));

        let client = test_client();
        let (mut request, mut response) = client
            .new_request()
            .post(format!("https://{host}/echo").parse().unwrap())
            .await
            .expect("failed to send request");
        assert_eq!(response.status(), http::StatusCode::OK);

        request
            .write(TEST_DATA)
            .await
            .expect("failed to write body")
            .close()
            .await
            .expect("failed to close stream");
        let response = response
            .read_to_bytes()
            .await
            .expect("failed to read response body");
        assert_eq!(response, TEST_DATA);
    })
}

#[test]
fn fallback() {
    run("fallback", async move {
        let mut server = test_server(
            Router::new()
                .get("/hello_world", hello_world_service)
                .post("/hello_world", hello_world_service),
        )
        .await;
        let host = get_server_authority(&server);
        let _serve = AbortOnDropHandle::new(tokio::spawn(async move { server.run().await }));

        let client = test_client();
        let (_, response) = client
            .new_request()
            .get(format!("https://{host}/non_exist").parse().unwrap())
            .await
            .expect("failed to send request");

        assert_eq!(response.status(), http::StatusCode::NOT_FOUND);
    })
}

async fn echo_service(request: &mut server::Request, response: &mut server::Response) {
    let body = request
        .read_to_bytes()
        .await
        .expect("failed to read request body");
    response.set_status(http::StatusCode::OK).set_body(body);
}

#[test]
fn auto_close() {
    run("auto_close", async move {
        let mut server = test_server(Router::new().post("/echo", echo_service)).await;
        let host = get_server_authority(&server);
        let _serve = AbortOnDropHandle::new(tokio::spawn(async move { server.run().await }));

        let client = test_client();
        let (_, mut response) = client
            .new_request()
            .with_body(TEST_DATA)
            .post(format!("https://{host}/echo").parse().unwrap())
            .await
            .expect("failed to send request");

        assert_eq!(response.status(), http::StatusCode::OK);
        let response = response
            .read_to_bytes()
            .await
            .expect("failed to read response body");
        assert_eq!(response, TEST_DATA);
    })
}

#[test]
fn missing_server_name_closes_connection_with_no_error() {
    run(
        "missing_server_name_closes_connection_with_no_error",
        async move {
            let mut servers: H3Servers<Router> = H3Servers::builder()
                .without_client_cert_verifier()
                .expect("failed to initialize server tls")
                .with_router(Arc::new(
                    dquic::qinterface::component::route::QuicRouter::new(),
                ))
                .listen()
                .expect("failed to listen");
            servers
                .quic_listener()
                .add_server(
                    "localhost",
                    SERVER_CERT.to_certificate(),
                    SERVER_KEY.to_private_key(),
                    [
                        dquic::prelude::BindUri::from("inet://127.0.0.1:0").alloc_port(),
                        dquic::prelude::BindUri::from("inet://[::1]:0").alloc_port(),
                    ],
                    None,
                )
                .await
                .expect("failed to add server");

            let host = get_server_authority(&servers);
            let _serve = AbortOnDropHandle::new(tokio::spawn(async move { servers.run().await }));

            let client = test_client();
            let error = client
                .new_request()
                .get(
                    format!("https://{host}/hello_world")
                        .parse()
                        .expect("valid uri"),
                )
                .await
                .err()
                .expect("request should fail with no matching server name");

            match error {
                h3x::client::RequestError::ResponseStream {
                    source:
                        quic::StreamError::Connection {
                            source:
                                quic::ConnectionError::Application {
                                    source: quic::ApplicationError { code, .. },
                                },
                        },
                } => assert_eq!(code, Code::H3_NO_ERROR),
                h3x::client::RequestError::ResponseStream {
                    source:
                        quic::StreamError::Connection {
                            source:
                                quic::ConnectionError::Transport {
                                    source: quic::TransportError { kind, .. },
                                },
                        },
                } => {
                    assert_eq!(kind, VarInt::from_u32(0x0c));
                }
                // Stream reset with H3_NO_ERROR: race between GuardedQuicWriter drop
                // (RESET_STREAM) and connection close (CONNECTION_CLOSE). Both are valid.
                h3x::client::RequestError::ResponseStream {
                    source: quic::StreamError::Reset { code },
                } => {
                    assert_eq!(code, Code::H3_NO_ERROR.into_inner());
                }
                other => panic!(
                    "expected response stream close from missing-server-name connection close, got: {other:?}"
                ),
            }
        },
    )
}