A simple, multi-process HTTP/1.1 server written in C. It is designed to be small, fast, and portable between Linux and macOS.
- Efficient File Transfer: Uses the
sendfile()system call for high-performance, zero-copy file delivery. A portable wrapper ensures it works on both Linux and macOS/BSD. - HTTP Keep-Alive: Supports persistent connections to reduce latency by reusing the same TCP connection for multiple requests.
- Metadata Caching: Implements an in-memory LRU cache to store file metadata (size, modification time, MIME type). This significantly reduces disk I/O by avoiding
stat()system calls on subsequent requests. - Dynamic IP Detection: Correctly identifies the original client IP address when running behind a reverse proxy (like Cloudflare or Nginx) by reading a configurable HTTP header (e.g.,
CF-Connecting-IPorX-Forwarded-For). - External Configuration: All settings are managed via a simple
server.conffile, including port, content path, and log file locations. - MIME Type Support: Dynamically loads MIME types from a
mime.typesfile to serve content with the correctContent-Typeheader. - Logging: Provides separate, configurable log files for access and error reporting.
- ETag Support: Generates and validates ETags for cache control, supporting
304 Not Modifiedresponses. - Security: Includes basic protection against directory traversal attacks.
The server is built with a modular structure.
srv.c: Main entry point. Initializes the configuration, creates the cache, and starts the server.config.c/config.h: Handles loading and parsing theserver.conffile.server.c: Core server logic. It initializes the listening socket, runs the mainaccept()loop, and forks child processes to handle connections. It also contains thesend_file_responsefunction which integrates the caching logic.request.c: Parses raw HTTP requests and dynamically checks for the client IP header specified in the configuration.log.c: Provides functions for access and error logging.cache.c: Implements a fixed-size LRU cache for file metadata. It uses a hash map for fast lookups and a doubly linked list to manage item recency.mime.c/hashmap.c: Implements a hash map to load and query MIME types from themime.typesfile.
- Concurrency Model Scalability: The server uses a process-per-connection model (
fork), which is simple and robust but can be resource-intensive (especially memory) and may not scale to thousands of concurrent connections as efficiently as thread-based or event-driven models. - No Shared Cache: The cache is created in each child process due to the
fork()model. It is not shared between concurrent connections, which limits its effectiveness under heavy load. A shared memory cache would be required to solve this. - HTTP Only: Does not support HTTPS/TLS. It should be run behind a reverse proxy for SSL termination.
- Limited HTTP Support: Implements only
GETandHEADmethods. - IPv4 Only: The server socket listens only on IPv4 (
AF_INET).
- A C compiler (e.g.,
gccorclang) make
To compile the server, run make:
makeThis will produce the srvd executable.
To install the server binary and default configuration files:
sudo make installThis command will:
- Install the
srvdbinary to/usr/local/bin/. - Install
server.confandmime.typesto/etc/srv/. - Create a web directory at
/var/www/srvand a log directory at/var/log/gabitojusrv.
Warning: Configuration Path Mismatch The
make installcommand places the configuration file at/etc/srv/server.conf. However, thesrvdbinary is hardcoded to look for it at/etc/gabitojusrv/server.confby default.To resolve this, you have two options:
- Recommended: Always use the
-cflag to specify the configuration file path:srvd -c /etc/srv/server.conf- Alternative: Modify the
CONFIG_FILEpath inconstants.hto"/etc/srv/server.conf"before compiling.
The server is configured via a key value file. All available options are listed below.
Example server.conf:
# Server port
port 8080
# Path to the website content
content_path /var/www/srv
# Path to the mime.types file
mime_types_path /etc/srv/mime.types
# Path to log files
access_log_path /var/log/gabitojusrv/access.log
error_log_path /var/log/gabitojusrv/error.log
# Header for identifying the real client IP
real_ip_header X-Forwarded-For
Configuration Options
| Key | Description | Default Value |
|---|---|---|
port |
TCP port to listen on. | 8080 |
content_path |
Root directory for website files. | . (current directory) |
mime_types_path |
Path to the mime.types database file. |
/etc/gabitojusrv/mime.types |
access_log_path |
Path to the access log file. | /var/log/gabitojusrv/access.log |
error_log_path |
Path to the error log file. | /var/log/gabitojusrv/error.log |
real_ip_header |
HTTP header to use for the real client IP. | X-Forwarded-For |
Run the server with the following command, making sure to specify the configuration file:
srvd -c /etc/srv/server.confThe project includes a simple stress testing script, stress_test.sh, which uses curl to send a configurable number of concurrent requests to the server.
./stress_test.sh [url] [requests] [concurrency]url: The URL to request (e.g.,http://localhost:8080/index.html).requests: The total number of requests to make.concurrency: The number of concurrent requests to run at a time.
To send 10,000 requests to the server with a concurrency of 100:
./stress_test.sh http://localhost:8080/index.html 10000 100