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.
.
├── 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
└── ...
- C++17 compatible compiler
- CMake 3.10 or higher
- Boost libraries 1.66 or higher (system, log, log_setup, regex)
-
Create a build directory:
mkdir build && cd build
-
Configure the project:
cmake ..
-
Build the project:
make
-
Run the tests:
make test
-
Create a code coverage directory:
mkdir build && cd build_coverage
-
Generate code coverage:
make coverage
-
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.
The server requires a configuration file that specifies the port to listen on and the routes to handle:
./build/bin/server path/to/configExample configuration (see tests/config for a complete example):
server {
listen 80;
routes {
location /static {
handler static;
base_dir /static;
}
location /echo {
handler echo;
}
}
}
To add a new request handler to the server, follow these steps:
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;
};
#endifCreate 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;
}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
)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>();
}Update session.cc to handle your new handler type:
// In build_response() method
else if (handler_type == "example")
{
handler = std::make_unique<ExampleHandler>();
}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");
}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)Add your test to the coverage report generation:
generate_coverage_report(
TARGETS
server
# ... existing targets ...
request_handler_lib
TESTS
# ... existing tests ...
example_handler_test
)The EchoHandler is a simple example of a request handler that echoes back the request:
#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#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;
}#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);
}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;
};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;
};For more details on the server architecture and implementation, refer to the following files:
include/request_handler.h: Base class for all request handlersinclude/server.h: Server implementationinclude/session.h: Session managementtests/config: Example configuration file
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.