Categorieën
C++

A Microserver built with C++

Introduction

Microservices architectures are becoming more common as a deploymentmodel for componentized systems. I will not go in depth on microservice architectures in this article. Martin Fowler gives a description of the microservice architecture on his website; see the following article about microservices from Martin Fowler and James Lewis.

I will show an implementation of a microservices server. This implementation uses minimal resources. The server will be deployed separately from the services that use this server to provide services to the world.

Prerequisites

This server is based on the Poco Project libraries (version 1.6.1); see Poco Project. This server must be compiled with an C++11 compatible compiler. CMake (3.3.2) is used to prepare the makefiles (GNU make 3.81) to compile and link the microserver (and to compile and link the services that use the microserver) . The microserver software is developed on OS X Yosemite (10.10.5) with clang (700.1.81). The microserver has been tested on OS X Yosemite (10.10.5) and Ubuntu 14.04. My continuous integration system is running Jenkins on Ubuntu 14.04.

Running the microserver

After compiling you can start the microserver application. The application’s configuration must be stored in a property file (the file extension for the property file must be .properties). By default the search path for the property file is the directory in which the application executable is stored, if not found at that location then /etc/executable name is searched and after that the /etc directory is searched for the property file. A location can be given by the means of the command line argument –c or ––config with a full path to the property file. When this command line argument is given, it will have precedence over the default search paths. So with this command line argument the microserver application will not look into the default paths for a configuration file. When the microserver application is started as a daemon, it will change the current working directory to “/” (the root of the filesystem).

Note: Due to the fact that I can only develop and test under MAC OS X and linux, I didn’t make the microserver search for the configuration file in the standard ms-windows locations. Perhaps someone can test and deliver a patch (when needed) to create this functionality on windows.

In the configuration file the services are defined. Services are defined by an url, a library that implements the service and the fully qualified class that acts as the provider. A full path to the implementing library must also be given in the property file. When an URI is not defined in the configuration file the microserver will respond with a default 404 Not Found HTTP error.

The microserver can use lazyloading to load the services libraries. The lazyloading property can be defined in the configuration (properties) file. When not defined in the configuration file the default value will be false, which means no lazyloading. This (first) version of microserver cannot unload libraries. To unload libraries you need to stop and start the microserver.

See below for an example configuration file.

A status message can be requested (by for instance a loadbalancer) to get a 200 OK HTTP message. A response to a status request will contain a JSON with a status 200 (OK) message, this is the HTTP message to indicate that this server is servicing requests. A status request can be requested by requesting the following URI: http://host:port/status. Of course the host is the hostname of the server on which the microserver is deployed. The port is defined in the configuration file. When the port is not defined in the configuration file it falls back to the default port which is 9980.

Creating a library to handle requests

Class diagram

The microserver uses dynamic classloading from the poco libraries. The provider class must inherit from AbstractRequestHandler; as described in the poco documentation all classes loaded by a classloader must have a common base class. A base class is necessary because the microserver needs an interface to access the provider class. For the microserver this means that provider classes must inherit from AbstractRequestHandler. The provider class acts as a factory for classes that handle the actual requests. The classes that handle the actual requests must inherit from HTTPRequestHandler.

Configuration

Configuration items that (together) define a library and a provider class start with ‘microserver.library’. The following example defines a library with the name ms-hello-world-lib.

microserver.library.ms-hello-world-lib = /helloWorld
microserver.library.ms-hello-world-lib.path = /var/microservices/
microserver.library.ms-hello-world-lib.class = hello_world::MicroServerRequestHandlerProvider

It is advised to use namespaces in the library (isn’t it always) that implements the provider class. I have the preference to reuse the class name MicroServerRequestHandlerProvider for consistency. By using a namespace the actual provider classes can use the same descriptive name and be used next to each other.

The full path must be given to the path configuration item, because the dynamic class loader uses system wide predefined search paths. By using the full path name you can be sure that the dynamic class loader will find your library.

The configuration item that defines the library name (ms-hello-world-lib in the above example) also defines the URI that point to the service(s) which are provided. All URI’s that start with /helloWorld in the above example will be handled by hello_world::MicroServerRequestHandlerProvider. This means for instance that host:port//helloWorld, but also host:port//helloWorld/SaySomething will be handled by the same provider class.

Logging

Logging is defined in the configuration file. The default logger is the root logger and all loggers inherit from this logger. The logging framework of the poco project is structured as a logging hierarchy. See the poco project for more information. All microserver classes define their own logger, which inherits from the root logger. The logger name is the class name of the class in which the logger is defined. For example the logger that is defined in the class MicroServer goes by the name MicroServer. When needed you can define different log levels for different classes. See the HelloWorld example for a setup with different log levels for different classes.

The unavoidable HelloWorld example

To show the setup and working of microserver a HelloWorld example is created. To play with the HelloWorld example you can download the microserver and the HelloWorld sources from github and build the microserver and the HelloWorld library with CMake. When you want to see the microserver in action without building it yourself? Go to HelloWorld.

MicroserverHelloWorldRequestHandler.h

#ifndef __MICROSERVERHELLOWORLDREQUESTHANDLER_H__
#define __MICROSERVERHELLOWORLDREQUESTHANDLER_H__

#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Logger.h"

using Poco::Net::HTTPRequestHandler;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPServerResponse;
using Poco::Logger;

namespace ms_helloworld {

class MicroServerHelloWorldRequestHandler : public HTTPRequestHandler {
public:
  MicroServerHelloWorldRequestHandler();

  void handleRequest(HTTPServerRequest &request, HTTPServerResponse &response);

private:
  Logger &l = Logger::get("MicroServerHelloWorldRequestHandler");
};

} // namespace ms_helloworld
#endif //__MICROSERVERHELLOWORLDREQUESTHANDLER_H__

MicroserverHelloWorldRequestHandler.cpp

#include "MicroServerHelloWorldRequestHandler.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"

using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPResponse;

namespace ms_helloworld {

    MicroServerHelloWorldRequestHandler::MicroServerHelloWorldRequestHandler() { }

    void MicroServerHelloWorldRequestHandler::handleRequest(HTTPServerRequest &request, HTTPServerResponse &response) {
        l.information("Request from " + request.clientAddress().toString());

        response.setChunkedTransferEncoding(true);
        response.setContentType("text/html");

        std::ostream &ostr = response.send();
        ostr << "<html><head><title>MicroServerRequestHandler powered by POCO C++ Libraries</title>";
        ostr << "<meta http-equiv=\"refresh\" content=\"30\"></head>";
        ostr << "<body>";
        ostr << "Hello World! MicroServerHelloWorldRequestHandler.";
        ostr << "</body></html>";
    } 
} // namespace ms_helloworld 

MicroServerRequestHandlerProvider.h

#ifndef __MICROSERVERREQUESTHANDLERPROVIDER_H__
#define __MICROSERVERREQUESTHANDLERPROVIDER_H__

#include "AbstractRequestHandler.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/ClassLibrary.h"

using Poco::Net::HTTPRequestHandler;

namespace ms_helloworld {
class MicroServerRequestHandlerProvider : public AbstractRequestHandler {
public:
  HTTPRequestHandler* getRequestHandler(std::string uri);
};

} //namespace ms_helloworld
#endif //__MICROSERVERREQUESTHANDLERPROVIDER_H__

MicroServerRequestHandlerProvider.cpp

#include "MicroServerRequestHandlerProvider.h"
#include "MicroServerHelloWorldRequestHandler.h"

namespace ms_helloworld {

HTTPRequestHandler *
MicroServerRequestHandlerProvider::getRequestHandler(std::string uri) {
  if (uri == "/helloworld") {
    return new MicroServerHelloWorldRequestHandler();
  } 
  else {
    return nullptr;
  }
}

} // namespace ms_helloworld

This is my microserver example. When analyzing the microserver in production, you will find that it is very resource efficient. I used Docker to deploy the example. It was very easy to deploy this server with Docker, I will explain what I did to deploy this microserver with Docker in a future post.
I’m happy to receive your opinions and reviews on the microserver and the c++ source code. Anything is very welcome.

Happy Coding Everyone!