Skip to content

Its a python Flask. But Thor drinks from a chalice.

Notifications You must be signed in to change notification settings

Loki-Astari/Mug

Repository files navigation

Mug Server

Mug

The Mug server is designed to be a lightweight server that will dynamically load configured C++ modules.

If the modules change (are rebuilt) Mug will auto-detect this update and load this new version automatically allowing a very simple build/test cycle, trying to be similar to the Python Jug concept.

Modules implement the "MugPlugin" interface which allows you to install REST based listeners on a "NissaServer" that provide all the underlying functionality for async processing of HTTP(S) requests.

The "Mug" server is configured via a JSON based configuration file (see below)

Command line arguments:

FlagDescription
--config=<File> Default: The first of ./mug.cfg /etc/mug.cfg /opt/homebrew/etc/mug.cfg

The config file defines what modules are loaded and what ports they listen on (see below).
--logFile=<file>Send log information to <file>.
Default is std::cerr.
--logSysSend log information to syslog.
Default is std::cerr.
--logLevel=<Level>Set the level of events that will be logged.
Valid values are [ Error | Warning | Info | Debug | Track | Trace | All | 0-9 ]
Default Log level is: 0 (Info)
NameValue
Error-2
Warning-1
Info0
Debug3
Track5
Trace7
All9
See: ThorsLogging for details.
--silentDon't print output to standard output.
--helpShow the help documentation.

Config File:

Mug Configuration:

The main "Mug" configuration looks like this.

{
    "controlPort":      <port>,
    "libraryCheckTime": <TimeCheck>,
    servers: [
       <Server Config: See Below>
    ]
}
  • controlPort: Use this port to send commands to the server.

    • curl https://host:&lt;port&gt;?command=stophard Immediately exit the server.
    • url https://host:&lt;port&gt;?command=stopsoft Stop accepting requests. Exit when all current requests are complete.

    Note: This port is usually not exposed outside the server and used for development and configuration.

  • libraryCheckTime: Time in milliseconds to check for changes to any plugins.

    • A value of 0 means no checks are done.
    • Recommended value 500
  • servers: An array of "Server Configuration".

Server Configuration:

A server configuration looks like this:

{
    "port":         <port>,
    "certPath":     <path>,
    "actions":  [
         <Plugin To Load>
    ]
}
  • port: The port you want to connect a plugin to.
  • certpath: Path in the local filesystem where the certificate and chain file are located.
    Note: This value is optional.
    • If not provided, a regular HTTP socket is created.
    • But if provided, an HTTPS-encrypted socket is created.
  • actions: An array of "Action Configuration".

Action Configuration:

{
     "pluginPath":    <path>,
     "configPath":    <config>
}
  • pluginPath: Path in the local filesystem to a shared library for the platform that implements the Mug Interface.
  • configPath: A string passed to initPlugin(). Recommend that this is a path to a configuration file for the plugin.

Each shared library specified in any "action" configuration is dynamically loaded, and the spinUp() method is called. Then initPlugin(<handler>, <configPath>) is called.

Note: If a shared library is specified in multiple "Action Configuration", then spinUp() is only called once, but initPlugin() is called each time.

Implementing a module:

Implement the interface ThorsAnvil::ThorMug::MugPlugin (See ThorsMug/MugPlugin.h)

    class MugPlugin
    {
        public:
            virtual ~MugPlugin() {}
            virtual void spinUp()                                                           {}
            virtual void spinDown()                                                         {}
            virtual void initPlugin(NisHttp::HTTPHandler& handler, std::string const& config)   = 0;
            virtual void destPlugin(NisHttp::HTTPHandler& handler)                              = 0;
    };

The spinUp() and spinDown() methods get called when the module is first loaded/unloaded. The initPlugin() and destPlugin() are called for each configuration specified (See the configuration file).

Exposing a module:

The following C code is used to expose a module from a shared library:

class SlackBot: public ThorsAnvil::ThorsMug::MugPlugin
{
     // Implementation of MugInterface
};
SlackBot            slackBot;

extern "C" void* mugFunction()
{
    return dynamic_cast<ThorsAnvil::ThorsMug::MugPlugin*>(&slackBot);
}

Implementing a handler

Details of implementing handlers can be found in Nissa. But a simple example:

class MyRestAction
{
    public:
        virtual void initPlugin(NisHttp::HTTPHandler& handler, std::string const& /*config*/) override
        {
             using ThorsAnvil::Nissa::HTTP::Method;
             using ThorsAnvil::Nissa::HTTP::Request;
             using ThorsAnvil::Nissa::HTTP::Reponse;

             handler.addPath(Method::GET, "/MyCheck/{Name}/{Age}",
                             [&]()Request& request, Response& response) {
                                  return handleMyCheck(request, response);
                             });
             handler.addPath(Method::POST, "/AddAddress/{Name}/{Age}",
                             [&]()Request& request, Response& response) {
                                  return handle addAddress(request, response);
                             });
        }
        virtual void destPlugin(NisHttp::HTTPHandler& handler) override
        {
             using ThorsAnvil::NissaHTTP::Method;

             handler.remPath(Method::GET, "/MyCheck/{Name}/{Age}");
             handler.remPath(Method::GET, "/AddAddress/{Name}/{Age}");
        }
    private:
        bool handleMyCheck(ThorsAnvil::NissaHTTP::Request& request, ThorsAnvil::NissaHTTP::Response& response)
        {
             using ThorsAnvil::Nissa::HTTP::Encoding;

             std::string const& name = request.variables()["Name"]; // From {Name} in the path
             std::string const& age  = request.variables()["Age"];  // From {Age}  in the path

             // If you know the exact size of the body you can pass that as a parameter to body.
             response.body(Encoding::Chunked) << "<html><head><title>" << name << "</title></head>"
                             << "<body>Got: " << name << " is " << age << " years old!</body></html>";

             // Default is a 200 OK
             //   Can set the status code to anything you like with response.setStatus()
             //   The result of body() is `std::ostream` that is connected to a socket.
             //   Once you start streaming to the body adding headers will result in an exception.
             return true;
             // If you return false, the server will see if other handlers have been mapped to this path.
        }
        bool addAddress(ThorsAnvil::NissaHTTP::Request& request, ThorsAnvil::NissaHTTP::Response& response)
        {
             std::string const& name = request.variables()["Name"];    // From {Name} in the path
             std::string const& age  = request.variables()["Age"];     // From {Age}  in the path
             std::string const& str1 = request.variables()["street1"]; // HTTP Header values available here.
             std::string const& str2 = request.variables()["street2"]; // Form attributes are available here.
             std::string const& pCode= request.variables()["postCode"];// query parameters are available here.

             // Update DB with address.
             // See ThorsSerializer for examples of sending JSON to a stream.
             // See ThorsMongo for examples of connecting to Mongo DB to store data.
             // No. Action returns a 200 OK.
             return true;
        }
};

MyRestAction            restAction;

extern "C" void* mugFunction()
{
    return dynamic_cast<ThorsAnvil::ThorsMug::MugPlugin*>(&restAction);
}

About

Its a python Flask. But Thor drinks from a chalice.

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages