Skip to content

Repository files navigation

RawServe

A multithreaded HTTP/1.1 web server built from scratch in C++ on Linux.

RawServe was built to deeply understand the low-level mechanics of web servers. By writing the server from scratch, this project provides hands-on exploration of POSIX networking, HTTP parsing, routing, concurrency, synchronization, and resource management.

What makes this project interesting?

RawServe was not built using an existing HTTP framework. The project directly combines:

POSIX sockets
+
custom HTTP parser
+
custom router
+
custom HashMap
+
ThreadPool
+
middleware
+
static file server
+
JSON API support

This makes it an educational systems engineering project demonstrating how these fundamental components interact to form a working web server.

Architecture

The architecture is divided into application setup and request processing. RawServe uses a blocking POSIX-socket server model with a fixed worker ThreadPool.

Application setup:

main
 ├── create Router
 ├── register routes/middleware
 └── create Server(router)
          ↓
       start()

Request processing:

Client
  ↓
Server
  ↓
accept()
  ↓
ThreadPool
  ↓
Worker
  ↓
Read HTTP bytes
  ↓
Parser
  ↓
Request
  ↓
Router
  ├── Middleware
  ├── Exact/Dynamic Route
  └── Static File Fallback
        ↓
    Controller
        ↓
     Response
        ↓
      send()
        ↓
      Client

main() configures the application while the Server and ThreadPool execute the request lifecycle. The parser creates a structured Request object from raw bytes, which is then passed to the Router. The Router coordinates middleware execution, route matching, and static file fallback to determine the correct controller logic.

Features

  • Networking: Direct use of Linux POSIX socket APIs (socket(), bind(), listen(), accept(), SO_REUSEADDR, SO_RCVTIMEO, SO_SNDTIMEO, and handling of partial sends).
  • HTTP Parsing: Parsing of HTTP/1.1 request lines, case-insensitive headers (normalized to lowercase), query parameters (including URL percent-decoding and + handling), and request bodies via Content-Length.
  • Routing: Exact route matching, HTTP method-based routing (GET, POST, PUT, PATCH, DELETE), and support for multiple dynamic path parameters (e.g., /user/:id/post/:postId).
  • Middleware: Middleware functions can inspect requests and either continue processing or halt and return a response immediately.
  • Static Files: Serves static files from a public/ directory with MIME type detection and 404/403 error handling.
  • JSON API Support: Provides JSON parsing and JSON response generation using nlohmann/json, including application/json response headers.
  • Concurrency: A custom ThreadPool orchestrating std::thread workers and synchronization primitives.
  • Custom HashMap: A custom bucket-based HashMap implementation for route storage.

TCP Stream Handling and HTTP Parsing

TCP is a byte stream rather than a message protocol. RawServe accumulates incoming bytes until the HTTP header terminator:

\r\n\r\n

is detected, and then uses the Content-Length header to determine how much request body data is required.

RawServe implements a learning-oriented subset of HTTP/1.1 sufficient for the implemented request, routing, static-file, JSON, and connection-handling features. Persistent connections are supported at a basic sequential request level; advanced pipelining and more complex stream-state handling are not implemented.

Routing and Static-file Fallback

RawServe supports strict exact matches (e.g., GET /about) and dynamic path parameters (e.g., GET /user/:id).

The Router also implements a GET fallback that attempts static-file serving: if no exact/dynamic application route matches a GET request, the Router attempts to serve a file from public/.

GET request
   ↓
exact/dynamic application route?
   ├── yes → controller
   └── no → static-file fallback (serves from public/)

Middleware

Middleware executes sequentially before the target controller. The middleware signature receives the Request by const reference and returns a Response*:

std::function<Response*(const Request&)>
  • If nullptr is returned, the router proceeds to the next middleware or the controller.
  • If a valid Response* is returned, the request is immediately halted and the response is dispatched.

ThreadPool and Concurrency

To handle concurrent clients, RawServe implements a fixed-size custom ThreadPool. The final implementation uses:

  • A fixed number of worker threads.
  • A synchronized task queue of type std::queue<int> containing client socket file descriptors, along with a per-client handler callback (std::function<void(int)>).
  • std::mutex and std::condition_variable to safely coordinate task distribution.
  • Workers wait when no task is available.
  • The server submits accepted client socket descriptors to the queue.
  • Workers execute the handler for the client, blocking on network I/O.
  • Workers are properly joined during shutdown.

Because I/O operations inside the task are blocking, the number of simultaneously processed client connections is bounded by the fixed worker count; additional accepted connections wait in the task queue. This avoids unlimited thread creation while ensuring the main thread remains available to accept new connections.

Graceful Shutdown

Handling Ctrl+C (SIGINT) initiates a server shutdown sequence:

Ctrl+C
 ↓
SIGINT handler
 ↓
running flag changes
 ↓
accept loop stops
 ↓
active client connections are shut down
 ↓
workers finish/exit
 ↓
ThreadPool joins workers
 ↓
server socket is cleaned up

Custom HashMap

The project features a custom HashMap used internally for route storage. It implements a hash function, a bucket array, and handles collisions via linked lists. Standard operations like insertion, lookup, and removal are supported (Note: this is an educational implementation and is not optimized or production-grade).

Repository Structure

web_server/
├── main.cpp
├── server.h
├── router.h
├── parser.h
├── request.h
├── response.h
├── threadpool.h
├── hashmap.h
├── http_exception.h
├── file_util.h
├── utils.h
└── public/

Build and Run

Compile the server using standard GCC (requires C++17 and pthreads). Ensure nlohmann/json is available on your system.

g++ main.cpp -std=c++17 -pthread -o main
./main

Usage Examples

Below are realistic usage examples demonstrating the actual RawServe API structure.

Basic Setup and Middleware

#include "request.h"
#include "response.h"
#include "router.h"
#include "server.h"
#include <iostream>

int main() {
    Router router;

    // Logging Middleware
    router.use([](const Request& request) -> Response* {
        std::cout << request.method
                  << " "
                  << request.path
                  << std::endl;

        return nullptr; // Continue processing
    });

    // Example of middleware rejecting a request
    router.use([](const Request& request) -> Response* {
        if (request.headers.find("authorization") == request.headers.end()) {
            std::string body = "Unauthorized";
            std::vector<std::string> headers = {
                "Content-Type: text/plain",
                "Content-Length: " + std::to_string(body.size())
            };
            return new Response(401, headers, body); // Stop the request
        }
        return nullptr;
    });

    // ... route registration ...

    Server server(&router);
    server.start();

    return 0;
}

Controllers

Controllers take a const Request* and return a Response*.

// Exact route
Response* home(const Request* request) {
    std::string body = "This is home page";

    std::vector<std::string> headers;
    headers.push_back("Content-Type: text/plain");
    headers.push_back(
        "Content-Length: " + std::to_string(body.size())
    );

    return new Response(200, headers, body);
}

// Dynamic route
Response* get_user(const Request* request) {
    std::string id = request->params.at("id");

    std::string body = "User ID: " + id;
    std::vector<std::string> headers = {
        "Content-Type: text/plain",
        "Content-Length: " + std::to_string(body.size())
    };

    return new Response(200, headers, body);
}

int main() {
    Router router;
    
    router.get("/", home);
    router.get("/user/:id", get_user);
    
    Server server(&router);
    server.start();
    
    return 0;
}

Security Considerations

RawServe implements safeguards such as:

  • Path traversal protection and filesystem canonicalization for static file serving.
  • Request-size limits.
  • Receive and send socket timeouts.

These measures reduce several common risks, but the project has not undergone a security audit and should not be deployed as production infrastructure.

Current Limitations

  • Linux/POSIX focused: Reliance on Linux socket APIs means it does not port natively to Windows.
  • Blocking socket I/O: Network I/O is fully blocking, which occupies worker threads during client read/writes.
  • Fixed-size ThreadPool: Bounded concurrency means the server can only handle a fixed number of simultaneous active connections.
  • No epoll / non-blocking event loop: Does not utilize asynchronous multiplexing I/O.
  • Limited HTTP compliance: Implements a functional subset but does not claim full RFC-compliance.
  • No TLS/HTTPS: Unencrypted HTTP traffic only.
  • No chunked transfer encoding.
  • No HTTP/2 or HTTP/3.
  • Limited testing: Lacks comprehensive automated testing or benchmarking frameworks.
  • Not production ready: Built for educational purposes, missing production security audits.

Future Improvements

Future work on this project may include:

  • epoll and event-driven architecture
  • Non-blocking sockets
  • TLS/HTTPS support
  • Chunked transfer encoding
  • Stronger HTTP compliance
  • HTTP/2 or WebSockets multiplexing
  • Automated testing and benchmarks
  • Configuration files and structured logging

License

RawServe is licensed under the MIT License.

About

A multithreaded HTTP/1.1 web server built from scratch in C++, featuring custom routing, middleware, static file serving, JSON APIs, request parsing, and a thread pool.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages