hyperlane-core 22.0.7

A lightweight, high-performance, and cross-platform Rust HTTP server library built on Tokio. It simplifies modern web service development by providing built-in support for middleware, WebSocket, Server-Sent Events (SSE), and raw TCP communication. With a unified and ergonomic API across Windows, Linux, and MacOS, it enables developers to build robust, scalable, and event-driven network applications with minimal overhead and maximum flexibility.
Documentation
use super::*;

#[tokio::test]
async fn http2_plaintext_reports_the_real_local_and_peer_addresses_of_the_connection() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let socket: TcpStream = connect_tcp(port).await.expect("socket must connect");
    let client_local: SocketAddr = socket
        .local_addr()
        .expect("a connected socket must report its local address");
    let (mut sender, connection) = timeout(
        Duration::from_secs(CONNECT_TIMEOUT_SECS),
        h2::client::handshake(socket),
    )
    .await
    .expect("h2 prior-knowledge handshake must not time out")
    .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let (status, _version, body): H2Reply = h2_request(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{SOCKET_METADATA_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("h2 socket metadata request must complete");
    assert_eq!(status, 200);
    let text: String = String::from_utf8_lossy(&body).to_string();
    let (reported_local, reported_peer): (String, String) =
        match text.split_once(SOCKET_METADATA_SEPARATOR) {
            Some(pair) => (pair.0.to_string(), pair.1.to_string()),
            None => panic!("the route must report both addresses, got: {text:?}"),
        };
    let expected_local: SocketAddr = format!("{LOOPBACK_HOST}:{port}")
        .parse()
        .expect("the bind address must parse");
    assert_eq!(
        reported_local,
        expected_local.to_string(),
        "an h2 stream must carry the connection's real local address"
    );
    assert_eq!(
        reported_peer,
        client_local.to_string(),
        "an h2 stream must carry the connection's real peer address, not a fabricated one"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_streams_a_body_that_never_announced_a_content_length() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let (status, headers, body): H2HeadReply = h2_request_with_headers(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{UNFRAMED_BODY_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("an unframed h2 body must be accepted");
    assert_eq!(status, 200);
    assert!(
        !headers.contains_key(CONTENT_LENGTH),
        "the head must not announce a length for a body framed by data frames"
    );
    assert_eq!(
        String::from_utf8_lossy(&body),
        UNFRAMED_BODY,
        "a body with no content-length must still reach the client in full"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_plaintext_prior_knowledge_serves_http2_over_tcp() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let (status, version, body): H2Reply = h2_request(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{VERSION_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("h2 plaintext request must complete");
    assert_eq!(status, 200);
    assert_eq!(
        version,
        http::Version::HTTP_2,
        "the response must be framed as HTTP/2, not downgraded"
    );
    assert_eq!(
        String::from_utf8_lossy(&body),
        format!("{:?}", HttpVersion::Http2)
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_plaintext_prior_knowledge_reads_path_query_and_header() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let target: String = format!(
        "http://{TLS_SERVER_NAME}:{port}{PROBE_PATH}?{}",
        PROBE_QUERY
    );
    let (status, _version, _body): H2Reply = h2_request(&mut sender, "GET", &target, &[])
        .await
        .expect("h2 probe request must complete");
    assert_eq!(status, 200);
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_tls_negotiates_h2_alpn_against_validated_chain() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, Some(trusted_tls_config()))
            .await
            .expect("http2 tls server must start");
    let (alpn, (mut sender, connection)): (Option<Vec<u8>>, H2TlsSession) = h2_tls_alpn(port)
        .await
        .expect("h2 tls handshake must succeed");
    assert_eq!(
        alpn,
        Some(ALPN_HTTP2.to_vec()),
        "the server must select the h2 ALPN identifier"
    );
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let (status, version, body): H2Reply = h2_request(
        &mut sender,
        "GET",
        &format!("https://{TLS_SERVER_NAME}:{port}{VERSION_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("h2 tls request must complete");
    assert_eq!(status, 200);
    assert_eq!(version, http::Version::HTTP_2);
    assert_eq!(
        String::from_utf8_lossy(&body),
        format!("{:?}", HttpVersion::Http2)
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_tls_rejects_a_client_offering_only_http11() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, Some(trusted_tls_config()))
            .await
            .expect("http2 tls server must start");
    let outcome: io::Result<ClientTlsStream> = connect_tls(port, ALPN_HTTP1).await;
    assert!(
        outcome.is_err(),
        "an http2 listener must refuse an http/1.1-only client instead of downgrading"
    );
    let valid: io::Result<ClientTlsStream> = connect_tls(port, ALPN_HTTP2).await;
    assert!(
        valid.is_ok(),
        "a rejected client must not disturb a later valid connection"
    );
    control.shutdown().await;
}

#[tokio::test]
async fn http2_plaintext_binary_post_round_trips_hostile_bytes() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let payload: Vec<u8> = hostile_binary_payload();
    let (status, version, body): H2Reply = h2_request(
        &mut sender,
        "POST",
        &format!("http://{TLS_SERVER_NAME}:{port}{BINARY_ROUTE_PATH}"),
        &payload,
    )
    .await
    .expect("h2 binary post must complete");
    assert_eq!(status, 200);
    assert_eq!(version, http::Version::HTTP_2);
    assert_eq!(
        body, payload,
        "every hostile byte must survive the h2 body round trip"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_plaintext_delivers_a_response_above_the_flow_control_window() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let expected: Vec<u8> = large_binary_payload();
    let (status, _version, body): H2Reply = h2_request(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{LARGE_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("large h2 response must complete");
    assert_eq!(status, 200);
    assert_eq!(
        body.len(),
        expected.len(),
        "the whole multi-megabyte body must arrive"
    );
    assert!(
        body == expected,
        "the large response must be byte identical past the flow control window"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_one_connection_answers_a_fast_stream_while_a_slow_stream_is_blocked() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let (sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 prior-knowledge handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let _lease: GateLease = gate_lease(GateSlot::SlowHttp2).await;
    let order: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
    let slow_target: String = format!("http://{TLS_SERVER_NAME}:{port}{SLOW_ROUTE_PATH}");
    let slow_order: Arc<Mutex<Vec<&'static str>>> = Arc::clone(&order);
    let mut slow_sender: h2::client::SendRequest<Bytes> = sender.clone();
    let slow: JoinHandle<Result<H2Reply, String>> = spawn(async move {
        let outcome: Result<H2Reply, String> =
            h2_request(&mut slow_sender, "GET", &slow_target, &[]).await;
        slow_order.lock().expect("order lock").push("slow");
        outcome
    });
    route_gate(GateSlot::SlowHttp2).await_slow_entered().await;
    let fast_target: String = format!("http://{TLS_SERVER_NAME}:{port}{VERSION_ROUTE_PATH}");
    let fast_order: Arc<Mutex<Vec<&'static str>>> = Arc::clone(&order);
    let mut fast_sender: h2::client::SendRequest<Bytes> = sender.clone();
    let fast: JoinHandle<Result<H2Reply, String>> = spawn(async move {
        let outcome: Result<H2Reply, String> =
            h2_request(&mut fast_sender, "GET", &fast_target, &[]).await;
        fast_order.lock().expect("order lock").push("fast");
        outcome
    });
    let budget: Duration = Duration::from_millis(FAST_STREAM_BUDGET_MILLIS);
    let fast_result: Result<H2Reply, String> = timeout(budget, fast)
        .await
        .expect("the fast stream must complete while the slow handler is still blocked")
        .expect("the fast stream task must not panic");
    assert!(
        fast_result.is_ok(),
        "the fast request must succeed while the slow handler is held"
    );
    assert_eq!(
        *order.lock().expect("order lock"),
        vec!["fast"],
        "only the fast stream may complete while the slow handler is blocked"
    );
    route_gate(GateSlot::SlowHttp2).release_slow();
    let slow_result: Result<H2Reply, String> =
        timeout(Duration::from_secs(REQUEST_TIMEOUT_SECS), slow)
            .await
            .expect("the released slow stream must finish inside its timeout")
            .expect("the slow stream task must not panic");
    assert!(slow_result.is_ok());
    assert_eq!(
        *order.lock().expect("order lock"),
        vec!["fast", "slow"],
        "the fast stream must complete before the released slow one"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_plaintext_shutdown_releases_the_bound_tcp_port() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    assert_wait_pending_then_complete(&control).await;
    let rebound: io::Result<TcpListener> = TcpListener::bind(format!("{LOOPBACK_HOST}:{port}"));
    assert!(
        rebound.is_ok(),
        "port {port} must be free again after shutdown and wait"
    );
}

#[tokio::test]
async fn http2_sse_chunk_reaches_the_client_before_the_handler_finishes() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 plaintext server must start");
    let _lease: GateLease = gate_lease(GateSlot::SseHttp2).await;
    let target: String = format!("http://{TLS_SERVER_NAME}:{port}{SSE_ROUTE_PATH_HTTP2}");
    let reading: JoinHandle<(Vec<u8>, bool)> =
        spawn(async move { h2_stream_until(port, &target, SSE_FIRST_CHUNK).await });
    route_gate(GateSlot::SseHttp2)
        .await_first_chunk_sent()
        .await;
    let (collected, matched): (Vec<u8>, bool) =
        timeout(Duration::from_secs(FAST_STREAM_BUDGET_MILLIS), reading)
            .await
            .expect("the first sse chunk must be readable while the handler is still blocked")
            .expect("the reader task must not panic");
    let text: String = String::from_utf8_lossy(&collected).to_string();
    assert!(
        matched,
        "the first sse chunk must be readable while the handler is still blocked, got: {text:?}"
    );
    assert!(
        !text.contains(SSE_SECOND_CHUNK),
        "the second chunk must wait for the handler to be released, got: {text:?}"
    );
    route_gate(GateSlot::SseHttp2).release_sse();
    control.shutdown().await;
}