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)
| Flag | Description | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| --config=<File> |
Default: The first of ./mug.cfg /etc/mug.cfg /opt/homebrew/etc/mug.cfgThe 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. | ||||||||||||||||
| --logSys | Send 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)
| ||||||||||||||||
| --silent | Don't print output to standard output. | ||||||||||||||||
| --help | Show the help documentation. |
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:<port>?command=stophardImmediately exit the server. - url
https://host:<port>?command=stopsoftStop 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.
- curl
-
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".
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".
{
"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.
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).
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);
}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);
}