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.
- HTTP WebServer
- 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_namematching - 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
┌─────────────────────────────────────────────────────┐
│ main.cpp │
│ Entry point & server loop │
└──────────────────────┬──────────────────────────────┘
│
┌────────────┼────────────┐
▼ ▼ ▼
┌────────────┐ ┌─────────┐ ┌──────────┐
│ ConfigFile │ │ Socket │ │ Request │
│ Parsing │ │ Layer │ │ Handling │
└────────────┘ └─────────┘ └──────────┘
| 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 |
The server uses a single-threaded, non-blocking I/O model:
- All sockets are set to
O_NONBLOCK poll()monitors all file descriptors for read/write readiness- Server sockets trigger
accept()for new connections - Client sockets trigger
read()/send()as data becomes available - Each
ConnectSockettracks its own state (reading headers, reading body, sending response) - 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
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)
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
- C++11 compatible compiler
- POSIX system (macOS / Linux)
- PHP CGI and/or Python 3 (optional, for CGI support)
make # Compile the webserv binary
make clean # Remove object files
make fclean # Remove object files and binary
make re # Full rebuild# With default config (conf-files/config.conf)
./webserv
# With custom config
./webserv path/to/config.confThe server uses an NGINX-inspired configuration format.
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;
}
}| 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 |
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
- 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
- File upload to configured
uploaddirectory - CGI execution with request body passed via stdin
- Returns:
201 Created(withLocationheader),409 Conflict(file exists)
- Remove files from the filesystem
- Returns:
200 OK,403 Forbidden,404 Not Found
- PHP via
php-cgi - Python via
python3
- Fork a child process
- Set up environment variables (
REQUEST_METHOD,QUERY_STRING,CONTENT_TYPE,SCRIPT_FILENAME, etc.) - Redirect stdin from a temp file (for POST body)
- Redirect stdout to a pipe
execve()the CGI interpreter with the script path- Parent reads output from pipe, parses CGI headers
- 10-second execution timeout
| 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 |
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;