ehttpd 0.14.0

A HTTP server nano-framework, which can be used to create custom small-scale HTTP server applications
Documentation
use ehttpd::bytes::{Data, Source};
use ehttpd::http::Request;
use std::io::Read;

#[test]
fn request_header_is_parsed() {
    // A request with a header field
    let mut source = Source::from(concat! {
        "POST /resource HTTP/1.1\r\n",
        "Host: example.test\r\n",
        "\r\n",
    });

    // Parse the request and ensure that all header components are available
    let request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    assert_eq!(request.method, b"POST");
    assert_eq!(request.target, b"/resource");
    assert_eq!(request.version, b"HTTP/1.1");
    assert_eq!(request.field("host").expect("host field is missing").as_ref(), b"example.test");
}

#[test]
fn closed_connection_has_no_request() {
    // Ensure that no request is returned
    let mut source = Source::default();
    let request: Option<Request> = Request::from_stream(&mut source).expect("failed to parse request");
    assert!(request.is_none());
}

#[test]
fn oversized_header_is_rejected() {
    // A request larger than the configured header limit
    let mut source = Source::from(concat! {
        "GET / HTTP/1.1\r\n",
        "\r\n",
    });

    // Ensure that the header is rejected
    let request: Result<Option<Request<16>>, _> = Request::from_stream(&mut source);
    let error = request.expect_err("oversized header was accepted");
    assert_eq!(error.error, "HTTP header is too large");
}

#[test]
fn body_with_content_length_is_read() {
    // A request with a fixed-length body
    let mut source = Source::from(concat! {
        "POST / HTTP/1.1\r\n",
        "Content-Length: 6\r\n",
        "\r\n",
        "<BODY>",
    });

    // Parse the request and read its body
    let mut request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    let body = request.read_body_data(6).expect("failed to read request body");
    assert_eq!(body, Some(Data::new_static(b"<BODY>")));
}

#[test]
fn body_without_content_length_is_not_read() {
    // A request without a content-length field
    let mut source = Source::from(concat! {
        "POST / HTTP/1.1\r\n",
        "\r\n",
        "<BODY>",
    });

    // Parse the request and attempt to read its body
    let mut request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    let body = request.read_body_data(6).expect("failed to inspect request body");
    assert!(body.is_none());

    // Ensure that the indeterminate body is left unread
    let mut unread = [0; 6];
    request.stream.read_exact(&mut unread).expect("failed to read remaining body");
    assert_eq!(unread, *b"<BODY>");
}

#[test]
fn chunked_body_is_not_read() {
    // A request with chunked transfer encoding
    let mut source = Source::from(concat! {
        "POST / HTTP/1.1\r\n",
        "Transfer-Encoding: chunked\r\n",
        "\r\n",
        "6\r\n",
        "<BODY>\r\n",
        "0\r\n",
        "\r\n",
    });

    // Parse the request and ensure that unsupported chunked bodies are not returned
    let mut request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    let body = request.read_body_data(6).expect("failed to inspect request body");
    assert!(body.is_none());
}

#[test]
fn body_over_limit_is_rejected() {
    // A request whose body exceeds the configured limit
    let mut source = Source::from(concat! {
        "POST / HTTP/1.1\r\n",
        "Content-Length: 6\r\n",
        "\r\n",
        "<BODY>",
    });

    // Parse the request and ensure that the oversized body is rejected
    let mut request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    let body = request.read_body_data(5);
    let error = body.expect_err("oversized body was accepted");
    assert_eq!(error.error, "HTTP body is too large");
}

#[test]
fn truncated_body_is_rejected() {
    // A request with fewer body bytes than promised
    let mut source = Source::from(concat! {
        "POST / HTTP/1.1\r\n",
        "Content-Length: 7\r\n",
        "\r\n",
        "<BODY>",
    });

    // Parse the request and ensure that the truncated body is rejected
    let mut request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    let body = request.read_body_data(7);
    let error = body.expect_err("truncated body was accepted");
    assert_eq!(error.error, "Truncated HTTP body");
}

#[test]
fn connection_close_is_recognized_inside_a_token_list() {
    // Spam the server with a lot of connection verbs
    let mut source = Source::from(concat! {
        "GET / HTTP/1.1\r\n",
        "Host: example.test\r\n",
        "Connection: keep-alive, Close, upgrade\r\n",
        "\r\n",
    });

    // Ensure the close-header is detected
    let request: Request =
        Request::from_stream(&mut source).expect("failed to parse request").expect("request is missing");
    assert!(request.has_connection_close());
}