Minimal C++98 web server with non-blocking I/O (poll), a simple configuration parser, static file serving, basic error responses, and experimental CGI (Python) support. Developed by @nands93 and @amenesca for a university project. Educational and learning purpose only.
- Core entrypoint: srcs/main.cpp
- Server bootstrap:
WebServer::configVServers
,WebServer::initConnection
- Socket loop:
Socket::startServer
,Socket::acceptConnection
,Socket::receiveRequest
,Socket::sendResponse
- Request parsing:
RequestParser::parse
- Response handling:
Response::httpMethods
,Response::handleGET
,Response::handlePOST
- CGI handler:
cgiHandler::postCgi
,cgiHandler::configCgi
,cgiHandler::createEnv
- Config parsing:
ConfigParser::initConfig
,ConfigParser::configServer
,ConfigParser::treatLocation
- Virtual server + locations:
VirtualServer
,struct Location
in the same header
- Single-process, event-driven server using poll()
- Multiple clients, non-blocking sockets
- Nginx-like configuration syntax
- Static files from configured roots
- Basic GET and POST handling
- Basic error statuses for 400/404/405 (static HTML error pages exist but are not yet wired to responses)
- Experimental CGI (Python) invocation on .py URIs
- Linux (uses poll, POSIX sockets, fcntl)
- g++ with C++98 support
- Python 3 at /usr/bin/python3 (used by CGI)
- Terminal (for build/run logs)
make
Outputs binary: ./webserver
Targets are defined in Makefile.
Run from the repository root (paths are relative).
- Default config:
./webserver
- Custom config:
./webserver ./conf/default.conf
The default config (conf/default.conf) listens on port 8080 and defines two servers (server_name localhost
and server_name 127.0.0.1
) on the same port. The active VirtualServer is selected by the Host header (without the port).
Examples:
- http://localhost:8080/ → serves data/www/index.html
- Host matching matters; e.g., curl with a specific Host:
curl -H "Host: localhost" http://127.0.0.1:8080/
curl -H "Host: 127.0.0.1" http://127.0.0.1:8080/
Note:
- Tilde paths are not expanded. The
root ~/data/www/
in the secondlocation
of the default config will not resolve unless changed to an absolute path. - Stop with Ctrl+C (handled in srcs/main.cpp).
Syntax is parsed by ConfigParser
.
Example: conf/default.conf
server {
listen 8080
server_name localhost
body_size 1K
location / {
root data/www/
index index.html
}
location /data {
# Tilde is not expanded by the server; use an absolute path instead
root /absolute/path/to/data/www/
index index.html
}
}
Recognized directives in server
:
- listen, server_name, body_size, error_page, location
Inside location
, see struct Location
in VirtualServer
:
- _locationPath, _root, _cgi_extension, _upload, _autoindex, _methods, _return, _index
Notes:
- Location matching is exact against the request URI (no prefix/longest-match logic).
- The selected VirtualServer is chosen by exact
Host
header match toserver_name
(port stripped).
-
Requests are parsed by
RequestParser
:- Method, URI, version, headers, and body (for POST)
- Host header parsing also extracts the port and stores Host without the port
-
Responses are built by
Response
:- GET:
Response::handleGET
- If
uri
matches alocation
exactly, servesroot + index[1]
- Else, for single-location configs, serves
root + uri
- On success returns 200 text/html with file contents
- On miss returns 404 (body currently not using custom error page)
- If
- POST:
Response::handlePOST
- If URI ends with
.py
, invokes CGI viacgiHandler::postCgi
- Otherwise echoes a simple text/plain response if a body is present; else 400
- If URI ends with
- GET:
-
Static files are read in
Response::readData
- Entry script: cgi-bin/index.py
- Trigger: POST to a URI ending with .py (e.g., /cgi-bin/index.py)
- Invoked by
cgiHandler::postCgi
usingexecve
and environment fromcgiHandler::createEnv
Current state:
- Experimental: child process executes Python, but the HTTP response body does not capture the script’s stdout back to the client. You will see a 200 OK with an empty body.
- The standalone test harness
cgiHandler::configCgi
(used by srcs/cgi/maincgi.cpp) demonstrates execution/redirection but is not wired into the main server path.
Ensure the script is executable. The form in data/www/index.html posts multipart/form-data to ../../cgi-bin/index.py.
- Main page: data/www/index.html (includes a form to POST to CGI)
- Alternate pages: data/www/index_test.html, data/www/alan_index.html
- Error page templates (not automatically returned yet):
- GET home:
curl -i http://localhost:8080/
- POST to CGI (multipart):
curl -i -F "nome=Alice" -F "[email protected]" -F "imagem=@/path/to/image.png" http://localhost:8080/cgi-bin/index.py
- POST simple form data (echo path):
curl -i -d "hello=world" http://localhost:8080/
- Select a specific VirtualServer by Host:
curl -i -H "Host: localhost" http://127.0.0.1:8080/
- Core
- HTTP
- Configuration
- Clients
- CGI
- Data
- Includes
- Single listen socket; binds to the first configured VirtualServer port
- VirtualServer selection by exact Host header match only (no SNI, no IP-based)
- Location matching is exact against the request URI (no prefix/longest-match)
- Tilde (~) in paths is not expanded
- Some error responses currently miss body content
- No directory listing/autoindex
- No DELETE implementation yet
- CGI response body not returned to client (experimental plumbing)
- No TLS, no HTTP/1.1 persistent connections, no chunked encoding
- Minimal error handling and validation
- Signals: SIGINT triggers clean exit in srcs/main.cpp
- Non-blocking clients: set via fcntl in
Socket::acceptConnection
- Buffering: