isner 0.0.1-alpha.2

A static HTTP server that aims to be minimalistic and fast.
Documentation
use std::io::{BufRead, Write};
use std::sync::Arc;

use anyhow::Error;
use fehler::throws;
use http::status::StatusCode;

use super::parser::{parse_request, Request};

pub type Response = http::response::Response<Vec<u8>>;

pub trait Handler: Sync + Send  {
    fn handle_request(&self, request: &Request) -> Response;
}

fn get_response(reader: &mut dyn BufRead, handler: Arc<impl Handler>) -> Response {
    let res = parse_request(reader);
    match res {
        Ok(req) => handler.handle_request(&req),
        Err(_) => http::Response::builder()
            .status(StatusCode::BAD_REQUEST)
            .body(vec![])
            .unwrap(),
    }
}

#[throws]
fn write_response(resp: &Response, writer: &mut dyn Write) {
    write!(
        writer,
        "HTTP/1.1 {} {}\r\n",
        resp.status().as_str(),
        resp.status().canonical_reason().unwrap_or("UNKNOWN")
    )?;
    let headers = resp.headers();
    for (header_name, value) in headers {
        if !value.is_empty() {
            writer.write_all(header_name.as_ref())?;
            writer.write_all(b": ")?;
            writer.write_all(value.as_bytes())?;
            writer.write_all(b"\r\n")?;
        }
    }
    write!(writer, "Connection: close \r\n")?;
    write!(writer, "\r\n")?;

    writer.write_all(resp.body())?;
}

#[throws]
pub fn handle(
    reader: &mut dyn BufRead,
    writer: &mut dyn Write,
    handler: Arc<impl Handler>,
) -> Response {
    let response = get_response(reader, handler);
    write_response(&response, writer)?;
    response
}

#[cfg(test)]
mod tests {
    use std::io;
    use std::io::BufReader;

    use super::*;

    struct ErrWriter();
    impl Write for ErrWriter {
        fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
            Err(io::Error::new(io::ErrorKind::Other, "always fails"))
        }

        fn flush(&mut self) -> io::Result<()> {
            Err(io::Error::new(io::ErrorKind::Other, "always fails"))
        }
    }

    struct OkEmptyHandler {}
    impl Handler for OkEmptyHandler {
        fn handle_request(&self, _request: &Request) -> Response {
            http::Response::builder()
                .status(StatusCode::OK)
                .header("Content-length", "0")
                .body(vec![])
                .unwrap()
        }
    }

    fn mock_reader(raw_request: &str) -> BufReader<&[u8]> {
        BufReader::new(raw_request.as_bytes())
    }

    #[test]
    fn get_response_ok() {
        let h = Arc::new(OkEmptyHandler {});
        let got = get_response(&mut mock_reader("GET / HTTP/1.0\r\n\r\n"), h);
        assert_eq!(got.status(), StatusCode::OK);
    }

    #[test]
    fn get_response_with_parse_error() {
        let h = Arc::new(OkEmptyHandler {});
        let got = get_response(&mut mock_reader(""), h);
        assert_eq!(got.status(), StatusCode::BAD_REQUEST);
    }

    #[test]
    #[throws]
    fn write_response_ok() {
        let resp = http::Response::builder()
            .status(StatusCode::OK)
            .body(vec![])?;
        let mut writer = Vec::new();
        write_response(&resp, &mut writer)?;
        let got = String::from_utf8(writer)?;
        assert_eq!(
            &got,
            "HTTP/1.1 200 OK\r\nConnection: close \r\n\r\n"
        );
    }

    #[test]
    #[throws]
    fn write_response_err() {
        let resp = http::Response::builder()
            .status(StatusCode::OK)
            .body(vec![])?;
        let mut writer = ErrWriter();
        let got = write_response(&resp, &mut writer);
        assert!(got.is_err(), "Error expected");
    }

    #[test]
    #[throws]
    fn handle_ok() {
        let h = Arc::new(OkEmptyHandler {});

        let raw_request = "GET / HTTP/1.0\r\n\r\n";

        let mut writer = Vec::new();
        let resp = handle(&mut mock_reader(&raw_request), &mut writer, h)?;
        let got = String::from_utf8(writer)?;

        assert_eq!(resp.status(), StatusCode::OK);
        assert_eq!(
            &got,
            "HTTP/1.1 200 OK\r\ncontent-length: 0\r\nConnection: close \r\n\r\n"
        );
    }

    #[test]
    fn handle_write_err() {
        let h = Arc::new(OkEmptyHandler{});

        let raw_request = "GET / HTTP/1.0\r\n\r\n";

        let mut writer = ErrWriter();
        let got = handle(&mut mock_reader(&raw_request), &mut writer, h);
        assert!(got.is_err(), "Error expected");
    }
}