Skip to content

best-collaborators/webserv

Repository files navigation

This project has been created as part of the 42 curriculum by rzvir, kvalerii

Description

Webserv is a custom-built HTTP server designed to deepen understanding of how the web works at a fundamental level. The project’s primary goal is to recreate the core functionality of a real-world web server - similar to Nginx or Apache HTTP Server, using low-level programming concepts, typically in C or C++.

The server handles HTTP requests from clients (such as web browsers), processes them according to defined configuration rules, and returns appropriate responses, including static files or dynamically generated content. It supports essential features such as request parsing, response generation, error handling, and configuration management.

Through this project, developers gain practical experience with networking, socket programming, concurrency, and protocol implementation.

Instructions

To compile, install, and run Webserv, follow the steps below. These may vary slightly depending on your implementation, but the general workflow remains the same.

Compilation

The project is typically compiled using a Makefile.

make

This command builds the executable (usually named webserv).

Clean compiled files with:

make clean

Remove all generated files:

make fclean

Installation

No formal installation is required. Once compiled, the executable can be run directly from the project directory.

Make sure it has execution permissions:

chmod +x webserv

Execution

Run the server by providing a configuration file:

./webserv config.conf

Configuration

The configuration file defines server behavior, including:

  • Port and host settings
  • Server names
  • Routes and locations
  • Error pages
  • Index files for both Location and Server block
  • Root directories
  • Allowed HTTP methods
  • CGI configurations

Configuration Format

The server uses a YAML-like configuration format. Below is a full example:

server:
    listen:
        127.0.0.1:8000

    error_pages:
        400: /BadRequestPage.html

    root: data
    index: /another/index.html

    locations:
        - path: /
            allowed_methods: "GET"

        - path: /old
            allowed_methods: "GET"
            redirect: 301 /new

        - path: /new
            allowed_methods: "GET"
            autoindex: on

        - path: /upload_video
            max_body_size: 1000000000
            allowed_methods: "POST"
            index: /another/0-upload.txt

        - path: /delete_video
            allowed_methods: "DELETE"

        - path: /files
            allowed_methods: "GET"
            index: my_index.html
            root: /files_directory
			autoindex: on

    cgi:
        - pass_to: /usr/bin/node
            extensions: ".js"

Usage

Once the server is running, access it via a browser:

http://localhost:8000

Or test using curl:

curl http://localhost:8000

Requirements

  • Unix-based system (Linux or macOS recommended)
  • C/C++ compiler (e.g., g++)
  • make utility

Resources

References

The following resources were used throughout the development of Webserv:

These materials provided essential knowledge about HTTP protocol behavior, server architecture, networking, and low-level system calls.


Use of AI

AI tools (such as ChatGPT) were used as supportive resources during the project in the following ways:

  • Testing: Generating edge cases and example HTTP requests to validate server behavior.
  • Documentation: Assisting in writing and structuring project documentation.
  • Utility Functions: Helping implement small helper functions and improve code readability.
  • Refactoring: Suggesting cleaner, more maintainable code structures.
  • Learning & Architecture: Explaining complex concepts related to server design, C++ patterns, and overall architecture.

AI was used strictly as an aid for understanding and productivity, while all core logic, design decisions, and implementations were developed and validated manually.

Additional Sections

Features

  • HTTP/1.1 request handling
  • Support for multiple server blocks
  • Configurable routes and locations
  • Static file serving
  • File uploads handling
  • Custom error pages
  • CGI execution (e.g. with Node.js)
  • Autoindex (directory listing)
  • Method restriction (GET, POST, DELETE)
  • Redirections

Usage Examples

Basic request using a browser

Using curl

curl -X GET http://127.0.0.1:8000/

POST request with data

curl -X POST http://127.0.0.1:8000/post_body -d "hello world"
curl -X DELETE http://127.0.0.1:1111/post_body/file.txt

Technical Choices

  • Language: C++
  • Standard: C++17
  • I/O Multiplexing: epoll()
  • Architecture:
  • Event-driven server design
  • Non-blocking sockets
  • Separation between parsing, routing, and response generation

Configuration Parsing:

  • Custom parser for a YAML-like configuration format

Error Handling:

  • HTTP-compliant status codes
  • Custom error page support

Project Structure

.
├── data/ # Static files, assets, error pages, CGI scripts
│ ├── cgi-bin/
│ ├── html_errors/
│ └── YoupiBanane/
│
├── includes/ # Header files
│ ├── cgi/
│ ├── execution/
│ └── http/
│
├── sources/ # Source files (.cpp)
│ ├── cgi/
│ ├── execution/
│ └── http/
│
├── tests/ # Test cases (config, HTTP, CGI, etc.)
│ ├── config_tests/
│ ├── get/
│ ├── post/
│ └── malformed/
│
├── scripts/ # Helper scripts (testing, monitoring)
├── docs/ # Documentation (architecture, notes)
├── config/ # Configuration files
│ └── webserv.conf
│
├── Makefile
├── README.md
└── webserv # Compiled binary

Webserv — Architecture & Execution Flow

Complete project diagram: class relationships, execution flow, and state machines.


1. Class Relationships & Architecture

All classes organized into four layers: Configuration, Socket/Server, HTTP Parser, and Utility/Enum.

classDiagram
    direction TB

    %% ═══════════════════════════════════════════
    %% CONFIGURATION LAYER
    %% ═══════════════════════════════════════════

    class ConfigurationFileParser {
        -string _filename
        -ServerBlock _current_server_block
        -server_block_map& _server_blocks
        +ConfigurationFileParser(filename, server_blocks)
        +parse() e_parse_result
        -_parseAllServerBlocks()
        -_parseSingleServerBlock()
        -_dispatchServerDirective()
        -_handleListenDirective()
        -_handleLocationsDirective()
        -_handleCGIDirective()
        -_validateServerBlocks()
    }

    class ServerBlock {
        +string _server_name
        +ListenData _listen_data
        +map~HttpStatus__e_code,string~ _error_pages
        +size_t _max_body_size
        +path _root
        +optional~string~ _index
        +optional~vector~Location~~ _locations
        +optional~vector~CGIPath~~ _cgi
        +bitset~8~ _assigned_fields
    }

    class Location {
        -path path
        -path root
        -bool autoindex
        -string default_file
        -optional~HttpMethodRegistry~ methods_registry
        -HttpPage return_page
        +getters/setters()
        +to_string() string
    }

    class CGIPath {
        +string path
        +unordered_map~string,string~ pass_to
        +string extension
        +optional~HttpMethodRegistry~ methods_registry
    }

    class ListenData {
        +short port
        +string ip_address
        +operator==() bool
    }

    class ListenDataHash {
        +operator()(ListenData) size_t
    }

    class HttpPage {
        +path path
        +HttpStatus__e_code status_code
    }

    ConfigurationFileParser ..> ServerBlock : creates
    ServerBlock *-- ListenData
    ServerBlock *-- "0..*" Location
    ServerBlock *-- "0..*" CGIPath
    ServerBlock ..> HttpPage : error_pages
    Location *-- HttpPage : return_page
    Location *-- HttpMethodRegistry
    CGIPath *-- HttpMethodRegistry
    ListenDataHash ..> ListenData : hashes

    %% ═══════════════════════════════════════════
    %% SOCKET / SERVER LAYER
    %% ═══════════════════════════════════════════

    class Server {
        -CONNECTION_TIMEOUT = 5s
        -CGI_TIMEOUT = 10s
        -connections_map _connections
        -cgi_pipe_fds_set _cgi_pipe_fds
        -fd_to_connection_map _fd_to_connection
        -pid_to_connection_map _pid_to_connection
        -listeners_map _listeners
        -Poller _poller
        -ChildSignalHandler _childHandler
        -server_blocks_map _server_blocks
        +Server(server_block_map)
        +run() void
        -_acceptConnection(fd)
        -_handleEvent(epoll_event)
        -_handleTimeouts()
        -_handleConnectionEvent(IoEvent, fd)
        -_handleCGIEvent(IoEvent, Connection)
        -_handleFinishedChildren()
        -_registerConnectionCGI(Connection, CGIOperation)
        -_unregisterConnectionCGI(Connection, CGIOperation)
        -_closeConnection(fd)
    }

    class Poller {
        -MAX_EVENTS = 1024
        -TIMEOUT = 1000ms
        -int _epoll_fd
        -vector~epoll_event~ _events
        +Poller()
        +wait() int
        +add(fd, events)
        +mod(fd, events)
        +del(fd)
        +getEvent(index) epoll_event
    }

    class Listener {
        -Socket _socket
        +Listener(ip, port)
        +accept() Socket
        +getFD() int
        -getAddresses() AddrInfoPtr
        -setupSocket(addrinfo)
    }

    class Socket {
        -int _fd
        +Socket()
        +Socket(fd)
        +create(addrinfo)
        +setAddressReuse()
        +setDualStack()
        +bind(addrinfo)
        +listen()
        +accept() Socket
        +getFD() int
        +isHealthy() bool
        -_setNonBlocking()
        -_safeClose()
    }

    class Connection {
        -chrono_time _last_activity
        -optional~chrono_time~ _cgi_start_time
        -int _fd
        -Socket _socket
        -optional~CGIHandler~ _cgi_handler
        -ServerBlock* _server_block
        -BufferManager _buffer_manager
        -HttpRequestReader _request_reader
        -HttpResponseWriter _response_writer
        -bool _response_formed
        -bool _headers_sent_to_client
        -ssize_t _sent_bytes
        -ssize_t _read_bytes
        +Connection(ServerBlock*, Socket&&)
        +processConnectionEvents(events) IoResult
        +processCGIEvents(events) IoResult
        +onCGIOutputReady() EventAction
        +onChildProcessExited() EventAction
        +getCGIPID() int
        +getCGIPipe(CGIOperation) int
        +closeCGIPipe(CGIOperation)
        +abortCGI()
        -_receiveData() IoEvent
        -_sendData() IoEvent
        -_formResponse()
        -_formCGIResponse()
        -_tryInitCGI() IoEvent
    }

    class PipeFD {
        -int _fd
        +PipeFD()
        +PipeFD(fd)
        +release() int
        +reset()
        +get() int
        +~PipeFD()
    }

    class ChildSignalHandler {
        -int _sig_fd
        +ChildSignalHandler()
        +getFD() int
        +handleFinishedChildren() vector~pid_t~
        -_readSignalFD()
    }

    class CGIHandler {
        -PIPE_BUFFER_SIZE = 65536
        -int _pid
        -PipeFD _write_fd
        -PipeFD _read_fd
        -size_t _content_length
        -size_t _header_end_offset
        -bool _headers_parsed
        -size_t _write_offset
        -bool _is_output_ready
        -bool _is_child_dead
        -string _recv_buffer
        +CGIHandler(CGIConfig)
        +writeToCGI(buffer) IoEvent
        +readFromCGI() IoEvent
        +isResponseReady() bool
        +onCGIOutputReady() EventAction
        +onChildProcessExited() EventAction
        +getWriteFD() int
        +getReadFD() int
        +closeWritePipe()
        +closeReadPipe()
    }

    class CGIExecutor {
        -int _pid
        -PipeFD _write_pipe[2]
        -PipeFD _read_pipe[2]
        +CGIExecutor(CGIConfig)
        +getPID() int
        +releaseWriteFD() int
        +releaseReadFD() int
        -_initPipes()
        -_initFork()
        -_initChild(CGIConfig)
        -_initChildPipes()
        -_restoreDefaultSignalMask()
        -_generateEnvp(CGIConfig) vector
        -_generateArgv(CGIConfig) vector
    }

    class CGIRequestConfig {
        +buildConfig(headers, file)$ CGIConfig
        -addTargetEnv(envVars, target)$
        -toCGIHeaderName(name)$ string
    }

    Server *-- Poller
    Server *-- ChildSignalHandler
    Server *-- "0..*" Connection : _connections map
    Server *-- "1..*" Listener : _listeners map
    Server o-- ServerBlock : _server_blocks
    Server ..> CGIOperation : uses
    Server ..> IoResult : reads
    Server ..> EventAction : reads
    Listener *-- Socket
    Connection *-- Socket
    Connection *-- BufferManager
    Connection *-- HttpRequestReader
    Connection *-- HttpResponseWriter
    Connection o-- "0..1" CGIHandler : optional
    Connection o-- ServerBlock : references
    Connection ..> IoResult : returns
    Connection ..> CGIRequestConfig : calls buildConfig
    CGIHandler *-- "2" PipeFD : read + write
    CGIHandler ..> CGIExecutor : creates in ctor
    CGIHandler ..> EventAction : returns
    CGIExecutor *-- "4" PipeFD : pipe pairs
    CGIExecutor ..> CGIConfig : reads

    %% ═══════════════════════════════════════════
    %% HTTP PARSER LAYER
    %% ═══════════════════════════════════════════

    class HttpMessage {
        #unordered_map~string,string~ _headers
        #string _body
        +get_headers() map
        +get_header_value(key) string
        +set_header_value(key, value)
        +get_content_length() size_t
        +append_body_value(addition)
        +get_body() string
    }

    class Request {
        -HttpMethod__e_code _method
        -string _version
        -string _uri
        -HttpStatus__e_code _status_code
        -ChunkHandler _chunk_handler
        -bool _is_cgi
        -ServerBlock* _server_block
        -File _file
        +get_method() HttpMethod_e_code
        +get_status_code() HttpStatus_e_code
        +set_status_code(code)
        +getFile() File
        +getBodyStatus() RequestType
        +isCGI() bool
        +reset()
    }

    class Response {
        -HttpStatus__e_code _status_code
        -HttpMethod__e_code _method
        -File _file
        -size_t _response_length
        -streampos _content_length
        -size_t _bytes_sent
        -ssize_t _bytes_read
        -size_t _buffer = 10240
        -bool _is_default_page
        -string _header_str
        +form_response(status, method, file, body, isCGI) string
        +get_total_response_length() size_t
        +getResponseData() char*
        +consume_body(bytes)
        +read_body_partially()
        +set_content_type(filename)
    }

    class HttpRequestReader {
        -Request _request
        -size_t _stored_body_bytes
        -ReaderState _curr_state
        +HttpRequestReader(ServerBlock*)
        +read(buffer, bytes) ReaderState
        +reset()
        +getFile() File
        +getMethod() HttpMethod_e_code
        +getHeaders() map
        +getStatusCode() HttpStatus_e_code
        +setStatusCode(code)
        -_processHeader(buffer) ReaderState
        -_processBody(buffer, bytes) ReaderState
        -_checkHeaderState(buffer) HeaderState
        -_checkBodyState(buffer, bytes) BodyState
    }

    class RequestParser {
        -ParseContext _parse_context
        +RequestParser(Request, buffer)
        +parse_headers()
        +parse_body()
    }

    class RequestLineValidator {
        -ParseContext& _parse_context
        +RequestLineValidator(ParseContext)
        +parse()
        -_isValidRequestLine(buffer) bool
        -_isValidHttpVersion() bool
        -_isValidUriLength() bool
        -_isMethodAllowed() bool
        -_isRequestTargetInConfigFile(target) e_parse_result
    }

    class HttpHeaderParser {
        -ParseContext& _parse_context
        +HttpHeaderParser(ParseContext)
        +parse()
        -_validateRequestHeaders() HttpStatus_e_code
        -_isValidHeader(buffer) bool
        -_contentLengthValidation() HttpStatus_e_code
    }

    class HttpBodyParser {
        -ParseContext& _parse_context
        -vector~MultipartFormData~ _multipartFormDatas
        +HttpBodyParser(ParseContext)
        +parse()
        -_handleChunked()
        -_handleMultipart()
        -_handleRawUpload()
    }

    class BufferManager {
        -READ_BUFFER_SIZE = 32768
        -string _read_buffer
        -char* _recv_buffer
        +BufferManager()
        +getRecvBuffer() char*
        +getReceiveBufferSize() size_t
        +append(bytes)
        +consume(bytes)
        +clear()
        +getBuffer() string
    }

    class HttpResponseWriter {
        -Response _response
        +formResponse(status, method, file, buffer, isCGI)
        +write()
        +totalLength() size_t
        +currResponseLength() size_t
        +getResponseData() char*
        +consume(bytes)
    }

    class TransferEncodingChunkedParser {
        -ParseContext& _parse_context
        -size_t _chunk_size
        +TransferEncodingChunkedParser(ParseContext)
        +parse()
        -_tryGetNewChunk(buffer) bool
        -_isFinalChunk(buffer) bool
    }

    class MultipartDataParser {
        -vector~MultipartFormData~& _multipartFormDatas
        -ParseContext& _parse_context
        -BoundaryContext _boundary_context
        +MultipartDataParser(vector, ParseContext)
        +parse()
        -_getMultipartFormBoundary() string
        -_isValidMultipartForm() bool
        -_createMultipartDataFormFiles()
    }

    class MultipartFormData {
        -string _content_type
        -string _content
        -string _name
        -string _filename
        +get/set content_type()
        +get/set name()
        +get/set filename()
        +get/set content()
        +append_content(data, size)
    }

    class FileUploadHandler {
        -string _upload_dir
        -string _filename
        -Request& _request
        -size_t _uploaded_files_count$
        +FileUploadHandler(dir, Request, filename)
        +write_into_file(body)
        -_getFileName()
    }

    class ChunkHandler {
        -string _current_chunk
        -size_t _expected_size
        -bool _received
        +getChunk() string
        +setExpectedSize(size)
        +append_to(body)
        +finalize()
        +reset()
    }

    class File {
        -string _relative_path
        -string _full_filename
        -bool _is_dir
        -bool _autoindex
        -string _extension
        -optional~string~ _pass_to
        -HttpPage _return_page
        -HttpMethodRegistry _method_registry
        -size_t _max_body_size
        +getters/setters()
    }

    class ListingGenerator {
        +getListingPage(File)$ string
        -_isPathValid(path)$ bool
        -_getPathEntries(path)$ string
        -_escapeHtml(s)$ string
    }

    HttpMessage <|-- Request : extends
    HttpMessage <|-- Response : extends
    Request *-- ChunkHandler
    Request *-- File
    Request o-- ServerBlock : references
    File *-- HttpPage : return_page
    File *-- HttpMethodRegistry
    HttpRequestReader *-- Request
    HttpResponseWriter *-- Response
    RequestParser *-- ParseContext
    RequestParser ..> RequestLineValidator : creates
    RequestParser ..> HttpHeaderParser : creates
    RequestParser ..> HttpBodyParser : creates
    RequestLineValidator ..> File : resolves
    RequestLineValidator ..> Location : matches
    RequestLineValidator ..> CGIPath : matches
    HttpBodyParser ..> TransferEncodingChunkedParser : creates
    HttpBodyParser ..> MultipartDataParser : creates
    HttpBodyParser ..> FileUploadHandler : creates
    MultipartDataParser *-- "0..*" MultipartFormData
    MultipartDataParser ..> FileUploadHandler : creates
    Response ..> ListingGenerator : autoindex
    Response ..> File : reads

    %% ═══════════════════════════════════════════
    %% UTILITY & ENUM LAYER
    %% ═══════════════════════════════════════════

    class HttpMethod {
        <<enumeration>>
        OPTIONS
        GET
        POST
        DELETE
        INVALID
        +toString(code)$ string
        +fromString(str)$ e_code
        +hasBody(code)$ bool
    }

    class HttpStatus {
        <<enumeration>>
        OK = 200
        CREATED = 201
        NO_CONTENT = 204
        MOVED_PERMANENTLY = 301
        BAD_REQUEST = 400
        NOT_FOUND = 404
        METHOD_NOT_ALLOWED = 405
        LENGTH_REQUIRED = 411
        PAYLOAD_TOO_LARGE = 413
        URI_TOO_LONG = 414
        INTERNAL_SERVER_ERROR = 500
        SERVICE_UNAVAILABLE = 503
        GATEWAY_TIMEOUT = 504
        +get_status_code_name(code)$ string
        +is_bad(code)$ bool
        +is_good(code)$ bool
        +is_redirect(code)$ bool
    }

    class HttpContentType {
        <<enumeration>>
        TEXT_HTML
        TEXT_CSS
        TEXT_PLAIN
        APPLICATION_JSON
        IMAGE_PNG
        IMAGE_JPEG
        +get_content_type_by_extension(ext)$ string
    }

    class HttpMethodRegistry {
        -bitset~8~ _allowed_methods
        +isAllowed(method) bool
        +setAllowedMethod(method)
        +disableAllowedMethod(method)
        +to_string() string
    }

    class Logger {
        <<static>>
        +DEBUG$
        +INFO$
        +WARNING$
        +ERROR$
        +CRITICAL$
        +displayLog(level, msg, module)$
    }

    class HttpRegexPatterns {
        <<static>>
        +METHOD()$ regex
        +FILEPATH()$ regex
        +VERSION()$ regex
        +HEADER()$ regex
        +BOUNDARY()$ regex
        +CGI_VALID_PATH()$ regex
    }

    class PercentEncoder {
        +percent_encoding(buffer)$ string
    }

    class RegexMatcher {
        +get_regex_value(line, regex)$ string
    }

    class Trimmer {
        +trim(s)$ string
        +ltrim(s)$ string
        +rtrim(s)$ string
    }

    class RequestStringUtils {
        +cut_after_new_line(line)$ string
        +transform_to_lower(str)$ string
    }

    class IoResult {
        +IoSource source
        +IoEvent event
    }

    class IoSource {
        <<enumeration>>
        Connection
        CGI
    }

    class IoEvent {
        <<enumeration>>
        Pending
        Closed
        Error
        Sent
        Received
        Init
        Done
    }

    class EventAction {
        <<enumeration>>
        NoAction
        EnableOutput
    }

    class CGIOperation {
        <<enumeration>>
        READ
        WRITE
    }

    class CGIConfig {
        +string executable
        +string scriptPath
        +vector~string~ envVariables
    }

    class ParseContext {
        +Request& request
        +string& raw_bits
    }

    class ReaderState {
        <<enumeration>>
        AwaitingHeaders
        AwaitingBody
        Complete
        Error
        CGI
    }

    class HeaderState {
        <<enumeration>>
        Complete
        Incomplete
        ContainsBody
        Error
        Redirect
        CGI
    }

    class BodyState {
        <<enumeration>>
        Incomplete
        Complete
        Overflow
        Chunked
        CGI
        Invalid
    }

    class RequestType {
        <<enumeration>>
        NO_BODY
        RAW_BODY
        MULTIPART
        CHUNKED
        CGI
    }

    IoResult *-- IoSource
    IoResult *-- IoEvent
    Request ..> HttpMethod : uses
    Request ..> HttpStatus : uses
    Response ..> HttpStatus : uses
    Response ..> HttpContentType : uses
    RequestLineValidator ..> PercentEncoder : decodes URI
    RequestLineValidator ..> HttpRegexPatterns : validates
    HttpHeaderParser ..> RequestStringUtils : parses
    HttpRequestReader ..> ReaderState : tracks state
    HttpRequestReader ..> HeaderState : internal
    HttpRequestReader ..> BodyState : internal
    Request ..> RequestType : body classification
    ParseContext ..> Request : references
Loading

2. Execution Flow

Complete lifecycle: startup → event loop → request processing → CGI → response → cleanup.

flowchart TB
    subgraph STARTUP["Phase 1: Startup"]
        direction TB
        A1["main(argc, argv)"] --> A2["Validate config file argument"]
        A2 --> A3["ConfigurationFileParser::parse()"]
        A3 --> A4["Parse server: blocks<br/>→ ServerBlock map<br/>(ListenData → ServerBlock)"]
        A4 --> A5["Install signal handlers<br/>SIGPIPE → SIG_IGN<br/>SIGINT → set g_running=false"]
        A5 --> A6["Server(server_blocks_map)"]
        A6 --> A7["Server::run()"]
    end

    subgraph INIT["Phase 2: Server Initialization (inside run)"]
        direction TB
        B1["For each ServerBlock:"] --> B2["Create Listener(ip, port)<br/>→ getaddrinfo() → socket()<br/>→ setsockopt(SO_REUSEADDR)<br/>→ bind() → listen()"]
        B2 --> B3["Store ListenerEntry<br/>in _listeners map"]
        B3 --> B4["Poller::add(listener_fd, EPOLLIN)"]
        B4 --> B5["Poller::add(childHandler.getFD(), EPOLLIN)<br/>(signalfd for SIGCHLD)"]
    end

    subgraph EVENTLOOP["Phase 3: Event Loop (while g_running)"]
        direction TB
        C1["_handleTimeouts()"] --> C2["Poller::wait(1000ms)<br/>epoll_wait() → count events"]
        C2 --> C3{"For each event:<br/>What FD type?"}

        C3 -->|"Listener FD"| D1["_acceptConnection()"]
        C3 -->|"Signal FD<br/>(SIGCHLD)"| E1["_handleFinishedChildren()"]
        C3 -->|"Connection/<br/>CGI Pipe FD"| F1["_handleEvent()"]
    end

    subgraph ACCEPT["Accept New Connection"]
        direction TB
        D1 --> D2["Listener::accept()<br/>→ Socket(new_fd)<br/>→ set non-blocking"]
        D2 --> D3["Create Connection(<br/>ServerBlock*, Socket&&)"]
        D3 --> D4["Poller::add(fd, EPOLLIN)"]
        D4 --> D5["Store in _connections,<br/>_fd_to_connection maps"]
    end

    subgraph DISPATCH["Event Dispatch (_handleEvent)"]
        direction TB
        F1 --> F2{"Is FD in<br/>_cgi_pipe_fds?"}
        F2 -->|Yes| F3["conn.processCGIEvents(events)<br/>→ IoResult CGI, event"]
        F2 -->|No| F4["conn.processConnectionEvents(events)<br/>→ IoResult Connection, event"]

        F3 --> F5{"IoResult.source?"}
        F4 --> F5
        F5 -->|Connection| F6["_handleConnectionEvent()"]
        F5 -->|CGI| F7["_handleCGIEvent()"]
    end

    subgraph CONNEV["Connection Event Handling"]
        direction TB
        F6 --> G1{"IoEvent?"}
        G1 -->|Error/Closed| G2["_closeConnection(fd)"]
        G1 -->|Received| G3["Poller::mod(fd,<br/>EPOLLIN|EPOLLOUT)<br/>— enable sending"]
        G1 -->|Sent| G4["Poller::mod(fd, EPOLLIN)<br/>— ready for next request"]
    end

    subgraph CGIEV["CGI Event Handling"]
        direction TB
        F7 --> H1{"IoEvent?"}
        H1 -->|Init| H2["Register write pipe<br/>to epoll (EPOLLOUT)<br/>Store PID→Connection"]
        H1 -->|Sent| H3["Unregister write pipe<br/>Register read pipe<br/>(EPOLLIN)"]
        H1 -->|Done| H4["Unregister read pipe<br/>onCGIOutputReady()<br/>Enable output if ready"]
        H1 -->|Error| H5["Unregister both pipes<br/>abortCGIWithError()<br/>Enable output"]
    end

    subgraph RECEIVE["Request Receive Flow (EPOLLIN on connection)"]
        direction TB
        I1["recv(fd, buffer, 32768)"] --> I2["BufferManager::append(bytes)"]
        I2 --> I3["HttpRequestReader::read()"]
        I3 --> I4{"ReaderState?"}
        I4 -->|AwaitingHeaders| I5["Continue receiving<br/>return Pending"]
        I4 -->|AwaitingBody| I5
        I4 -->|Complete| I6["return IoResult<br/>Connection, Received"]
        I4 -->|Error| I6
        I4 -->|CGI| I7["_tryInitCGI()<br/>return IoResult<br/>CGI, Init"]
    end

    subgraph PARSE["Request Parsing Pipeline (inside HttpRequestReader)"]
        direction TB
        J1["_processHeader(buffer)"] --> J2["Find CRLFCRLF<br/>header terminator"]
        J2 --> J3["RequestParser::parse_headers()"]
        J3 --> J4["RequestLineValidator::parse()<br/>Extract: METHOD URI HTTP/VERSION<br/>Decode URI with PercentEncoder<br/>Route: _isRequestTargetInConfigFile()"]
        J4 --> J5["HttpHeaderParser::parse()<br/>Validate each header<br/>Check Content-Length<br/>Check Transfer-Encoding"]
        J5 --> J6{"Has body?<br/>— POST"}
        J6 -->|No| J7["→ Complete"]
        J6 -->|Yes| J8["→ AwaitingBody"]
        J8 --> J9["_processBody(buffer)"]
        J9 --> J10["RequestParser::parse_body()"]
        J10 --> J11["HttpBodyParser::parse()"]
        J11 --> J12{"RequestType?"}
        J12 -->|RAW_BODY| J13["FileUploadHandler<br/>write_into_file()"]
        J12 -->|MULTIPART| J14["MultipartDataParser<br/>parse boundaries<br/>→ FileUploadHandler"]
        J12 -->|CHUNKED| J15["TransferEncoding<br/>ChunkedParser<br/>parse hex chunks"]
    end

    subgraph ROUTING["URI Routing (_isRequestTargetInConfigFile)"]
        direction TB
        K1["Request URI"] --> K2{"Directory redirect?<br/>missing trailing /"}
        K2 -->|Yes| K3["301 Moved<br/>Permanently"]
        K2 -->|No| K4{"Match Location<br/>blocks?"}
        K4 -->|Yes| K5["Set File from<br/>Location config<br/>root, index, autoindex"]
        K4 -->|No| K6{"Match CGI<br/>paths?"}
        K6 -->|Yes| K7["Set File with<br/>pass_to executor<br/>Mark as CGI"]
        K6 -->|No| K8["Fallback to<br/>ServerBlock root<br/>+ request path"]
        K5 --> K9{"Location has<br/>return directive?"}
        K9 -->|Yes| K10["Redirect response"]
        K9 -->|No| K11["Serve file/dir"]
    end

    subgraph CGIFLOW["CGI Execution Flow"]
        direction TB
        L1["_tryInitCGI()"] --> L2["cgi::buildConfig()<br/>Build env vars:<br/>REQUEST_METHOD, QUERY_STRING<br/>CONTENT_TYPE, SCRIPT_NAME..."]
        L2 --> L3["CGIHandler(config)"]
        L3 --> L4["CGIExecutor:<br/>pipe(stdin), pipe(stdout)<br/>fork()"]
        L4 --> L5{"Parent or Child?"}
        L5 -->|Child| L6["dup2 pipes → stdin/stdout<br/>restore signal mask<br/>execve(executable, argv, envp)"]
        L5 -->|Parent| L7["Store PipeFDs<br/>write to child stdin<br/>read from child stdout"]
        L7 --> L8["writeToCGI(request_body)<br/>via write pipe → EPOLLOUT"]
        L8 --> L9["Close write pipe<br/>Switch to reading"]
        L9 --> L10["readFromCGI()<br/>via read pipe → EPOLLIN<br/>up to 64KB chunks"]
        L10 --> L11["Parse CGI headers<br/>Check Content-Length"]
        L11 --> L12["Child exits →<br/>SIGCHLD → signalfd<br/>→ onChildProcessExited()"]
        L12 --> L13{"Both ready?<br/>_is_output_ready AND<br/>_is_child_dead"}
        L13 -->|Yes| L14["EventAction::<br/>EnableOutput"]
        L13 -->|No| L15["Wait for<br/>other condition"]
    end

    subgraph RESPONSE["Response Generation and Sending"]
        direction TB
        M1["_formResponse()"] --> M2{"CGI request?"}
        M2 -->|Yes| M3["_formCGIResponse()<br/>Parse CGI output headers<br/>CGIValidator validates<br/>form_response(isCGI=true)"]
        M2 -->|No| M4{"Response type?"}
        M4 -->|Autoindex| M5["ListingGenerator::<br/>getListingPage()"]
        M4 -->|Redirect| M6["301/302 response<br/>with Location header"]
        M4 -->|Error| M7["Error page HTML<br/>custom or default"]
        M4 -->|File| M8["Read file in 10KB chunks<br/>set Content-Type<br/>by extension"]
        M3 --> M9["HttpResponseWriter"]
        M5 --> M9
        M6 --> M9
        M7 --> M9
        M8 --> M9
        M9 --> M10["_sendData()<br/>send(fd, data, len)"]
        M10 --> M11{"All bytes sent?"}
        M11 -->|No| M12["Partial send<br/>return Pending<br/>wait for EPOLLOUT"]
        M11 -->|Yes| M13["Reset connection<br/>for keep-alive<br/>return Sent"]
    end

    subgraph TIMEOUT["Timeout Handling (_handleTimeouts)"]
        direction TB
        N1["Iterate all connections"] --> N2{"Connection idle<br/>> 5 seconds?"}
        N2 -->|Yes| N3["_closeConnection(fd)"]
        N2 -->|No| N4{"CGI running<br/>> 10 seconds?"}
        N4 -->|Yes| N5["kill(pid, SIGKILL)"]
        N5 --> N6{"Headers already<br/>sent to client?"}
        N6 -->|Yes| N7["Close connection<br/>cant send error"]
        N6 -->|No| N8["abortCGI()<br/>504 Gateway Timeout<br/>Enable output"]
        N4 -->|No| N9["Skip — still valid"]
    end

    subgraph CHILDREAP["Child Process Reaping"]
        direction TB
        E1 --> E2["ChildSignalHandler::<br/>handleFinishedChildren()"]
        E2 --> E3["read(signalfd)<br/>waitpid(-1, WNOHANG)"]
        E3 --> E4["For each reaped PID:<br/>Lookup _pid_to_connection"]
        E4 --> E5["connection.onChildProcessExited()<br/>→ _is_child_dead = true"]
        E5 --> E6{"Output already<br/>ready?"}
        E6 -->|Yes| E7["EnableOutput<br/>→ Poller::mod(EPOLLOUT)"]
        E6 -->|No| E8["NoAction<br/>wait for output"]
    end

    A7 --> B1
    B5 --> C1
    G3 --> RECEIVE
    G3 --> RESPONSE
    I7 --> CGIFLOW
    I6 --> RESPONSE
    L14 --> RESPONSE
Loading

3. State Machines

3.1 HttpRequestReader States

stateDiagram-v2
    [*] --> AwaitingHeaders

    AwaitingHeaders --> AwaitingHeaders : More data needed<br/>(no CRLF CRLF yet)
    AwaitingHeaders --> AwaitingBody : Headers complete<br/>+ POST with body
    AwaitingHeaders --> Complete : Headers complete<br/>+ GET/DELETE (no body)
    AwaitingHeaders --> Error : Invalid request line<br/>or header validation failed
    AwaitingHeaders --> CGI : Headers complete<br/>+ CGI path matched

    AwaitingBody --> AwaitingBody : Body incomplete<br/>(bytes received < Content-Length)
    AwaitingBody --> Complete : Body complete<br/>(all bytes received)
    AwaitingBody --> Error : Body overflow<br/>(exceeds max_body_size)<br/>or parse error

    Complete --> [*]
    Error --> [*]
    CGI --> [*]
Loading

3.2 CGI Lifecycle

stateDiagram-v2
    [*] --> Init : _tryInitCGI()<br/>fork + exec child

    Init --> Writing : Server registers<br/>write pipe (EPOLLOUT)

    Writing --> Writing : Partial write<br/>(more body to send)
    Writing --> SentToChild : All request body<br/>written to stdin pipe

    SentToChild --> Reading : Close write pipe<br/>Register read pipe<br/>(EPOLLIN)

    Reading --> Reading : Partial read<br/>(more output expected)
    Reading --> OutputReady : All CGI output received<br/>(EOF or Content-Length match)

    state WaitForBothConditions <<fork>>
    OutputReady --> WaitForBothConditions
    ChildExited --> WaitForBothConditions

    OutputReady --> WaitingForChild : Child still running
    ChildExited --> WaitingForOutput : Output not ready yet

    WaitForBothConditions --> ResponseReady : Both conditions met<br/>→ EnableOutput

    ResponseReady --> SendResponse : _formCGIResponse()<br/>Validate + send

    state "Error Paths" as Errors {
        CGIError : fork/exec failed → 503
        CGIWriteError : Write pipe error
        CGIReadError : Read pipe error
        CGITimeout : Running > 10s → 504
    }

    Init --> CGIError
    Writing --> CGIWriteError
    Reading --> CGIReadError
    Writing --> CGITimeout
    Reading --> CGITimeout

    CGIError --> SendResponse
    CGIWriteError --> SendResponse
    CGIReadError --> SendResponse
    CGITimeout --> SendResponse : If headers not sent
    CGITimeout --> ConnectionClosed : If headers already sent
Loading

3.3 Server Event Dispatch

stateDiagram-v2
    [*] --> WaitForEvent

    WaitForEvent --> WaitForEvent : epoll_wait timeout<br/>(1000ms, no events)

    state "FD Type Detection" as FDType {
        ListenerFD : Listener FD readable
        SignalFD : Signal FD readable (SIGCHLD)
        ConnectionFD : Connection FD event
        CGIPipeFD : CGI pipe FD event
    }

    WaitForEvent --> ListenerFD
    WaitForEvent --> SignalFD
    WaitForEvent --> ConnectionFD
    WaitForEvent --> CGIPipeFD

    ListenerFD --> AcceptConn : accept() → new Socket
    AcceptConn --> WaitForEvent : Add to epoll (EPOLLIN)

    SignalFD --> ReapChild : waitpid() reap PIDs
    ReapChild --> WaitForEvent : onChildProcessExited()

    state "Connection Events" as ConnEvents {
        EPOLLIN_conn : recv() → parse request
        EPOLLOUT_conn : send() response data
        EPOLLERR_conn : Socket error
        EPOLLHUP_conn : Peer closed
    }

    ConnectionFD --> EPOLLIN_conn
    ConnectionFD --> EPOLLOUT_conn
    ConnectionFD --> EPOLLERR_conn
    ConnectionFD --> EPOLLHUP_conn

    EPOLLIN_conn --> EnableWrite : Request complete<br/>mod(EPOLLIN|EPOLLOUT)
    EPOLLIN_conn --> InitCGI : CGI detected<br/>register CGI pipes
    EPOLLOUT_conn --> BackToRead : All sent<br/>mod(EPOLLIN)
    EPOLLOUT_conn --> WaitForEvent : Partial send<br/>keep EPOLLOUT
    EPOLLERR_conn --> CloseConn
    EPOLLHUP_conn --> CloseConn

    state "CGI Pipe Events" as PipeEvents {
        EPOLLOUT_pipe : Write body to child stdin
        EPOLLIN_pipe : Read output from child stdout
        EPOLLERR_pipe : Pipe error
    }

    CGIPipeFD --> EPOLLOUT_pipe
    CGIPipeFD --> EPOLLIN_pipe
    CGIPipeFD --> EPOLLERR_pipe

    EPOLLOUT_pipe --> SwitchToRead : All sent → unregister write,<br/>register read pipe
    EPOLLIN_pipe --> CGIDone : Output complete<br/>unregister read pipe
    EPOLLERR_pipe --> CGIAbort : Unregister both<br/>pipes, send error

    EnableWrite --> WaitForEvent
    BackToRead --> WaitForEvent
    InitCGI --> WaitForEvent
    SwitchToRead --> WaitForEvent
    CGIDone --> WaitForEvent
    CGIAbort --> WaitForEvent
    CloseConn --> WaitForEvent
Loading

3.4 Connection Lifecycle

stateDiagram-v2
    [*] --> Idle : Connection accepted<br/>epoll EPOLLIN

    Idle --> ReceivingRequest : EPOLLIN event<br/>recv() data

    ReceivingRequest --> ReceivingRequest : Partial headers/body<br/>(Pending)
    ReceivingRequest --> ReadyToRespond : Request complete (Received)<br/>epoll EPOLLIN|EPOLLOUT
    ReceivingRequest --> CGIProcessing : CGI request (Init)
    ReceivingRequest --> Closed : Error or HUP

    CGIProcessing --> ReadyToRespond : CGI complete (EnableOutput)<br/>epoll EPOLLIN|EPOLLOUT

    ReadyToRespond --> SendingResponse : EPOLLOUT event<br/>form + send response

    SendingResponse --> SendingResponse : Partial send (Pending)
    SendingResponse --> Idle : Response fully sent<br/>epoll EPOLLIN (keep-alive)
    SendingResponse --> Closed : Error

    Idle --> Closed : Timeout (5s idle)
    CGIProcessing --> Closed : CGI Timeout (10s)

    Closed --> [*] : _closeConnection()<br/>remove from epoll<br/>destroy Connection
Loading

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors