francoisgib_webserver 1.0.3

HTTP Webserver
Documentation
pub mod body;
pub mod parser;
mod test;

use smallvec::SmallVec;

use crate::{config::PRE_ALLOCATED_REQUEST_HEADERS, utils::buffer::Buffer};

use super::{
    headers::{
        Connection, ContentType, HeaderEntry, HttpHeader, HttpHeaderValue, TransferEncoding,
    },
    methods::HttpMethod,
};

/// Represents an HTTP request received by the server.
///
/// Fields:
/// - `method`: HTTP method (GET, POST, etc.)
/// - `uri`: Requested URI
/// - `version`: HTTP version (major, minor)
/// - `headers`: List of headers
/// - `content_type`: Optional content type if parsed
/// - `body_type`: Optional body size/type (fixed, chunked)
/// - `body`: Optional body buffer
/// - `range`: Optional requested byte range
/// - `keep_alive`: Whether the connection should stay alive for more requests
#[derive(Debug)]
pub struct HttpRequest {
    pub method: HttpMethod,
    pub uri: String,
    pub version: (u8, u8),
    pub headers: SmallVec<[HeaderEntry; PRE_ALLOCATED_REQUEST_HEADERS]>,
    pub content_type: Option<ContentType>,
    pub body_type: Option<BodyType>,
    pub body: Option<Buffer>,
    pub range: Option<(u16, u16)>,
    pub keep_alive: bool,
}

/// Defines the body type of a request.
/// Chunked is for chunked transfer encoding,
/// Fixed when content-length header is provided.
#[derive(Debug, PartialEq)]
pub enum BodyType {
    Chunked,
    Fixed(u64),
}

impl HttpRequest {
    /// Creates a new `HttpRequest`.
    pub fn new(
        method: HttpMethod,
        uri: String,
        version: (u8, u8),
        headers: SmallVec<[HeaderEntry; PRE_ALLOCATED_REQUEST_HEADERS]>,
        body: Option<Buffer>,
    ) -> Self {
        Self {
            method,
            uri,
            version,
            headers,
            content_type: None,
            body_type: None,
            body,
            keep_alive: false,
            range: None,
        }
    }

    /// Processes headers and fills specific fields to handle faster afterwards.
    pub fn process_headers(&mut self) {
        for header_entry in &self.headers {
            let (header, value) = (&header_entry.name, &header_entry.value);
            match (header, value) {
                (HttpHeader::ContentLength, HttpHeaderValue::ContentLength(length)) => {
                    self.body_type = Some(BodyType::Fixed(*length));
                }
                (HttpHeader::TransferEncoding, HttpHeaderValue::TransferEncoding(encoding)) => {
                    if encoding == &TransferEncoding::Chunked {
                        self.body_type = Some(BodyType::Chunked)
                    }
                }
                (HttpHeader::ContentType, HttpHeaderValue::ContentType(content_type)) => {
                    self.content_type = Some(*content_type);
                }
                (HttpHeader::Connection, HttpHeaderValue::Connection(connection)) => {
                    match connection {
                        Connection::KeepAlive => self.keep_alive = true,
                        Connection::Close => self.keep_alive = false,
                    }
                }
                (HttpHeader::Range, HttpHeaderValue::Range(range)) => self.range = Some(*range),
                _ => {}
            }
        }
    }

    /// Finds a mutable reference to a specific header if it exists.
    pub fn find_header(&mut self, header: HttpHeader) -> Option<&mut HeaderEntry> {
        self.headers.iter_mut().find(|entry| entry.name == header)
    }
}

impl ToString for HttpRequest {
    /// Converts the request into a full HTTP request string.
    fn to_string(&self) -> String {
        let headers_str = self
            .headers
            .iter()
            .map(|header_entry| format!("{}\r\n", header_entry.to_string()))
            .collect::<Vec<String>>()
            .concat();

        let body = match &self.body {
            Some(body) => body.to_string(),
            None => String::new(),
        };

        format!(
            "{} {} HTTP/{}.{}\r\n{}\r\n{}",
            self.method, self.uri, self.version.0, self.version.1, headers_str, body
        )
    }
}