2
\$\begingroup\$

Getting close to a release of generic server.

Nisse Server: Part 1 Helper Functions
Nisse Server: Part 2 Socket Layer
Nisse Server: Part 3 Stream Layer

Here is the server. The basic usage should be very simple:

NisseServer   server;   // declare an object.

server.listenOn<TypeToHandleRequestAfterAcceptOn16382>(16382);
server.listenOn<TypeToHandleRequestAfterAcceptOn16383>(16383);

server.start();

When we get a connection on the port(16382/16383) a new object of type TypeToHandleRequestAfterAcceptOn1638[2/3] is created to handle the request (more on these objects in an upcoming post).

Of course nothing is ever that simple and you may want to parameterise your handler object. So the listenOn() call can take a second parameter that is passed as a reference to the constructor of the handler type.

An obvious example is the HTTP Protocol and how servers like NodeJS handle it. The same type of effect for NisseServer can be achieved like this.

HTTPBinder    binder;
binder.add("/path",[](Request& request, Response& respose) {
                       // Action to happen for path.
                     }
          );
server.listenOn<HttpHandler>(8080, binder);
server.start();

NisseServer.h

#ifndef THORSANVIL_NISSE_NISSE_SERVICE_H
#define THORSANVIL_NISSE_NISSE_SERVICE_H

#include "NisseEventUtil.h"
#include "Socket.h"
#include <memory>
#include <unordered_map>

namespace ThorsAnvil
{
    namespace Nisse
    {

class NisseService;
class NisseHandler;
using EventBaseDeleter  = decltype(&event_base_free);
using EventHolder       = std::unique_ptr<LibEventBase, EventBaseDeleter>;
using NisseManagHandler = std::unique_ptr<NisseHandler>;

class NisseService
{
    private:
        bool                            running;
        EventHolder                     eventBase;
        // The handlers are held by pointer because they are
        // polymorphic (there can potentially be a lot of different
        // handlers for different incoming streams).
        // We use an unordered map to make it easy to find the handler
        // when we want to delete it.
        std::unordered_map<NisseHandler*, NisseManagHandler>  handlers;

        // This vector is a non owning vector.
        // It contains the pointers to handlers that need to be
        // deleted. We don't want handlers to delete themselves
        // so they register themselves for deletion and get purged
        // in `runLoop()`.
        std::vector<NisseHandler*>      retiredHandlers;
    public:
        NisseService();

        NisseService(NisseService&&) noexcept;
        NisseService& operator=(NisseService&&) noexcept;

        void start();
        void flagShutDown();

        template<typename Handler>
        void listenOn(int port);
        template<typename Handler, typename Param>
        void listenOn(int port, Param& param);
    private:
        void runLoop();
        void purgeRetiredHandlers();
        void swap(NisseService& ) noexcept;

        friend class NisseHandler;
        template<typename H, typename... Args>
        void addHandler(Args&&... args);
        void delHandler(NisseHandler* oldHandler);
};

    }
}

#ifndef COVERAGE_TEST
#include "NisseService.tpp"
#endif
#endif

NisseServer.tpp

#ifndef THORSANVIL_NISSE_NISSE_SERVICE_TPP
#define THORSANVIL_NISSE_NISSE_SERVICE_TPP

#include "NisseHandler.h"
#include "Socket.h"
#include <memory>
#include <vector>

namespace ThorsAnvil
{
    namespace Nisse
    {

template<typename Handler>
inline void NisseService::listenOn(int port)
{
    addHandler<ServerHandler<Handler, void>>(ThorsAnvil::Socket::ServerSocket(port));
}

template<typename Handler, typename Param>
inline void NisseService::listenOn(int port, Param& param)
{
    addHandler<ServerHandler<Handler, Param>>(ThorsAnvil::Socket::ServerSocket(port), param);
}

template<typename H, typename... Args>
inline void NisseService::addHandler(Args&&... args)
{
    NisseManagHandler   value = std::make_unique<H>(*this, eventBase.get(), std::forward<Args>(args)...);
    NisseHandler*       key   = value.get();
    ((void)key);
    handlers.emplace(key, std::move(value));
}

    }
}

#ifndef COVERAGE_TEST
#include "NisseService.tpp"
#endif
#endif

NisseServer.cpp

#include "NisseService.h"
#include "NisseHandler.h"
#include "Utility.h"

#include <iostream>

using namespace ThorsAnvil::Nisse;

NisseService::NisseService()
    : running(false)
    , eventBase(event_base_new(), &event_base_free)
{
    if (eventBase == nullptr)
    {
        throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_new(): Failed"));
    }
}

NisseService::NisseService(NisseService&& move) noexcept
    : eventBase(nullptr, &event_base_free)
{
    swap(move);
}

NisseService& NisseService::operator=(NisseService&& move) noexcept
{
    swap(move);
    return *this;
}

void NisseService::swap(NisseService& other) noexcept
{
    if (running)
    {
        throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": move failed. Can't move a service once it has started."));
    }

    using std::swap;
    swap(running,           other.running);
    swap(eventBase,         other.eventBase);
    swap(handlers,          other.handlers);
    swap(retiredHandlers,   other.retiredHandlers);
}

void NisseService::start()
{
    std::cout << "Nisse Started\n";
    running = true;
    while (running)
    {
        std::cout << "Nisse Loop\n";
        runLoop();
        purgeRetiredHandlers();
    }
    std::cout << "Nisse Stopped\n";
}

void NisseService::flagShutDown()
{
    running = false;
    if (event_base_loopbreak(eventBase.get()) != 0)
    {
        throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_loopbreak(): Failed"));
    }
}

void NisseService::runLoop()
{
    static const TimeVal ten_sec{10,0};

    if (event_base_loopexit(eventBase.get(), &ten_sec) != 0)
    {
        throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_loopexit(): Failed"));
    }
    switch (event_base_dispatch(eventBase.get()))
    {
        case -1:
            throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_dispatch(): Failed"));
        case 1:
            throw std::runtime_error(Socket::buildErrorMessage"ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_dispatch(): No Events"));
        case 0:
            break;
        default:
            throw std::runtime_error(Socket::buildErrorMessage("ThorsAnvil::Nisse::NisseService::", __func__, ": event_base_dispatch(): Unknown Error"));
    }
}

void NisseService::purgeRetiredHandlers()
{
    for (auto const& key: retiredHandlers)
    {
        handlers.erase(key);
    }
    retiredHandlers.clear();
}

void NisseService::delHandler(NisseHandler* oldHandler)
{
    retiredHandlers.emplace_back(oldHandler);
}

#ifdef COVERAGE_TEST
/*
 * This code is only compiled into the unit tests for code coverage purposes
 * It is not part of the live code.
 */
#include "NisseService.tpp"
#endif
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.