hyperlane-core 22.0.9

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 http1_and_http2_plaintext_listeners_never_claim_the_same_tcp_port() {
    let (first, first_port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http1, None)
            .await
            .expect("http1 server must start");
    let (second, second_port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 server must start");
    assert_ne!(
        first_port, second_port,
        "each listener must own a distinct tcp port"
    );
    let request: Vec<u8> = http1_close_request("GET", VERSION_ROUTE_PATH);
    let raw: Vec<u8> = raw_http1_exchange(first_port, &request)
        .await
        .expect("the http1 listener must answer a plain http1 request");
    assert!(response_head(&raw).starts_with("HTTP/1.1 200"));
    first.shutdown().await;
    second.shutdown().await;
}

#[tokio::test]
async fn an_http1_listener_refuses_a_prior_knowledge_http2_client() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http1, None)
            .await
            .expect("http1 server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("the client handshake future resolves before the wire is settled");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let target: String = format!("http://{TLS_SERVER_NAME}:{port}{VERSION_ROUTE_PATH}");
    let outcome: Result<H2Reply, String> = h2_request(&mut sender, "GET", &target, &[]).await;
    assert!(
        outcome.is_err(),
        "an http1 listener must not answer an http2 request on the wire, got: {outcome:?}"
    );
    drop(sender);
    driving.abort();
    let request: Vec<u8> = http1_close_request("GET", VERSION_ROUTE_PATH);
    let raw: Vec<u8> = raw_http1_exchange(port, &request)
        .await
        .expect("the http1 listener must still serve plain http1");
    assert!(response_head(&raw).starts_with("HTTP/1.1 200"));
    control.shutdown().await;
}

#[tokio::test]
async fn an_http2_listener_does_not_answer_a_plain_http1_request() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 server must start");
    let request: &[u8] = b"GET /version HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
    if let Ok(raw) = raw_http1_exchange(port, request).await {
        let head: String = response_head(&raw);
        assert!(
            !head.starts_with("HTTP/1.1 200"),
            "an http2 listener must not answer an http1 request, got: {head}"
        );
    }
    control.shutdown().await;
}

#[tokio::test]
async fn an_http3_listener_does_not_answer_a_tcp_request() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http3, Some(trusted_tls_config()))
            .await
            .expect("http3 server must start");
    let request: &[u8] = b"GET /version HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
    if let Ok(raw) = raw_http1_exchange(port, request).await {
        let head: String = response_head(&raw);
        assert!(
            !head.starts_with("HTTP/1.1 200"),
            "an http3 listener binds udp only and must not answer tcp, got: {head}"
        );
    }
    control.shutdown().await;
}

#[tokio::test]
async fn http2_strips_every_hop_by_hop_header_but_keeps_end_to_end_ones() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let (status, headers, body): (u16, http::HeaderMap, Vec<u8>) = h2_request_with_headers(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{HOP_BY_HOP_CLEAN_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("the hop-by-hop route must answer");
    assert_eq!(status, 200);
    for name in [
        CONNECTION,
        KEEP_ALIVE,
        UPGRADE,
        TE,
        INTERNAL_CONNECTION_HEADER,
    ] {
        assert!(
            !headers.contains_key(name),
            "{name} is hop by hop on http2 and must not reach the client, got: {headers:?}"
        );
    }
    assert_eq!(
        headers
            .get(PRESERVED_HEADER_KEY)
            .map(|value: &http::HeaderValue| value.to_str().unwrap_or_default()),
        Some(PRESERVED_HEADER_VALUE),
        "an end-to-end header must survive the stripping, got: {headers:?}"
    );
    assert_eq!(String::from_utf8_lossy(&body), HOP_BY_HOP_SENT_BODY);
    let (status, _version, body): (u16, http::Version, Vec<u8>) = h2_request(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{HOP_BY_HOP_CLEAN_ROUTE_PATH}"),
        &[],
    )
    .await
    .expect("the hop-by-hop route must answer a second time on the same connection");
    assert_eq!(status, 200);
    assert_eq!(String::from_utf8_lossy(&body), HOP_BY_HOP_SENT_BODY);
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http2_refuses_a_response_announcing_a_chunked_transfer_encoding() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http2, None)
            .await
            .expect("http2 server must start");
    let (mut sender, connection) = h2_plaintext_session(port)
        .await
        .expect("h2 handshake must succeed");
    let driving: JoinHandle<()> = spawn(async move {
        let _result: Result<(), h2::Error> = connection.await;
    });
    let outcome: Result<H2Reply, String> = h2_request(
        &mut sender,
        "GET",
        &format!("http://{TLS_SERVER_NAME}:{port}{HOP_BY_HOP_ROUTE_PATH}"),
        &[],
    )
    .await;
    assert!(
        outcome.is_err(),
        "a head announcing a transfer coding must be refused, never delivered as a success, got: {outcome:?}"
    );
    drop(sender);
    driving.abort();
    control.shutdown().await;
}

#[tokio::test]
async fn http3_refuses_a_response_announcing_a_chunked_transfer_encoding() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http3, Some(trusted_tls_config()))
            .await
            .expect("http3 quic server must start");
    let outcome: Result<H3Reply, String> = h3_request(
        port,
        "GET",
        &format!("https://{TLS_SERVER_NAME}:{port}{HOP_BY_HOP_ROUTE_PATH}"),
        &[],
    )
    .await;
    assert!(
        outcome.is_err(),
        "a head announcing a transfer coding must be refused, never delivered as a success, got: {outcome:?}"
    );
    control.shutdown().await;
}

#[tokio::test]
async fn http1_keeps_its_connection_header_because_it_is_end_to_end_on_http1() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http1, None)
            .await
            .expect("http1 server must start");
    let request: Vec<u8> = http1_close_request("GET", HOP_BY_HOP_ROUTE_PATH);
    let raw: Vec<u8> = raw_http1_exchange(port, &request)
        .await
        .expect("http1 exchange must complete");
    let head: String = response_head(&raw).to_lowercase();
    assert!(
        head.contains(CONNECTION),
        "an http1 response keeps its connection header, got: {head}"
    );
    control.shutdown().await;
}

#[tokio::test]
async fn http1_split_writes_reassemble_into_one_body_across_several_writes() {
    let (control, port): (ServerControlHook, u16) =
        start_protocol_server(ServerProtocol::Http1, None)
            .await
            .expect("http1 server must start");
    let mut stream: TcpStream = connect_tcp(port).await.expect("socket must connect");
    let request: Vec<u8> = http1_close_request("GET", SPLIT_ROUTE_PATH);
    write_bounded(&mut stream, &request)
        .await
        .expect("request must be written");
    let (head, body): (String, Vec<u8>) = read_http1_head(&mut stream, SPLIT_BODY_LEN)
        .await
        .expect("the split response head must be readable");
    assert!(head.starts_with("HTTP/1.1 200"), "got: {head}");
    let text: String = String::from_utf8_lossy(&body).to_string();
    assert_eq!(
        text,
        format!("{SPLIT_HEAD_BODY}{SPLIT_TAIL_BODY}"),
        "fragments written separately must reassemble into one body"
    );
    control.shutdown().await;
}