Skip to content

Latest commit

 

History

119 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Runtime Terrors HTTP Server

A modular, extensible HTTP server built with Boost.Beast. This server supports serving static files and echo responses, with an easy-to-extend architecture for adding new request handlers.

Project Structure

.
├── CMakeLists.txt         # Main build configuration
├── include/               # Header files
│   ├── echo_handler.h     # Echo request handler definition
│   ├── request_handler.h  # Base request handler interface
│   ├── static_handler.h   # Static file handler definition
│   └── ...
├── src/                   # Implementation files
│   ├── echo_handler.cc    # Echo handler implementation
│   ├── static_handler.cc  # Static file handler implementation
│   ├── server.cc          # Server implementation
│   └── ...
└── tests/                 # Test files and configurations
    ├── config             # Example server configuration
    ├── echo_handler_test.cc # Unit tests for echo handler
    ├── integration_test.sh  # Generic integration test
    └── ...

Building the Project

Prerequisites

  • C++17 compatible compiler
  • CMake 3.10 or higher
  • Boost libraries 1.66 or higher (system, log, log_setup, regex)

Build Steps

  1. Create a build directory:

    mkdir build && cd build
  2. Configure the project:

    cmake ..
  3. Build the project:

    make
  4. Run the tests:

    make test

Generating Code Coverage

  1. Create a code coverage directory:

    mkdir build && cd build_coverage
  2. Generate code coverage:

    make coverage
  3. Find your report

The report is generally in build_coverage/report/index.html. It is recommended to install live-server, then right-click to open the file.

Running the Server

The server requires a configuration file that specifies the port to listen on and the routes to handle:

./build/bin/server path/to/config

Example configuration (see tests/config for a complete example):

server {
  listen   80;
  routes {
    location /static {
      handler static;
      base_dir /static;
    }
    location /echo {
      handler echo;
    }
  }
}

Adding a New Request Handler

To add a new request handler to the server, follow these steps:

1. Create Handler Header File

Create a new header file in the include directory that inherits from the RequestHandler base class:

// include/example_handler.h
#ifndef EXAMPLE_HANDLER_H
#define EXAMPLE_HANDLER_H

#include <boost/beast.hpp>
#include "request_handler.h"
#include "types.h"

namespace beast = boost::beast;
namespace http = beast::http;

/**
 * @class ExampleHandler
 * @brief Example request handler that demonstrates a new functionality
 */
class ExampleHandler : public RequestHandler
{
public:
    /**
     * @brief Handles example requests
     *
     * @param request HTTP request to process
     * @param base_dir Base directory (may be used for configuration)
     * @return HTTP response generated based on the request
     */
    http::response<http::string_body> handle_request(const request& request, const std::string &base_dir) override;
};

#endif

2. Implement the Handler

Create a new implementation file in the src directory:

// src/example_handler.cc
#include "example_handler.h"

namespace beast = boost::beast;
namespace http = beast::http;

http::response<http::string_body> ExampleHandler::handle_request(const request& request, const std::string &base_dir)
{
    http::response<http::string_body> response{http::status::ok, request.version()};

    // Add your handler logic here
    response.set(http::field::content_type, "text/plain");
    response.body() = "Example response";

    response.prepare_payload();
    return response;
}

3. Add to Build System

Update CMakeLists.txt to include your new handler:

# Add your handler to the request_handler_lib
add_library(request_handler_lib
    src/echo_handler.cc
    src/static_handler.cc
    src/example_handler.cc  # Add your handler here
    src/request_handler_factory.cc
    src/registry.cc
)

4. Update Request Handler Factory

Modify request_handler_factory.cc to create instances of your new handler:

// In RequestHandlerFactory constructor
if ((handler_ != "EchoHandler") && (handler_ != "StaticHandler") && (handler_ != "ExampleHandler"))
{
    BOOST_LOG_SEV(Logger::Get(), boost::log::trivial::error)
        << "RequestHandlerFactory: Invalid handler type: " << handler_;
}

// In the create() method
if (handler_ == "ExampleHandler")
{
    return std::make_unique<ExampleHandler>();
}

5. Add to Session

Update session.cc to handle your new handler type:

// In build_response() method
else if (handler_type == "example")
{
    handler = std::make_unique<ExampleHandler>();
}

6. Write Tests

Create a test file for your handler:

// tests/example_handler_test.cc
#include "gtest/gtest.h"
#include "example_handler.h"
#include <boost/beast/http.hpp>

namespace http = boost::beast::http;

class ExampleHandlerTest : public ::testing::Test
{
protected:
    ExampleHandler handler;
    http::request<http::string_body> request;

    void SetUp() override
    {
        request.version(11);
        request.method(http::verb::get);
        request.target("/example");
        request.set(http::field::host, "localhost");
    }
};

TEST_F(ExampleHandlerTest, HandlesRequestCorrectly)
{
    auto response = handler.handle_request(request, "");

    EXPECT_EQ(response.result(), http::status::ok);
    EXPECT_EQ(response[http::field::content_type], "text/plain");
    EXPECT_EQ(response.body(), "Example response");
}

7. Update CMakeLists.txt for Tests

Add your test to the CMakeLists.txt:

# Example handler test
add_executable(example_handler_test tests/example_handler_test.cc)
target_link_libraries(example_handler_test
    request_handler_lib
    logger_lib
    gtest
    gtest_main
)
gtest_discover_tests(example_handler_test)

8. Update Coverage Configuration

Add your test to the coverage report generation:

generate_coverage_report(
    TARGETS
        server
        # ... existing targets ...
        request_handler_lib
    TESTS
        # ... existing tests ...
        example_handler_test
)

Well-Documented Example: Echo Handler

The EchoHandler is a simple example of a request handler that echoes back the request:

Header File (include/echo_handler.h):

#ifndef ECHO_HANDLER_H
#define ECHO_HANDLER_H

#include <boost/beast.hpp>
#include "request_handler.h"
#include "types.h"

namespace beast = boost::beast;
namespace http = beast::http;

/**
 * @class EchoHandler
 * @brief Request handler that echoes back the original request
 */
class EchoHandler : public RequestHandler
{
public:
    /**
     * @brief Handles echo requests by returning the original request
     *
     * @param request HTTP request parsed as a string
     * @param base_dir Base directory (unused for echo handler)
     * @return HTTP response with the original request as body
     */
    http::response<http::string_body> handle_request(const request& request, const std::string &base_dir) override;
};

#endif

Implementation (src/echo_handler.cc):

#include "echo_handler.h"

namespace beast = boost::beast;
namespace http = beast::http;

http::response<http::string_body> EchoHandler::handle_request(const request& request, const std::string &base_dir)
{
    http::response<http::string_body> response{http::status::ok, request.version()};

    // Serialize the full request (start‑line, headers, blank line, body)
    std::ostringstream ss;
    ss << request;
    response.body() = ss.str();

    response.set(http::field::content_type, "text/plain");
    response.prepare_payload();
    return response;
}

Test File (tests/echo_handler_test.cc):

#include "gtest/gtest.h"
#include "echo_handler.h"
#include <boost/beast/http.hpp>
#include <sstream>

namespace http = boost::beast::http;

class EchoHandlerTest : public ::testing::Test
{
protected:
    EchoHandler handler;
    http::request<http::string_body> request;

    void SetUp() override
    {
        // Setup a basic request
        request.version(11); // HTTP/1.1
        request.method(http::verb::get);
        request.target("/echo");
        request.set(http::field::host, "localhost");
        request.body() = "Test Body";
        request.prepare_payload();
    }
};

TEST_F(EchoHandlerTest, HandleRequest_EchoesRequestStringCorrectly)
{
    // Convert request to string
    std::ostringstream oss;
    oss << request;
    std::string request_string = oss.str();

    // Act
    auto response = handler.handle_request(request, "");

    // Assert
    EXPECT_EQ(response.result(), http::status::ok);
    EXPECT_EQ(response[http::field::content_type], "text/plain");
    EXPECT_EQ(response.body(), request_string);
}

Key Components

Request Handler Interface (include/request_handler.h)

The RequestHandler class defines the interface that all handlers must implement:

/**
 * @class RequestHandler
 * @brief Abstract base class for HTTP request handlers.
 */
class RequestHandler
{
public:
    /**
     * @brief Virtual destructor for safe polymorphic deletion.
     */
    virtual ~RequestHandler() = default;

    /**
     * @brief Handles an HTTP request and returns the appropriate response.
     *
     * @param request The HTTP request to process.
     * @param base_dir The base directory relevant to the handler.
     * @return HTTP response generated based on the request.
     */
    virtual response handle_request(const request& request,
                                   const std::string& base_dir) = 0;
};

Request Handler Factory (include/request_handler_factory.h)

The RequestHandlerFactory creates handler instances based on configuration:

/**
 * @class RequestHandlerFactory
 * @brief Creates request_handlers with the create() method for each request
 */
class RequestHandlerFactory {
public:
    /**
     * @brief Constructor for class
     *
     * @param handler_type either "EchoHandler" or "StaticHandler"
     * @param root_path    root directory path for static files, empty for echo
     */
    RequestHandlerFactory(const std::string &handler_type, const std::string &root_path = "");

    /**
     * @brief Creates a request handler based on factory configuration
     *
     * @return unique pointer to the created request handler
     */
    std::unique_ptr<RequestHandler> create() const;
};

Further Resources

For more details on the server architecture and implementation, refer to the following files:

  • include/request_handler.h: Base class for all request handlers
  • include/server.h: Server implementation
  • include/session.h: Session management
  • tests/config: Example configuration file

Contributing

We welcome contributions to improve the server! Please ensure your code passes all tests and follows the style of the existing codebase. Add appropriate tests for any new functionality.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages