A high-performance, multi-threaded HTTP server written in C++20 with modern features including middleware support, static file serving, JSON handling with simdjson and nlohmann/json, advanced template engine, clustering capabilities, and sophisticated routing capabilities.
- Features
- Quick Start
- Installation
- Configuration
- Usage Examples
- Template Engine
- API Reference
- Performance
- Architecture
- Development
- Troubleshooting
- Contributing
- License
- 🚀 Quick Start Guide
- 📦 Installation
- ⚙️ Configuration
- 💡 Usage Examples
- 🎨 Template Engine
- 🔧 API Reference
- ⚡ Performance
- 🏗️ Architecture
- 🛠️ Development
- 🔍 Troubleshooting
- High Performance: Multi-threaded architecture with epoll-based event handling
- Middleware Support: Flexible middleware system for authentication and request processing
- Static File Serving: Built-in static file handler for serving public assets
- JSON Support: Integrated JSON parsing with simdjson for high-performance parsing and nlohmann/json for template engine data binding
- Route Parameters: Dynamic route parameter extraction (e.g.,
/test/:id) - Authentication: Token-based authentication middleware
- Thread Pool: Configurable thread pool for handling concurrent connections
- Keep-Alive: HTTP keep-alive support for better performance
- File Serving: Direct file serving with automatic MIME type detection
- HTTP Redirects: Support for permanent (301) and temporary (302) redirects
- Wildcard Routes: Catch-all routes for 404 handling and fallback patterns
- Enhanced Routing: Advanced routing with method-specific handlers, chaining, and grouping
- Advanced Template Engine: Dynamic HTML rendering with nlohmann/json data binding
- Template Include System: Modular template structure with include support
- Template Conditionals:
{{ if }}and{{ endif }}for conditional rendering - Template Loops:
{{ for }}and{{ endfor }}for iterative rendering - Template Filters: Custom filters like
|formatPrice,|add:1for data transformation - Template Caching: Compiled template caching for faster rendering
- Memory-Optimized Rendering: Direct response writing without intermediate string copies
- Custom 404 Pages: Render custom not found pages with templates
- Router Integration: Modular API design with Router objects
- Group Routing: Route grouping for API versioning and modular structure
- Memory Optimization: tcmalloc integration for better memory management
- Clustering Support: Fork-based worker processes for load distribution
- Configuration System: Dynamic configuration with
.nrvcfgfiles - Library Build: Shared library support with install system
- File Upload: Multipart/form-data support for file upload (Request::getFormData, File::save)
- HTTP Methods: Full support for GET, POST, PUT, DELETE methods with chaining
- Content-Type Detection: Automatic content-type detection for responses
- Thread-Safe Architecture: Thread-safe queue and thread pool for concurrent connections
- MIME Type Support: Comprehensive MIME type detection for static files
- Response Headers: Custom header management and automatic header setting
- Process Management: Fork-based clustering with graceful shutdown
- Non-blocking I/O: Epoll-based event-driven architecture
- Cookie Management: Comprehensive cookie handling with signed cookies and session management
- C++20 compatible compiler (clang++ recommended)
- Linux system (uses epoll)
- simdjson library (for high-performance JSON parsing)
- nlohmann/json library (for template engine data binding)
- tcmalloc (optional, for better performance)
-
Install Dependencies
sudo apt update sudo apt install clang++ libsimdjson-dev nlohmann-json3-dev libssl-dev libtcmalloc-minimal4 make
-
Clone and Build
git clone https://github.com/wyrexdev/nerva.git cd nerva make -
Run the Server
make run
-
Test the Server
# In another terminal curl http://localhost:8080/ # Should return: "Home Page - Nerva HTTP Server"
-
Create Your First Route
// Edit src/main.cpp and add: server.Get("/hello", {}, [](const Http::Request &req, Http::Response &res, auto next) { res << 200 << "Hello, World!"; });
# Build executable
make
# Build shared library
make lib
# Install library and headers
make installmake runThe server will start on port 8080 by default with tcmalloc preloaded for optimal performance.
- Operating System: Linux (uses epoll for event handling)
- Compiler: C++20 compatible (clang++ 12+ or g++ 10+)
- Memory: Minimum 512MB RAM, recommended 2GB+
- Storage: 100MB free space for build artifacts
sudo apt update
sudo apt install clang++ libsimdjson-dev nlohmann-json3-dev libssl-dev libtcmalloc-minimal4sudo pacman -S clang simdjson nlohmann-json openssl gperftoolssudo dnf install clang simdjson-devel nlohmann-json-devel openssl-devel gperftools-devel# Clone the repository
git clone https://github.com/wyrexdev/nerva.git
cd nerva
# Build the server
make
# Build shared library (optional)
make lib
# Install system-wide (optional)
sudo make installFROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
clang++ \
libsimdjson-dev \
nlohmann-json3-dev \
libssl-dev \
libtcmalloc-minimal4 \
make \
git
WORKDIR /app
COPY . .
RUN make
EXPOSE 8080
CMD ["./server"]After installation, verify the server works:
# Start the server
make run
# In another terminal, test the server
curl http://localhost:8080/
# Should return: "Home Page - Nerva HTTP Server"
# Test JSON endpoint
curl -X POST http://localhost:8080/json
# Should return: {"message": "JSON POST successful!"}Create a server.nrvcfg file for custom configuration:
server {
port = 8080;
buffer_size = 4096;
thread_pool_size = 48;
keep_alive_timeout = default;
cluster_thread = 3;
max_connections = 500000;
accept_queue_size = 65535;
accept_retry_delay_ms = 10;
max_events = 8192;
}#include <nerva/Server.hpp>
#include <nerva/Middleware.hpp>
#include <nerva/Json.hpp>
int main() {
Server server = Server();
server.SetConfigFile("server");
server.Get("/", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Home Page - Nerva HTTP Server";
});
server.Start();
server.Stop();
return 0;
}server.Get("/test/:id", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Test ID: " << req.getParam("id");
});server.Post("/test", {}, [](const Http::Request &req, Http::Response &res, auto next) {
const std::string jsonResponse = R"({"message": "Test POST successful!"})";
res << 200 << Json::ParseAndReturnBody(jsonResponse);
});Middleware authMiddleware = Middleware([](Http::Request &req, Http::Response &res, auto next) {
std::string token = req.getQuery("token");
if (token != "123") {
res << 401 << "Unauthorized";
return;
}
next();
});
server["GET"].Use("/protected", {authMiddleware}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << Json::ParseAndReturnBody(R"({"message": "Protected area - Welcome!"})");
});server.Static("/static", "./public");server.Get("/image-test", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.SendFile("./public/a.jpg");
});server["GET"].Use("/redirect", {authMiddleware}, [](const Http::Request &req, Http::Response &res, auto next) {
res.MovedRedirect("/home");
});server["GET"].Register("/register-test").Use(authMiddleware).Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << Json::ParseAndReturnBody(R"({"message": "Register test successful!"})");
});
server.Get("/secure")
.Use(authMiddleware)
.Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << Json::ParseAndReturnBody(R"({"message": "Secure area", "access": "granted"})");
});Router apiRouter;
apiRouter.Get("/users", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "User list";
});
apiRouter.Get("/users/:id", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "User ID: " << req.getParam("id");
});
server.Use("/api", apiRouter);server.Group("/api/v1").Then([](Router &r) {
r.Get("/users").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "API v1 - Users";
});
r.Get("/posts").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "API v1 - Posts";
});
});
server.Group("/admin").Then([](Router &r) {
r.Get("/dashboard").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Admin Dashboard";
});
r.Get("/settings").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Admin Settings";
});
});
// Group with middleware support
server.Group("/testGroup", {}, [](Router &r) {
r.Get("/users").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Protected Users";
});
r.Get("/posts").Then([](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Protected Posts";
});
});Nerva::Engine *engine = new Nerva::Engine();
engine->setViewsDirectory("./views");
server.Set("view engine", engine);
server.Get("/products").Then([](const Http::Request &req, Http::Response &res, auto next) {
nlohmann::json data = {
{"pageTitle", "Super Products"},
{"showPromo", true},
{"promoMessage", "TODAY'S SPECIAL DISCOUNT!"},
{"user", {
{"name", "Ayşe Demir"},
{"premium", true},
{"cartItems", "3"}
}},
{"products", {
{
{"id", "101"},
{"name", "Smartphone"},
{"price", 7999.90},
{"inStock", true}
},
{
{"id", "205"},
{"name", "Laptop"},
{"price", 12499.99},
{"inStock", false}
},
{
{"id", "302"},
{"name", "Wireless Headphones"},
{"price", 1299.50},
{"inStock", true}
}
}},
{"features", {
"Fast Delivery",
"Free Returns",
"Original Product Guarantee"
}}
};
res.Render("productPage", data);
});{{ include header }}
{{ include productCard with product }}
{{ include footer }}{{ if showPromo }}
<div class="promo-banner">
{{ promoMessage }}
</div>
{{ endif }}
{{ if user.premium }}
<span class="badge premium">PREMIUM</span>
{{ endif }}{{ for product in products }}
{{ include productCard with product }}
{{ endfor }}
{{ for feature, index in features }}
<li>{{ index|add:1 }}. {{ feature }}</li>
{{ endfor }}<p class="price">${{ product.price|formatPrice }}</p>
<li>{{ index|add:1 }}. {{ feature }}</li>server.Get("/*").Then([](const Http::Request &req, Http::Response &res, auto next) {
res.Render("notFound", nlohmann::json{});
});// GET method
server.Get("/users", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "User list";
});
// POST method
server.Post("/users", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 201 << "User created";
});
// PUT method
server.Put("/users/:id", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "User updated: " << req.getParam("id");
});
// DELETE method
server.Delete("/users/:id", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 204 << "User deleted: " << req.getParam("id");
});server.Get("/api/data", {}, [](const Http::Request &req, Http::Response &res, auto next) {
// Automatic content-type detection
res << 200 << R"({"message": "JSON response"})";
// Will automatically set Content-Type: application/json
});
server.Get("/custom", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.setHeader("X-Custom-Header", "Custom Value");
res.setHeader("Cache-Control", "no-cache");
res << 200 << "Custom response with headers";
});The server uses a sophisticated thread-safe architecture:
- Thread-Safe Queue: Lock-free socket queue for connection handling
- Thread Pool: Configurable worker thread pool for request processing
- Non-blocking I/O: Epoll-based event-driven architecture
- Connection Limits: Configurable maximum connections and queue sizes
// Configuration for thread pool and connection limits
server {
thread_pool_size = 48; // Worker threads
max_connections = 500000; // Max concurrent connections
accept_queue_size = 65535; // TCP accept queue
max_events = 8192; // Epoll events limit
}// Fork-based worker processes
Cluster clusterManager;
std::vector<pid_t> workers = clusterManager.forkWorkers(serverSocket, cpuCount);
// Graceful shutdown
clusterManager.sendShutdownSignal(workers);
clusterManager.waitForWorkers(workers);The server automatically detects and serves files with proper MIME types:
// Supported file types with automatic MIME detection
server.Static("/static", "./public");
// Automatically serves:
// - HTML files (.html, .htm) as text/html
// - CSS files (.css) as text/css
// - JavaScript files (.js) as text/javascript
// - Images (.png, .jpg, .gif, .svg) as image/*
// - JSON files (.json) as application/json
// - And many more...server.Get("/download", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.setHeader("Content-Disposition", "attachment; filename=file.pdf");
res.setHeader("Cache-Control", "no-cache");
res.SendFile("./files/document.pdf");
});
server.Get("/redirect-permanent", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.MovedRedirect("/new-location"); // 301 redirect
});
server.Get("/redirect-temporary", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.TemporaryRedirect("/temp-location"); // 302 redirect
});The server includes advanced request handling with several performance optimizations:
// Enhanced request handling with timeout management
void Server::handleClient(int clientSocket) {
// Configurable buffer size with memory pre-allocation
const size_t BUFFER_SIZE = config.getInt("buffer_size");
std::vector<char> buffer(BUFFER_SIZE);
std::string requestData;
requestData.reserve(BUFFER_SIZE * 2); // Pre-allocate memory
// Socket timeout configuration
struct timeval tv;
tv.tv_sec = 5; // 5 second timeout
tv.tv_usec = 0;
setsockopt(clientSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// Keep-alive support
bool keepAlive = req.headers["Connection"] == "keep-alive" ||
(req.version == "HTTP/1.1" && req.headers["Connection"] != "close");
}// Comprehensive error handling with proper HTTP responses
try {
// Request parsing with validation
if (!req.parse(requestData)) {
std::string badReq = "HTTP/1.1 400 Bad Request\r\n"
"Connection: close\r\n"
"Content-Length: 0\r\n\r\n";
send(clientSocket, badReq.data(), badReq.size(), MSG_NOSIGNAL);
break;
}
} catch (const std::exception &e) {
std::cerr << "Client error: " << e.what() << std::endl;
}// Socket optimization for high performance
int Server::initSocket(int port, int listenQueueSize) {
// Non-blocking socket with optimized settings
int sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
// TCP optimizations
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt));
// Buffer size optimization (1MB)
int bufsize = 1024 * 1024;
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(bufsize));
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize));
}// Set various types of cookies
server.Get("/cookies", {}, [](const Http::Request &req, Http::Response &res, auto next) {
// Basic cookie
Http::CookieOptions basicOpts;
basicOpts.maxAge = std::chrono::hours(1);
res.setCookie("basic_cookie", "Hello World", basicOpts);
// Secure signed cookie
Http::CookieOptions secureOpts;
secureOpts.maxAge = std::chrono::hours(2);
secureOpts.httpOnly = true;
secureOpts.secure = false; // Set to true in production
res.setSignedCookie("secure_cookie", "Secret Data", "my-secret-key", secureOpts);
// Session cookie
Http::CookieOptions sessionOpts;
sessionOpts.maxAge = std::chrono::hours(24);
sessionOpts.httpOnly = true;
sessionOpts.sameSite = "Strict";
res.setCookie("session_cookie", "User Session Data", sessionOpts);
// Read cookies
std::string basicValue = res.getCookieValue("basic_cookie", "Not Set");
auto secureValue = res.getSignedCookie("secure_cookie", "my-secret-key");
nlohmann::json data = {
{"pageTitle", "Cookie Examples"},
{"basicCookie", basicValue},
{"secureCookie", secureValue.value_or("Invalid or Not Set")}
};
res.Render("cookies", data);
});
// Remove cookie
server.Get("/logout", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res.removeCookie("session_id");
res.TemporaryRedirect("/login");
});// Simple user database (in real app, use proper database)
std::map<std::string, std::string> users = {
{"admin", "password123"},
{"user1", "password456"},
{"demo", "demo123"}
};
// Session storage (in real app, use Redis or database)
std::map<std::string, std::string> sessions;
// Login page
server.Get("/login", {}, [](const Http::Request &req, Http::Response &res, auto next) {
nlohmann::json data = {
{"pageTitle", "Login - Nerva HTTP Server"},
{"error", ""}
};
res.Render("login", data);
});
// Login form handler
server.Post("/login", {}, [](const Http::Request &req, Http::Response &res, auto next) {
std::string username = req.getFormData("username").value;
std::string password = req.getFormData("password").value;
// Check credentials
if (users.find(username) != users.end() && users[username] == password) {
// Generate session ID
std::string sessionId = "sess_" + std::to_string(std::time(nullptr)) + "_" + username;
sessions[sessionId] = username;
// Set secure cookie
Http::CookieOptions cookieOpts;
cookieOpts.maxAge = std::chrono::hours(24); // 24 hours
cookieOpts.httpOnly = true;
cookieOpts.secure = false; // Set to true in production with HTTPS
cookieOpts.sameSite = "Lax";
res.setCookie("session_id", sessionId, cookieOpts);
res.TemporaryRedirect("/dashboard");
} else {
// Invalid credentials
nlohmann::json data = {
{"pageTitle", "Login - Nerva HTTP Server"},
{"error", "Invalid username or password"}
};
res.Render("login", data);
}
});
// Protected dashboard
server.Get("/dashboard", {}, [](const Http::Request &req, Http::Response &res, auto next) {
auto sessionId = res.getCookie("session_id");
if (!sessionId || sessions.find(*sessionId) == sessions.end()) {
res.TemporaryRedirect("/login");
return;
}
std::string username = sessions[*sessionId];
nlohmann::json data = {
{"pageTitle", "Dashboard - Nerva HTTP Server"},
{"username", username},
{"sessionId", *sessionId},
{"loginTime", std::to_string(std::time(nullptr))}
};
res.Render("dashboard", data);
});// Cookie management example
server.Get("/cookie-manager", {}, [](const Http::Request &req, Http::Response &res, auto next) {
std::string action = req.getQuery("action");
std::string name = req.getQuery("name");
std::string value = req.getQuery("value");
if (action == "set" && !name.empty()) {
Http::CookieOptions opts;
opts.maxAge = std::chrono::hours(1);
res.setCookie(name, value, opts);
} else if (action == "remove" && !name.empty()) {
res.removeCookie(name);
}
res.TemporaryRedirect("/cookies");
});#include <iostream>
#include "Server.hpp"
#include "Middleware.hpp"
#include "Json.hpp"
#include "ViewEngine/NervaEngine.hpp"
int main() {
Server server;
server.SetConfigFile("server");
server.Static("/static", "./public");
Nerva::Engine *engine = new Nerva::Engine();
engine->setViewsDirectory("./views");
server.Set("view engine", engine);
server.Get("/", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Home Page - Nerva HTTP Server";
});
server.Get("/test/:id", {}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << "Test ID: " << req.getParam("id");
});
server.Post("/upload", {}, [](const Http::Request &req, Http::Response &res, auto next) {
auto fileData = req.getFormData("file");
if (fileData.isFile && !fileData.file.empty()) {
fileData.file.save("./public/" + fileData.filename);
res << 200 << "File uploaded successfully: " << fileData.filename;
} else {
res << 400 << "File upload failed.";
}
});
Middleware authMiddleware = Middleware([](Http::Request &req, Http::Response &res, auto next) {
std::string token = req.getQuery("token");
if (token != "123") {
res << 401 << "Unauthorized";
return;
}
next();
});
server["GET"].Use("/protected", {authMiddleware}, [](const Http::Request &req, Http::Response &res, auto next) {
res << 200 << Json::ParseAndReturnBody(R"({"message": "Protected area - Welcome!"})");
});
server.Get("/products").Then([](const Http::Request &req, Http::Response &res, auto next) {
nlohmann::json data = {
{"pageTitle", "Super Products"},
{"products", {{{"id", "101"}, {"name", "Smartphone"}, {"price", 7999.90}, {"inStock", true}}}}
};
res.Render("productPage", data);
});
server.Get("/*").Then([](const Http::Request &req, Http::Response &res, auto next) {
res.Render("notFound", nlohmann::json{});
});
server.Start();
server.Stop();
return 0;
}The server uses a custom configuration system with .nrvcfg files:
- port: Server port (default: 8080)
- buffer_size: Request buffer size (default: 4096)
- thread_pool_size: Number of worker threads (default: 48)
- keep_alive_timeout: Keep-alive timeout in seconds (default: 5)
- cluster_thread: Number of cluster worker processes (default: 3)
- max_connections: Maximum concurrent connections (default: 500000)
- accept_queue_size: TCP accept queue size (default: 65535)
- accept_retry_delay_ms: Accept retry delay in milliseconds (default: 10)
- max_events: Maximum epoll events (default: 8192)
server {
port = 8080;
buffer_size = 8192; # Larger buffer for high throughput
thread_pool_size = 64; # More threads for high concurrency
keep_alive_timeout = 10; # Longer keep-alive for better performance
cluster_thread = 4; # More worker processes
max_connections = 1000000; # Higher connection limit
accept_queue_size = 131072; # Larger accept queue
accept_retry_delay_ms = 5; # Faster retry
max_events = 16384; # More epoll events
}server {
port = 8080;
buffer_size = 2048; # Smaller buffer for development
thread_pool_size = 8; # Fewer threads for debugging
keep_alive_timeout = 2; # Shorter timeout for testing
cluster_thread = 1; # Single worker process
max_connections = 1000; # Lower connection limit
accept_queue_size = 1024; # Smaller queue
accept_retry_delay_ms = 20; # Slower retry
max_events = 1024; # Fewer events
}server {
port = 80; # Standard HTTP port
buffer_size = 16384; # Large buffer for production
thread_pool_size = 128; # High thread count
keep_alive_timeout = 30; # Long keep-alive
cluster_thread = 8; # Multiple worker processes
max_connections = 2000000; # Very high connection limit
accept_queue_size = 262144; # Large accept queue
accept_retry_delay_ms = 1; # Fast retry
max_events = 32768; # High event limit
}server {
port = 8080;
buffer_size = 4096;
thread_pool_size = 48;
keep_alive_timeout = 5;
cluster_thread = 3;
max_connections = 500000;
accept_queue_size = 65535;
accept_retry_delay_ms = 10;
max_events = 8192;
}Server server = Server();
server.SetConfigFile("server"); // Loads server.nrvcfgNerva/
├── includes/ # Header files
│ ├── Core/ # Core server components
│ │ ├── Server/ # Server implementation
│ │ ├── Http/ # HTTP handling
│ │ │ ├── Request/ # Request/Response classes
│ │ │ ├── Router/ # Routing system
│ │ │ ├── Handler/ # Request handlers
│ │ │ └── Middleware/ # Middleware system
│ │ └── Cluster/ # Clustering support
│ ├── Secure/ # Security and configuration
│ │ └── Config/ # Configuration parser
│ ├── Utils/ # Utility functions
│ ├── Radix/ # Radix tree implementation
│ └── ViewEngine/ # Template engine system
├── src/ # Source files
├── public/ # Static files
├── views/ # HTML templates for view engine
│ ├── header.html # Header template
│ ├── footer.html # Footer template
│ ├── productCard.html # Product card component
│ ├── productPage.html # Product page template
│ └── notFound.html # 404 page template
├── server.nrvcfg # Server configuration file
├── lib/ # Library build directory
└── Makefile # Build configuration
Server(): Initialize server with default configurationServer(ServerConfig &config): Initialize server with custom configurationvoid Start(): Start the servervoid Stop(): Stop the servervoid Static(path, directory): Serve static filesvoid Set(key, value): Set server options (e.g., view engine, views directory)void Use(path, Router): Mount a Router at a pathGroup(path): Start a route group for modular routingGroup(path, middlewares, handler): Start a route group with middlewarevoid SetConfigFile(path): Set configuration file path
Get(path, middleware, handler): Register GET routePost(path, middleware, handler): Register POST routeUse(path, middleware): Apply middleware to pathRegister(path): Register a route for chaining["METHOD"].Use(path, middleware, handler): Method-specific routingThen(handler): Chain handler after middleware or groupGroup(path): Create a route groupGroup(path, middlewares, handler): Create a route group with middleware
Use(middleware): Add middleware to the groupThen(handler): Execute the group handler with middleware
getParam(name): Get route parametergetQuery(name): Get query parametergetHeader(name): Get request headergetBody(): Get request bodyconst FormData &Request::getFormData(const std::string &key) const: Returns multipart form field or file data.bool File::save(const std::string &path) const: Saves the uploaded file to the specified path.std::string Request::getHeader(const std::string &key) const: Gets request header value.bool Request::isMultipartFormData() const: Checks if request is multipart form data.
<< status << content: Send response with status code and contentSendFile(path): Serve a file directly with MIME type detectionMovedRedirect(location): Send 301 permanent redirectTemporaryRedirect(location): Send 302 temporary redirectsetHeader(key, value): Set custom response headerRender(view, data): Render a template with nlohmann::json datastd::string detectContentType(body): Automatically detect content typevoid setStatus(code, message): Set custom status code and messageResponse& setCookie(name, value, options): Set a cookie with optionsstd::optional<std::string> getCookie(name): Get cookie valuestd::string getCookieValue(name, defaultValue): Get cookie with defaultResponse& setSignedCookie(name, value, secret, options): Set signed cookiestd::optional<std::string> getSignedCookie(name, secret): Get signed cookievoid removeCookie(name, path, domain, secure): Remove a cookievoid handleClient(clientSocket): Enhanced client handling with timeout and keep-aliveint initSocket(port, listenQueueSize): Optimized socket initializationvoid acceptConnections(): Epoll-based connection acceptance
- Include System:
{{ include templateName }} - Include with Context:
{{ include templateName with variable }} - Conditionals:
{{ if condition }}...{{ endif }} - Loops:
{{ for item in collection }}...{{ endfor }} - Filters:
{{ value|filter:param }} - Data Binding: Direct nlohmann::json object access
- Template Caching: Automatic template caching for performance
Json::ParseAndReturnBody(jsonString): Parse and return JSON string using simdjsonnlohmann::json: Modern JSON library for template engine data binding
Cluster: Fork-based worker process managementforkWorkers(serverSocket, cpuCount): Create worker processessendShutdownSignal(workers): Graceful shutdown of workerswaitForWorkers(workers): Wait for worker termination
The server is optimized for high-performance scenarios:
- Epoll-based I/O: Efficient event-driven I/O handling
- Thread Pool: Configurable worker thread pool
- Non-blocking Sockets: Asynchronous connection handling
- Memory Efficient: Uses modern C++ features for optimal memory usage
- MIME Type Detection: Automatic content-type detection for files
- Radix Tree Routing: Fast route matching with parameter extraction
- tcmalloc Integration: High-performance memory allocator
- Template Caching: Compiled template caching for faster rendering
- High-Performance JSON: simdjson for fast JSON parsing
- Clustering: Fork-based worker processes for load distribution
- Dynamic Configuration: Runtime configuration loading
- Memory-Optimized Template Rendering: Direct response writing without intermediate string copies
The server has been tested with wrk benchmark tool under high load conditions:
wrk -t6 -c2000 -d10s http://localhost:8080/testTest Configuration:
- Threads: 6 worker threads
- Connections: 2000 concurrent connections
- Duration: 10 seconds
- Target: 404 Not Found endpoint
Results:
Running 10s test @ http://localhost:8080/test
6 threads and 2000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.66ms 12.56ms 248.06ms 95.03%
Req/Sec 34.53k 21.90k 82.11k 60.00%
2061090 requests in 10.09s, 2.98GB read
Requests/sec: 204,350.42
Transfer/sec: 302.65MB
Performance Metrics:
- Throughput: 204,350 requests/second
- Average Latency: 2.66ms
- Total Requests: 2,061,090 requests in 10.09 seconds
- Data Transfer: 302.65MB/second
- Connection Handling: 2000 concurrent connections
- CPU Utilization: 6 threads efficiently utilized
Key Performance Features:
- High Throughput: Over 200K requests/second
- Low Latency: Sub-3ms average response time
- Concurrent Handling: 2000 simultaneous connections
- Memory Efficiency: Optimized memory usage with tcmalloc
- Scalable Architecture: Fork-based clustering for load distribution
The Nerva template engine provides:
- Modular Design: Reusable template components (header, footer, cards)
- Data Binding: Seamless integration with nlohmann::json
- Conditional Rendering: Dynamic content based on data
- Iterative Rendering: Loop through collections and arrays
- Custom Filters: Extensible filter system for data transformation
- Include System: Template composition and reuse with context passing
- CSS Integration: Inline styling support in templates
- Template Caching: Automatic caching for improved performance
- Memory-Optimized Rendering: Direct response writing without intermediate string copies
The template engine has been optimized for memory efficiency:
Before (Memory Inefficient):
std::string render(const std::string &templateName, const json &context);
// Returns string, requires additional memory copyAfter (Memory Optimized):
void render(Http::Response &res, const std::string &templateName, const json &context);
// Directly writes to response body, no intermediate copiesBenefits:
- Reduced Memory Usage: Eliminates intermediate string copies
- Faster Rendering: Direct response writing
- Lower GC Pressure: Less temporary object creation
- Better Performance: Especially under high load
The Nerva clustering system provides:
- Fork-based Workers: Multiple worker processes for load distribution
- CPU-based Scaling: Automatic worker count based on CPU cores
- Graceful Shutdown: Proper signal handling and cleanup
- Process Management: Automatic worker process lifecycle management
- Load Distribution: Shared socket across worker processes
The server uses two JSON libraries for different purposes:
- simdjson: High-performance JSON parsing for
Json::ParseAndReturnBody()function - nlohmann/json: Modern JSON library for template engine data binding and rendering
The project supports multiple build targets:
- Executable:
make- Builds the main server executable - Shared Library:
make lib- Buildsnerva.soshared library - Install:
make install- Installs library and headers to system - Clean:
make clean- Removes build artifacts
# Install development tools
sudo apt install build-essential cmake valgrind gdb
# Install additional development libraries
sudo apt install libboost-all-dev libcurl4-openssl-dev
# Install code formatting tools
sudo apt install clang-format clang-tidy# Clone and setup
git clone https://github.com/wyrexdev/nerva.git
cd nerva
# Create development branch
git checkout -b feature/new-feature
# Build with debug symbols
make clean
CXXFLAGS="-g -O0 -DDEBUG" make
# Run with debugger
gdb ./server
# Run with valgrind for memory checking
valgrind --leak-check=full --show-leak-kinds=all ./server# Format code
find src/ includes/ -name "*.cpp" -o -name "*.hpp" | xargs clang-format -i
# Check code style
find src/ includes/ -name "*.cpp" -o -name "*.hpp" | xargs clang-tidy# Install testing framework
sudo apt install libgtest-dev
# Build and run tests
make test# Install testing tools
sudo apt install curl wrk ab
# Run performance tests
wrk -t6 -c2000 -d10s http://localhost:8080/test
# Run Apache Bench tests
ab -n 10000 -c 100 http://localhost:8080/test# Test basic functionality
curl http://localhost:8080/
curl -X POST http://localhost:8080/json
curl http://localhost:8080/test/123
# Test file upload
curl -X POST -F "file=@test.txt" http://localhost:8080/upload
# Test authentication
curl "http://localhost:8080/protected?token=123"Problem: Compilation fails with C++20 errors
error: 'concepts' is not a namespace-nameSolution: Ensure you have a C++20 compatible compiler
# Check compiler version
clang++ --version
# Should be clang++ 12+ or g++ 10+
# Update compiler if needed
sudo apt install clang-12Problem: Missing simdjson library
fatal error: 'simdjson.h' file not foundSolution: Install simdjson development package
sudo apt install libsimdjson-devProblem: Server fails to start with "Address already in use"
bind: Address already in useSolution: Check if port is already in use and kill the process
# Find process using port 8080
sudo lsof -i :8080
# Kill the process
sudo kill -9 <PID>
# Or use a different port in server.nrvcfgProblem: High memory usage
# Monitor memory usage
htop
# or
ps aux | grep serverSolution: Adjust configuration parameters
server {
thread_pool_size = 16; # Reduce thread count
max_connections = 10000; # Reduce connection limit
buffer_size = 2048; # Reduce buffer size
}Problem: Server crashes with segmentation fault
# Run with gdb for debugging
gdb ./server
(gdb) run
# When crash occurs, check backtrace
(gdb) btProblem: Low throughput Solution: Optimize configuration
server {
thread_pool_size = 64; # Increase threads
buffer_size = 8192; # Increase buffer
keep_alive_timeout = 10; # Increase keep-alive
}Problem: High latency Solution: Check system resources and optimize
# Monitor CPU and memory
top
iostat
vmstat
# Optimize system settings
echo 65535 > /proc/sys/net/core/somaxconn
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse// Add to your main.cpp
#define DEBUG_MODE 1
#include "Debug.hpp"
// Enable debug output
Debug::setLevel(DEBUG_LEVEL_VERBOSE);# Run with valgrind
valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes ./server
# Run with AddressSanitizer
CXXFLAGS="-fsanitize=address -g" make# Install profiling tools
sudo apt install perf
# Profile the application
perf record ./server
perf report// Add logging middleware
Middleware loggingMiddleware = Middleware([](Http::Request &req, Http::Response &res, auto next) {
std::cout << "[" << std::time(nullptr) << "] "
<< req.method << " " << req.path
<< " - " << res.statusCode << std::endl;
next();
});
server.Use("/", {loggingMiddleware});# Check server status
curl -I http://localhost:8080/
# Monitor system resources
htop
iotop
netstat -tulpn | grep 8080┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Client │ │ Load Balancer │ │ Nerva Server │
│ (Browser/App) │◄──►│ (Optional) │◄──►│ (Master) │
└─────────────────┘ └─────────────────┘ └─────────┬───────┘
│ fork()
▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Worker Process │ │ Worker Process │ │ Worker Process │
│ (Thread Pool) │ │ (Thread Pool) │ │ (Thread Pool) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
1. Client Request → 2. epoll Event → 3. Accept Thread → 4. Thread Pool → 5. Router → 6. Middleware → 7. Handler → 8. Response
- tcmalloc: High-performance memory allocator
- Template Caching: Compiled template storage
- Direct Response Writing: No intermediate buffers
- Connection Pooling: Reuse connections
// Always validate user input
std::string sanitizeInput(const std::string& input) {
// Remove potentially dangerous characters
std::string sanitized = input;
// Implementation details...
return sanitized;
}// Validate file uploads
server.Post("/upload", {}, [](const Http::Request &req, Http::Response &res, auto next) {
auto fileData = req.getFormData("file");
// Check file size
if (fileData.file.size() > MAX_FILE_SIZE) {
res << 413 << "File too large";
return;
}
// Check file type
if (!isAllowedFileType(fileData.filename)) {
res << 400 << "File type not allowed";
return;
}
// Save file safely
std::string safePath = sanitizePath("./uploads/" + fileData.filename);
fileData.file.save(safePath);
});// Use secure session management
Middleware secureAuth = Middleware([](Http::Request &req, Http::Response &res, auto next) {
auto sessionId = res.getSignedCookie("session", SECRET_KEY);
if (!sessionId || !isValidSession(*sessionId)) {
res << 401 << "Unauthorized";
return;
}
next();
});This project is licensed under the MIT License - see the LICENSE file for details.
We welcome contributions! Please follow these steps:
-
Fork the repository
git clone https://github.com/wyrexdev/nerva.git cd nerva -
Create a feature branch
git checkout -b feature/amazing-feature
-
Make your changes
- Follow the coding style guidelines
- Add tests for new features
- Update documentation
-
Test your changes
make clean && make make test
-
Submit a pull request
- Provide a clear description of changes
- Include any relevant issue numbers
- Ensure all tests pass
- Use C++20 features appropriately
- Follow consistent naming conventions
- Add comments for complex logic
- Keep functions small and focused
- Use meaningful variable names
- Write unit tests for new features
- Ensure existing tests pass
- Add integration tests for API changes
- Test performance impact of changes
- Operating System: Linux (uses epoll for event handling)
- Architecture: x86_64, ARM64
- Memory: Minimum 512MB RAM, recommended 2GB+
- Storage: 100MB free space for build artifacts
- Compiler: C++20 compatible (clang++ 12+ or g++ 10+)
- Libraries:
- simdjson (for high-performance JSON parsing)
- nlohmann/json (for template engine data binding)
- OpenSSL (for cryptographic functions)
- tcmalloc (optional, for better performance)
- Build Tools: make, cmake
- Debugging: gdb, valgrind
- Testing: curl, wrk, ab
- Code Quality: clang-format, clang-tidy
Made with ❤️ and ☕
For more detailed documentation, see: