francoisgib_webserver 1.0.3

HTTP Webserver
Documentation
mod test;

use smallvec::SmallVec;

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

use super::{
    headers::{HeaderEntry, HttpHeader},
    status::HttpStatus,
};

/// Represents an HTTP response.
///
/// This struct contains:
/// - HTTP version (e.g., (1, 1) for HTTP/1.1)
/// - HTTP status code
/// - Response headers
/// - Optional body
/// - Optional byte range for partial responses
#[derive(Debug)]
pub struct HttpResponse {
    pub version: (u8, u8),
    pub status: HttpStatus,
    pub headers: SmallVec<[HeaderEntry; PRE_ALLOCATED_RESPONSE_HEADERS]>,
    pub body: Option<Buffer>,
    pub range: Option<(u16, u16)>,
}

impl HttpResponse {
    /// Creates a new `HttpResponse` with the given parameters.
    pub fn new(
        version: (u8, u8),
        status: HttpStatus,
        headers: SmallVec<[HeaderEntry; PRE_ALLOCATED_RESPONSE_HEADERS]>,
        body: Option<Buffer>,
        range: Option<(u16, u16)>,
    ) -> Self {
        Self {
            version,
            status,
            headers,
            body,
            range,
        }
    }

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

    pub fn to_bytes(self) -> Vec<u8> {
        let mut response = Vec::new();

        response.extend_from_slice(
            format!(
                "HTTP/{}.{} {} {}\r\n",
                self.version.0,
                self.version.1,
                self.status as u16,
                self.status.to_string(),
            )
            .as_bytes(),
        );

        for header in &self.headers {
            response.extend_from_slice(header.to_string().as_bytes());
            response.extend_from_slice(b"\r\n");
        }

        response.extend_from_slice(b"\r\n");

        if let Some(body) = self.body {
            let vec = body.to_bytes();
            if let Some((start, end)) = self.range {
                response.extend_from_slice(&vec[start as usize..end as usize]);
            } else {
                response.extend_from_slice(&vec);
            }
        }
        response
    }
}

impl ToString for HttpResponse {
    /// Serializes the `HttpResponse` into a full HTTP response string.
    ///
    /// Format:
    /// ```ignore
    /// HTTP/version_major.version_minor status_code status_text
    /// Header-Name: value
    /// ...
    ///
    /// body (if any)
    /// ```
    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.version.0,
            self.version.1,
            self.status as u16,
            self.status.to_string(),
            headers_str,
            body
        )
    }
}