Add support for plugins

This commit is contained in:
antao 2019-03-29 15:34:14 +08:00
parent fd5c02c89f
commit aad06fdc3a
21 changed files with 633 additions and 6 deletions

View File

@ -214,5 +214,8 @@ endif()
file(GLOB drogon_util_headers "${CMAKE_CURRENT_SOURCE_DIR}/lib/inc/drogon/utils/*.h")
install(FILES ${drogon_util_headers}
DESTINATION include/drogon/utils)
file(GLOB drogon_plugin_headers "${CMAKE_CURRENT_SOURCE_DIR}/lib/inc/drogon/plugins/*.h")
install(FILES ${drogon_plugin_headers}
DESTINATION include/drogon/plugins)
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/lib/inc/drogon/version.h"
DESTINATION include/drogon)

View File

@ -149,6 +149,19 @@
//The default value of 0 means no limit.
"pipeline_requests": 0
},
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"heartbeat_interval": 2
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {}
}

View File

@ -31,6 +31,8 @@ std::string create::detail()
"create WebSocketController source files\n\n"
"drogon_ctl create filter <[namespace::]class_name> //"
"create a filter named class_name\n\n"
"drogon_ctl create plugin <[namespace::]class_name> //"
"create a plugin named class_name\n\n"
"drogon_ctl create project <project_name> //"
"create a project named project_name\n\n"
"drogon_ctl create model <model_path> //"

View File

@ -100,7 +100,7 @@ void create_filter::handleCommand(std::vector<std::string> &parameters)
exit(-1);
}
std::cout << "create a http fitler:" << className << std::endl;
std::cout << "create a http filter:" << className << std::endl;
createFilterHeaderFile(oHeadFile, className, fileName);
createFilterSourceFile(oSourceFile, className, fileName);
}

107
drogon_ctl/create_plugin.cc Normal file
View File

@ -0,0 +1,107 @@
/**
*
* create_plugin.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "create_plugin.h"
#include <drogon/DrTemplateBase.h>
#include <drogon/utils/Utilities.h>
#include <string>
#include <iostream>
#include <unistd.h>
#include <fstream>
#include <regex>
#include <sys/stat.h>
#include <sys/types.h>
using namespace drogon_ctl;
static void createPluginHeaderFile(std::ofstream &file, const std::string &className, const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("plugin_h");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto namespaceVector = utils::splitString(className, "::");
data.insert("className", namespaceVector.back());
namespaceVector.pop_back();
data.insert("namespaceVector", namespaceVector);
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
static void createPluginSourceFile(std::ofstream &file, const std::string &className, const std::string &fileName)
{
auto templ = drogon::DrTemplateBase::newTemplate("plugin_cc");
HttpViewData data;
if (className.find("::") != std::string::npos)
{
auto pos = className.rfind("::");
data.insert("namespaceString", className.substr(0, pos));
data.insert("className", className.substr(pos + 2));
}
else
{
data.insert("className", className);
}
data.insert("filename", fileName);
file << templ->genText(data);
}
void create_plugin::handleCommand(std::vector<std::string> &parameters)
{
if (parameters.size() < 1)
{
std::cout << "Invalid parameters!" << std::endl;
}
for (auto className : parameters)
{
std::regex regex("::");
std::string fileName = std::regex_replace(className, regex, std::string("_"));
std::string headFileName = fileName + ".h";
std::string sourceFilename = fileName + ".cc";
{
std::ifstream iHeadFile(headFileName.c_str(), std::ifstream::in);
std::ifstream iSourceFile(sourceFilename.c_str(), std::ifstream::in);
if (iHeadFile || iSourceFile)
{
std::cout << "The file you want to create already exists, overwrite it(y/n)?" << std::endl;
auto in = getchar();
(void)getchar(); //get the return key
if (in != 'Y' && in != 'y')
{
std::cout << "Abort!" << std::endl;
exit(0);
}
}
}
std::ofstream oHeadFile(headFileName.c_str(), std::ofstream::out);
std::ofstream oSourceFile(sourceFilename.c_str(), std::ofstream::out);
if (!oHeadFile || !oSourceFile)
{
perror("");
exit(-1);
}
std::cout << "create a plugin:" << className << std::endl;
createPluginHeaderFile(oHeadFile, className, fileName);
createPluginSourceFile(oSourceFile, className, fileName);
}
}

View File

@ -0,0 +1,31 @@
/**
*
* create_plugin.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include "CommandHandler.h"
using namespace drogon;
namespace drogon_ctl
{
class create_plugin : public DrObject<create_plugin>, public CommandHandler
{
public:
virtual void handleCommand(std::vector<std::string> &parameters) override;
virtual std::string script() override { return "create plugin class files"; }
protected:
std::string _outputPath = ".";
};
} // namespace drogon_ctl

View File

@ -104,6 +104,7 @@ void create_project::createProject(const std::string &projectName)
mkdir("views", 0755);
mkdir("controllers", 0755);
mkdir("filters", 0755);
mkdir("plugins", 0755);
mkdir("build", 0755);
mkdir("models", 0755);
mkdir("cmake_modules", 0755);

View File

@ -84,6 +84,7 @@ endif()
AUX_SOURCE_DIRECTORY(./ SRC_DIR)
AUX_SOURCE_DIRECTORY(controllers CTL_SRC)
AUX_SOURCE_DIRECTORY(filters FILTER_SRC)
AUX_SOURCE_DIRECTORY(plugins PLUGIN_SRC)
AUX_SOURCE_DIRECTORY(models MODEL_SRC)
include_directories(/usr/local/include)
@ -102,4 +103,4 @@ foreach(cspFile ${SCP_LIST})
endforeach()
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
add_executable({{ProjectName}} ${SRC_DIR} ${CTL_SRC} ${FILTER_SRC} ${VIEWSRC} ${MODEL_SRC})
add_executable({{ProjectName}} ${SRC_DIR} ${CTL_SRC} ${FILTER_SRC} ${VIEWSRC} ${PLUGIN_SRC} ${MODEL_SRC})

View File

@ -149,6 +149,19 @@
//The default value of 0 means no limit.
"pipeline_requests": 0
},
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//dependencies: Plugins that the plugin depends on. It's a null object by default.
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It's a null object by default.
"config": {
"heartbeat_interval": 2
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.
"custom_config": {}
}

View File

@ -0,0 +1,23 @@
/**
*
* {{filename}}.cc
*
*/
#include "{{filename}}.h"
using namespace drogon;
<%c++auto namespaceStr=@@.get<std::string>("namespaceString");
if(!namespaceStr.empty())
$$<<"using namespace "<<namespaceStr<<";\n";
%>
void {{className}}::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
}
void {{className}}::shutdown()
{
/// Shutdown the plugin
}

View File

@ -0,0 +1,36 @@
/**
*
* {{filename}}.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
using namespace drogon;
<%c++
auto namespaceVector=@@.get<std::vector<std::string>>("namespaceVector");
if(namespaceVector.empty())
$$<<"\n";
for(auto &namespaceName:namespaceVector){%>
namespace {%namespaceName%}
{
<%c++}
$$<<"\n";%>
class {{className}} : public Plugin<{{className}}>
{
public:
{{className}}() {}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() override;
};
<%c++for(size_t i=0;i<namespaceVector.size();i++){%>
}
<%c++}%>

View File

@ -0,0 +1,19 @@
/**
*
* DoNothingPlugin.cc
*
*/
#include "DoNothingPlugin.h"
using namespace drogon;
void DoNothingPlugin::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
}
void DoNothingPlugin::shutdown()
{
/// Shutdown the plugin
}

View File

@ -0,0 +1,25 @@
/**
*
* DoNothingPlugin.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
using namespace drogon;
class DoNothingPlugin : public Plugin<DoNothingPlugin>
{
public:
DoNothingPlugin() {}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() override;
};

View File

@ -0,0 +1,32 @@
/**
*
* TestPlugin.cc
*
*/
#include "TestPlugin.h"
#include <unistd.h>
using namespace drogon;
void TestPlugin::initAndStart(const Json::Value &config)
{
/// Initialize and start the plugin
if(config.isNull())
LOG_DEBUG << "Configuration not defined";
_interval = config.get("heartbeat_interval", 1).asInt();
_workThread = std::thread([this]() {
while(!_stop)
{
LOG_DEBUG << "TestPlugin heartbeat!";
sleep(_interval);
}
});
}
void TestPlugin::shutdown()
{
/// Shutdown the plugin
_stop = true;
_workThread.join();
}

View File

@ -0,0 +1,28 @@
/**
*
* TestPlugin.h
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
using namespace drogon;
class TestPlugin : public Plugin<TestPlugin>
{
public:
TestPlugin() {}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() override;
private:
std::thread _workThread;
bool _stop = false;
int _interval;
};

View File

@ -31,12 +31,15 @@
#include <drogon/NotFound.h>
#include <drogon/HttpClient.h>
#include <drogon/MultiPart.h>
#include <drogon/plugins/Plugin.h>
#include <trantor/net/EventLoop.h>
#include <drogon/CacheMap.h>
#include <memory>
#include <string>
#include <functional>
#include <vector>
#include <type_traits>
namespace drogon
{
//the drogon banner
@ -101,6 +104,30 @@ class HttpAppFramework : public trantor::NonCopyable
*/
virtual trantor::EventLoop *getLoop() = 0;
///Get the plugin object registered in the framework
/**
* NOTE:
* This method is usually called after the framework is run.
* Calling this method in the initAndStart() method of some plugins is also valid.
*/
template <typename T>
T *getPlugin()
{
static_assert(IsPlugin<T>::value, "The Template parameter must be a subclass of PluginBase");
assert(isRunning());
return dynamic_cast<T *>(getPlugin(T::className()));
}
///Get the plugin object registered in the framework
/**
* @param name: is the class name of the plugin.
*
* NOTE:
* This method is usually called after the framework is run.
* Calling this method in the initAndStart() method of some plugins is also valid.
*/
virtual PluginBase *getPlugin(const std::string &name) = 0;
///Load the configuration file with json format.
virtual void loadConfigFile(const std::string &fileName) = 0;

View File

@ -0,0 +1,109 @@
/**
* Plugin.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/DrObject.h>
#include <trantor/utils/NonCopyable.h>
#include <trantor/utils/Logger.h>
#include <json/json.h>
#include <memory>
namespace drogon
{
enum class PluginState
{
None,
Initializing,
Initialized
};
class PluginBase : public virtual DrObjectBase, public trantor::NonCopyable
{
public:
/// This method is usually called by drogon.
/// it always returns PlugiinState::Initialized if the user calls it.
PluginState stat() const { return _stat; }
/// This method must be called by drogon.
void initialize()
{
if (_stat == PluginState::None)
{
_stat = PluginState::Initializing;
for (auto dependency : _dependencies)
{
dependency->initialize();
}
initAndStart(_config);
_stat = PluginState::Initialized;
if (_initializedCallback)
_initializedCallback(this);
}
else if (_stat == PluginState::Initialized)
{
//Do nothing;
}
else
{
LOG_FATAL << "There are a loop-dependency within plugins.";
abort();
}
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) = 0;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() = 0;
virtual ~PluginBase() {}
protected:
PluginBase() {}
private:
PluginState _stat = PluginState::None;
friend class PluginsManager;
void setConfig(const Json::Value &config) { _config = config; }
void addDependency(PluginBase *dp) { _dependencies.push_back(dp); }
void setInitializedCallback(const std::function<void(PluginBase *)> &cb) { _initializedCallback = cb; }
Json::Value _config;
std::vector<PluginBase *> _dependencies;
std::function<void(PluginBase *)> _initializedCallback;
};
template <typename T>
struct IsPlugin
{
typedef typename std::remove_cv<typename std::remove_reference<T>::type>::type TYPE;
static int test(void *p) { return 0; }
static char test(PluginBase *p) { return 0; }
static constexpr bool value = (sizeof(test((TYPE *)nullptr)) == sizeof(int));
};
template <typename T>
class Plugin : public PluginBase, public DrObject<T>
{
public:
virtual ~Plugin() {}
protected:
Plugin() {}
};
} // namespace drogon

View File

@ -157,8 +157,7 @@ void HttpAppFrameworkImpl::loadConfigFile(const std::string &fileName)
{
ConfigLoader loader(fileName);
loader.load();
auto &jsonRoot = loader.jsonValue();
_customConfig = jsonRoot.get("custom_config", Json::Value());
_jsonConfig = loader.jsonValue();
}
void HttpAppFrameworkImpl::setLogPath(const std::string &logPath,
const std::string &logfileBaseName,
@ -415,6 +414,16 @@ void HttpAppFrameworkImpl::run()
}
_responseCachingMap = std::unique_ptr<CacheMap<std::string, HttpResponsePtr>>(new CacheMap<std::string, HttpResponsePtr>(getLoop(), 1.0, 4, 50)); //Max timeout up to about 70 days;
//Initialize plugins
const auto &pluginConfig = _jsonConfig["plugins"];
if (!pluginConfig.isNull())
{
_pluginsManager.initializeAllPlugins(pluginConfig,
[](PluginBase *plugin) {
//TODO: new plugin
});
}
// Let listener event loops run when everything is ready.
for (auto &loopTh : loopThreads)
{

View File

@ -22,6 +22,7 @@
#include "HttpControllersRouter.h"
#include "HttpSimpleControllersRouter.h"
#include "WebsocketControllersRouter.h"
#include "PluginsManager.h"
#include <drogon/HttpAppFramework.h>
#include <drogon/HttpSimpleController.h>
@ -53,10 +54,17 @@ class HttpAppFrameworkImpl : public HttpAppFramework
_connectionNum(0)
{
}
virtual const Json::Value &getCustomConfig() const override
{
return _customConfig;
return _jsonConfig["custom_config"];
}
virtual PluginBase *getPlugin(const std::string &name) override
{
return _pluginsManager.getPlugin(name);
}
virtual void addListener(const std::string &ip,
uint16_t port,
bool useSSL = false,
@ -229,7 +237,9 @@ class HttpAppFrameworkImpl : public HttpAppFramework
int _staticFilesCacheTime = 5;
std::unordered_map<std::string, std::weak_ptr<HttpResponse>> _staticFilesCache;
std::mutex _staticFilesCacheMutex;
Json::Value _customConfig;
//Json::Value _customConfig;
Json::Value _jsonConfig;
PluginsManager _pluginsManager;
#if USE_ORM
std::map<std::string, orm::DbClientPtr> _dbClientsMap;
struct DbInfo

100
lib/src/PluginsManager.cc Normal file
View File

@ -0,0 +1,100 @@
/**
*
* PluginsManager.cc
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#include "PluginsManager.h"
#include <trantor/utils/Logger.h>
using namespace drogon;
PluginsManager::~PluginsManager()
{
// Shut down all plugins in reverse order of initializaiton.
for (auto iter = _initializedPlugins.rbegin(); iter != _initializedPlugins.rend(); iter++)
{
(*iter)->shutdown();
}
}
void PluginsManager::initializeAllPlugins(const Json::Value &configs,
const std::function<void(PluginBase *)> &forEachCallback)
{
assert(configs.isArray());
std::vector<PluginBase *> plugins;
for (auto &config : configs)
{
auto name = config.get("name", "").asString();
if (name.empty())
continue;
auto pluginPtr = getPlugin(name);
assert(pluginPtr);
if (!pluginPtr)
continue;
auto configuration = config["config"];
auto dependencies = config["dependencies"];
pluginPtr->setConfig(configuration);
std::vector<std::string> depsNames;
assert(dependencies.isArray() || dependencies.isNull());
if (dependencies.isArray())
{
//Is not null and is an array
for (auto &depName : dependencies)
{
auto *dp = getPlugin(depName.asString());
if (dp)
{
pluginPtr->addDependency(dp);
}
else
{
LOG_FATAL << "Plugin " << depName.asString() << " not defined";
abort();
}
}
}
pluginPtr->setInitializedCallback([this](PluginBase *p) {
LOG_DEBUG << "Plugin " << p->className() << " initialized!";
_initializedPlugins.push_back(p);
});
plugins.push_back(pluginPtr);
}
//Initialize them, Depth first
for (auto plugin : plugins)
{
plugin->initialize();
}
}
PluginBase *PluginsManager::getPlugin(const std::string &pluginName)
{
auto iter = _pluginsMap.find(pluginName);
if (iter == _pluginsMap.end())
{
auto *p = DrClassMap::newObject(pluginName);
auto *pluginPtr = dynamic_cast<PluginBase *>(p);
if (!pluginPtr)
{
if (p)
delete p;
LOG_ERROR << "Plugin " << pluginName << " undefined!";
return nullptr;
}
_pluginsMap[pluginName].reset(pluginPtr);
return pluginPtr;
}
else
{
return iter->second.get();
}
return nullptr;
}

38
lib/src/PluginsManager.h Normal file
View File

@ -0,0 +1,38 @@
/**
*
* PluginsManager.h
* An Tao
*
* Copyright 2018, An Tao. All rights reserved.
* https://github.com/an-tao/drogon
* Use of this source code is governed by a MIT license
* that can be found in the License file.
*
* Drogon
*
*/
#pragma once
#include <drogon/plugins/Plugin.h>
#include <map>
namespace drogon
{
typedef std::unique_ptr<PluginBase> PluginBasePtr;
class PluginsManager : trantor::NonCopyable
{
public:
void initializeAllPlugins(const Json::Value &configs,
const std::function<void(PluginBase *)> &forEachCallback);
PluginBase *getPlugin(const std::string &pluginName);
~PluginsManager();
private:
std::map<std::string, PluginBasePtr> _pluginsMap;
std::vector<PluginBase *> _initializedPlugins;
};
} // namespace drogon