Skip to content

Mohamed-Moumni/HTTP-WebServer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

339 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP WebServer

A fully functional HTTP/1.1 web server written in C++11, inspired by NGINX. this server handles multiple concurrent connections using non-blocking I/O with poll(), supports CGI execution, virtual hosting, chunked transfer encoding, and an NGINX-style configuration file.


Table of Contents


Features

  • HTTP/1.1 compliant request/response handling
  • Non-blocking I/O using poll() — single-threaded, event-driven architecture
  • Virtual hosting — multiple servers on different ports with server_name matching
  • CGI execution — PHP and Python script support via fork/exec
  • Chunked Transfer Encoding — decode chunked request bodies
  • Static file serving with MIME type detection (50+ types)
  • Directory listing (autoindex)
  • File uploads via POST
  • Custom error pages per server
  • Keep-alive connections with configurable timeout
  • Configurable body size limits per server
  • 301 redirects via location directives
  • Path traversal protection

Architecture

┌─────────────────────────────────────────────────────┐
│                      main.cpp                       │
│              Entry point & server loop              │
└──────────────────────┬──────────────────────────────┘
                       │
          ┌────────────┼────────────┐
          ▼            ▼            ▼
   ┌────────────┐ ┌─────────┐ ┌──────────┐
   │ ConfigFile │ │ Socket  │ │ Request  │
   │  Parsing   │ │  Layer  │ │ Handling │
   └────────────┘ └─────────┘ └──────────┘

Core Components

Component Class Responsibility
Configuration ConfigFile, Server, location Parse NGINX-style config into structured objects
Networking Socket, ConnectSocket TCP socket creation, poll() event loop, per-connection state
Request Parsing request Parse HTTP request line, headers, and body
Response Generation response Route requests to handlers, build HTTP responses
CGI (in cgi_handler.cpp) Fork/exec CGI scripts, capture output via pipes

Event Loop Model

The server uses a single-threaded, non-blocking I/O model:

  1. All sockets are set to O_NONBLOCK
  2. poll() monitors all file descriptors for read/write readiness
  3. Server sockets trigger accept() for new connections
  4. Client sockets trigger read()/send() as data becomes available
  5. Each ConnectSocket tracks its own state (reading headers, reading body, sending response)
  6. Connections timeout after 5 seconds of inactivity
poll() loop
  │
  ├─ POLLIN on server fd  → accept() new connection
  ├─ POLLIN on client fd  → read request data
  ├─ POLLOUT on client fd → send response data
  ├─ POLLERR/POLLHUP      → close connection
  └─ Timeout check        → close idle connections

Class Relationships

ConfigFile
 ├── vector<Server>
 │    ├── listen (host:port pairs)
 │    ├── server_names
 │    ├── root, index, autoindex
 │    ├── error_pages
 │    ├── client_max_body_size
 │    └── vector<location>
 │         ├── path, root, index
 │         ├── allowed_methods
 │         ├── cgiPath, cgiExt
 │         ├── upload directory
 │         └── return (redirect)
 ├── mime_types map (.ext → Content-Type)
 └── code_status map (400 → "Bad Request")

ConnectSocket (per-connection state)
 ├── request  (parsed method, path, headers, body)
 ├── response (response string, send progress)
 ├── flags    (ReadAvailable, SendAvailable, Chunked, closed)
 └── timeout  (last activity timestamp)

Project Structure

HTTP-WebServer/
├── main.cpp                        # Entry point, server loop
├── Makefile                        # Build system (C++11)
├── conf-files/
│   └── config.conf                 # Server configuration
├── includes/                       # Header files
│   ├── configfile.hpp              # Config parsing class
│   ├── server.hpp                  # Server configuration struct
│   ├── location.hpp                # Location block struct
│   ├── socket.hpp                  # Socket & poll management
│   ├── ConnectSocket.hpp           # Per-connection state
│   ├── request.class.hpp           # HTTP request data
│   ├── response.class.hpp          # HTTP response data
│   ├── request.hpp                 # Request handling functions
│   └── utils.hpp                   # String utilities
├── src/
│   ├── configfile/                 # Configuration parsing
│   │   ├── configfile.cpp          # Config class methods
│   │   ├── parse.cpp               # Main parsing logic
│   │   ├── server.cpp              # Server block parsing
│   │   ├── location.cpp            # Location block parsing
│   │   ├── preprocessing.cpp       # Whitespace/comment normalization
│   │   ├── check_errors.cpp        # Config validation
│   │   ├── errors_handling.cpp     # Error reporting
│   │   ├── print_servers.cpp       # Debug printing
│   │   ├── code_status             # HTTP status code lookup table
│   │   └── MIME_TYPES              # File extension → Content-Type map
│   ├── server/                     # Network layer
│   │   ├── socket.cpp              # Socket creation, bind, listen
│   │   ├── ConnectSocket.cpp       # Connection read/write handling
│   │   └── ConnectSocketUtils.cpp  # Chunked decoding, helpers
│   ├── request/                    # HTTP request processing
│   │   ├── request.cpp             # Request class methods
│   │   ├── REQUEST_PARSING/
│   │   │   ├── pars_request.cpp    # Parse request line & headers
│   │   │   ├── possible_error.cpp  # Request validation
│   │   │   └── respond_error.cpp   # Error response generation
│   │   ├── HTTP_METHODS/
│   │   │   ├── GET.cpp             # GET handler (files, directories, CGI)
│   │   │   ├── POST.cpp            # POST handler (uploads, CGI)
│   │   │   └── DELETE.cpp          # DELETE handler (file removal)
│   │   └── RESPONDING/
│   │       ├── respond.cpp         # Server/location matching, routing
│   │       ├── response_generator.cpp  # Dispatch to method handlers
│   │       └── cgi_handler.cpp     # CGI fork/exec/pipe logic
│   └── utils/                      # String utility functions
├── www/                            # Default webroot
│   ├── index.html
│   ├── php/                        # PHP test scripts
│   ├── python/                     # Python CGI scripts
│   ├── images/                     # Static assets
│   ├── Error_pages/                # Custom error pages
│   └── upload/                     # Upload directory
├── Error_pages/                    # Fallback error pages
└── tools/
    └── php-cgi                     # PHP CGI binary

Build & Run

Prerequisites

  • C++11 compatible compiler
  • POSIX system (macOS / Linux)
  • PHP CGI and/or Python 3 (optional, for CGI support)

Build

make        # Compile the webserv binary
make clean  # Remove object files
make fclean # Remove object files and binary
make re     # Full rebuild

Run

# With default config (conf-files/config.conf)
./webserv

# With custom config
./webserv path/to/config.conf

Configuration

The server uses an NGINX-inspired configuration format.

Example

server {
    listen 127.0.0.1:8080;
    server_name localhost;

    root /www/;
    index index.html;
    autoindex off;
    client_max_body_size 10m;
    allowed_methods GET,POST,DELETE;

    error_page 404 /Error_pages/404.html;

    location / {
        autoindex on;
    }

    location /php {
        cgiPath /usr/bin/php-cgi;
        cgiExt .php;
        upload upload;
        allowed_methods GET,POST;
    }

    location /python {
        cgiPath /usr/bin/python3;
        cgiExt .py;
    }

    location /redirect {
        return http://example.com;
    }
}

Directives

Directive Scope Description
listen server Bind address and port (e.g. 127.0.0.1:8080)
server_name server Virtual host name(s)
root server, location Document root directory
index server, location Default index file(s)
autoindex server, location Enable directory listing (on/off)
client_max_body_size server Max request body size (e.g. 10m)
allowed_methods server, location Comma-separated list: GET,POST,DELETE
error_page server Custom error page (e.g. 404 /path/to/404.html)
return location 301 redirect URL
cgiPath location Path to CGI interpreter
cgiExt location File extension triggering CGI (e.g. .php)
upload location Upload subdirectory name

Request Lifecycle

Client Request
     │
     ▼
1. Read raw bytes from socket
     │
     ▼
2. Parse request line (METHOD /path HTTP/1.1)
   Parse headers (key: value pairs)
   Parse body (Content-Length or Chunked)
     │
     ▼
3. Validate request
   ├─ HTTP/1.1 only
   ├─ URI length < 10,000 chars
   ├─ Path traversal check (..)
   ├─ POST requires Content-Type & Content-Length
   └─ Body size vs client_max_body_size
     │
     ▼
4. Match server (by Host header → server_name)
   Match location (longest prefix match)
     │
     ▼
5. Check allowed methods for location
     │
     ▼
6. Route to handler
   ├─ GET  → static file / directory listing / CGI
   ├─ POST → file upload / CGI
   └─ DELETE → file removal
     │
     ▼
7. Build HTTP response (status line + headers + body)
     │
     ▼
8. Send response (partial sends tracked via offset)
     │
     ▼
9. Keep-alive or close connection

HTTP Methods

GET

  • Serve static files with appropriate MIME type
  • Directory listing when autoindex on
  • Execute CGI scripts when matching cgiExt
  • Returns: 200 OK, 301 Moved Permanently, 403 Forbidden, 404 Not Found

POST

  • File upload to configured upload directory
  • CGI execution with request body passed via stdin
  • Returns: 201 Created (with Location header), 409 Conflict (file exists)

DELETE

  • Remove files from the filesystem
  • Returns: 200 OK, 403 Forbidden, 404 Not Found

CGI Support

Supported Interpreters

  • PHP via php-cgi
  • Python via python3

Execution Model

  1. Fork a child process
  2. Set up environment variables (REQUEST_METHOD, QUERY_STRING, CONTENT_TYPE, SCRIPT_FILENAME, etc.)
  3. Redirect stdin from a temp file (for POST body)
  4. Redirect stdout to a pipe
  5. execve() the CGI interpreter with the script path
  6. Parent reads output from pipe, parses CGI headers
  7. 10-second execution timeout

Environment Variables

Variable Description
SERVER_SOFTWARE NGINY/0.1
SERVER_PROTOCOL HTTP/1.1
REQUEST_METHOD GET or POST
SCRIPT_FILENAME Full path to the script
QUERY_STRING URL query parameters (GET) or body (POST)
CONTENT_TYPE Request content type (POST)
CONTENT_LENGTH Request body length (POST)
PATH_INFO Extra path after the script
SERVER_PORT Listening port
REDIRECT_STATUS 301

Error Handling

The server returns appropriate HTTP error codes with optional custom error pages:

Code Condition
400 Malformed request, invalid headers
403 Path traversal attempt, no read permission, directory without autoindex
404 File or resource not found
405 Method not allowed for this location
408 Connection timeout (5 seconds idle)
409 File already exists (POST upload)
411 POST without Content-Length
413 Request body exceeds client_max_body_size
414 URI exceeds 10,000 characters
415 Unsupported Content-Type
501 Unimplemented feature
505 HTTP version not supported (not HTTP/1.1)

Custom error pages can be configured per server:

error_page 404 /Error_pages/404.html;
error_page 500 /Error_pages/500.html;

About

This project is about writing HTTP server, and learning about how the HTTP protocol works around the web.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors