Source Code
C++ backend server
logger.h, logger.c
Define and implement the Logger
class, which provides convenient ways of outputting information based on severity level (Debug, Info, Warning, Error, Fatal)
config_parser.h, config_parser.cc
Define and implement the NginxConfig
and NginxConfigParser
class, which takes Nginx config file as input, determine the validity of the config file, and extract informations.
server.h, server.cc
Define and implement the server
class which creates all necessary tools (request handlers and config parsers) for parsing requests. It also creates a session
object to listen to requests.
session.h, session.cc
Define and implement the session
class which listens to and parse the HTTP requests. The requests are dispatched to specific request handler for further processing.
echo_request_handler_factory.h, echo_request_handler_factory.cc
Define and implement the EchoRequestHandlerFactory
class, a factory class for creating echo_request_handler
.
static_request_handler_factory.h, static_request_handler_factory.cc
Define and implement the StaticRequestHandlerFactory
class, a factory class for creating static_request_handler
.
request_handler_factory.h, request_handler_factory.cc
Define and implement the virtual class RequestHandlerFactory
, a factory class for creating request_handler. All specific factory class of request handler should be inherited from this class.
request_handler.h
Defined and implement the virtual class RequestHandler
for extracting information from requests and generate appropriate response message. All specific class of request handler should be inherited from this class.
echo_request_handler.h, echo_request_handler.cc
Define and implement the EchoRequestHandler
class, for echoing back request message with appropriate header appended in the front.
static_request_handler.h, static_request_handler.cc
Define and implement the StaticRequestHandler
class, for reading the files specified in the request and return the file content.
not_found_handler.h, not_found_handler.cc
Define and implement the NotFoundHandler
class, for dealing with invalid requests or unfound resources.
sleep_request_handler_factory.h, sleep_request_handler_factory.cc
Define and implement the SleepRequestHandlerFactory
class, for creating SleepRequestHandler
object.
sleep_request_handler.h, sleep_request_handler.cc
Define and implement the SleepRequestHandler
class, for making the server sleep for 1 second.
health_request_handler_factory.h, health_request_handler_factory.cc
Define and implement the HealthRequestHandlerFactory
class, for creating HealthRequestHandler
object.
health_request_handler.h, health_request_handler.cc
Define and implement the HealthRequestHandler
class, for reporting the server status.
markdown_request_handler_factory.h, markdown_request_handler_factory.cc
Define and implement the Markdown_RequestHandlerFactory
class, for creating MarkdownRequestHandler
object.
markdown_request_handler.h, markdown_request_handler.cc
Define and implement the MarkdownRequestHandler
class, for translating markdown text into HTML text.
Angular Frontend
angular-blog/src/app/blog.service.ts
Implement functions for the frontend application to interact with backend server CRUD API.
angular-blog/src/app/app.component.ts
Implement the application logic for displaying markdown posts to user.
angular-blog/src/app/preview
Implement the markdown rendering component in the web applciation.
angular-blog/src/app/list
Implement the list component that displays all the posts in a directory.
angular-blog/src/app/edit
Implement the edit component that displays the markdown post content and allow the user to edit it.
Build the code
We would like to perform an out-of-source build, so the first step is to create a build directory.
mkdir build
cd build
Then, calling cmake and make sequentially to build the target program.
cmake ..
make
For docker build, we can use the following commands.
docker build -f docker/base.Dockerfile -t snore:base . && docker build -f docker/Dockerfile -t my_image . && docker run --rm -p 80:80 --name my_run my_image
To build the angular app
ng build --base-href=/ --deploy-url=/ --prod=true
Test the code
For testing of a single module, we could use gtest library. For example,
#include "gtest/gtest.h"
#include "echo_request_handler.h"
#include <vector>
class EchoTestFixture : public ::testing::Test
{
protected:
std::vector<std::string> locations = {"/static", "EchoHandler", "./static"};
};
TEST_F(EchoTestFixture, test_empty_request)
{
//something here
}
After including the necessary libraries, we first define a class as a subclass of testing::Test
, the variables within the class is shared for all test cases.
Each TEST_F
block is a test case, which takes in two argument, the first one is the class we defined, and the second one is the name of the specific test.
After writing the test file, we need to include it in the CMakeLists.txt for compilation.
gtest_discover_tests(echo_test WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/tests)
We can also test the whole program using integration_test. Details can be seen in integration_test.sh.
Run the code
The compiled executable is bin/server, so we call the following to run the code (in the build directory).
bin/server ../config_files/deploy_config
This command will open the server at port 80.
Or we can run the following to run the server at port 8080.
bin/server ../config_files/ex_config
To run the angular frontend app
cd angular-blog
npm install
npm start
Add a request handler
Firstly, we need to add a request handler factory class as a subclass of RequestHandlerFactory.
class RequestHandlerFactory
{
public:
RequestHandlerFactory(std::map<std::string, std::string> args);
virtual RequestHandler* create() = 0;
protected:
std::map<std::string, std::string> m_args;
};
The created class should have a create
method to return a dynamically allocated RequestHandler.
Next, we need to define the specific handler class, which should be the subclass of RequestHandler
.
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
class RequestHandler
{
public:
virtual int serve(const http::request<http::string_body> req, http::response<http::string_body> &res) = 0;
};
The added request handler should implement the serve method, which takes in a http::request<http::string_body> req
, and a http::response<http::string_body> &res
. The first argument is the received HTTP request, and the second argument is the referenced HTTP response. The returned int is the status code in which 0 represents success and all other codes represent failure.
Take echo handler as an example, the factory method has the following interface and implementations.
//echo_request_handler_factory.h
class EchoRequestHandlerFactory : public RequestHandlerFactory
{
public:
EchoRequestHandlerFactory(std::map<std::string, std::string> args);
RequestHandler* create();
};
//echo_request_handler_factory.cc
EchoRequestHandlerFactory::EchoRequestHandlerFactory(std::map<std::string, std::string> args)
: RequestHandlerFactory(args)
{}
RequestHandler* EchoRequestHandlerFactory::create()
{
return new EchoRequestHandler();
}
The EchoRequestHandler only overwrites the create method with a new EchoRequestHandler()
.
class EchoRequestHandler : public RequestHandler
{
public:
EchoRequestHandler();
int serve(const http::request<http::string_body> req, http::response<http::string_body> &res);
};
EchoRequestHandler::EchoRequestHandler()
{
}
int EchoRequestHandler::serve(const http::request<http::string_body> req, http::response<http::string_body> &res)
{
res = {http::status::ok, req.version()};
res.set(http::field::content_type, "text/plain");
std::stringstream header_buf;
header_buf << req.base(); // convert header to string
res.body() = header_buf.str() + req.body();
res.prepare_payload();
return 0;
}
The echo handler overwrites the serve method by setting appropriate headers of response and then appending the request body to the end. After setting the headers and body of the request, res.prepare_payload()
is called to convert it into a real response.