Skip to content

Commit

Permalink
Add support for AOP
Browse files Browse the repository at this point in the history
  • Loading branch information
an-tao committed Apr 18, 2019
1 parent c1b5a24 commit 1bc07c5
Show file tree
Hide file tree
Showing 16 changed files with 685 additions and 104 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ Drogon's main application platform is Linux. It also supports Mac OS and FreeBSD
* Support asynchronously reading and writing sqlite3 database based on thread pool;
* Support ARM Architecture;
* Provide a convenient lightweight ORM implementation that supports for regular object-to-database bidirectional mapping;
* Support plugins which can be installed by the configuration file at load time;
* Support AOP with build-in joinpoints.

## A very simple example

Expand Down
2 changes: 2 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,7 @@ Drogon的主要应用平台是Linux,也支持Mac OS、FreeBSD,目前还不
* 基于线程池实现sqlite3数据库的异步读写,提供与上文数据库相同的接口;
* 支持ARM架构;
* 方便的轻量级ORM实现,支持常规的对象到数据库的双向映射操作;
* 支持插件,可通过配置文件在加载期动态拆装;
* 支持内建插入点的AOP

### 更多详情请浏览 [wiki](https://gitee.com/an-tao/drogon/wikis/概述)
48 changes: 45 additions & 3 deletions examples/simple_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ using namespace drogon;
using namespace std::chrono_literals;
class A : public DrObjectBase
{
public:
public:
void handle(const HttpRequestPtr &req,
const std::function<void(const HttpResponsePtr &)> &callback,
int p1, const std::string &p2, const std::string &p3, int p4) const
Expand Down Expand Up @@ -45,7 +45,7 @@ class A : public DrObjectBase
};
class B : public DrObjectBase
{
public:
public:
void operator()(const HttpRequestPtr &req, const std::function<void(const HttpResponsePtr &)> &callback, int p1, int p2)
{
HttpViewData data;
Expand All @@ -64,7 +64,7 @@ namespace v1
{
class Test : public HttpController<Test>
{
public:
public:
METHOD_LIST_BEGIN
METHOD_ADD(Test::get, "get/{2}/{1}", Get); //path is /api/v1/test/get/{arg2}/{arg1}
METHOD_ADD(Test::list, "/{2}/info", Get); //path is /api/v1/test/{arg2}/info
Expand Down Expand Up @@ -155,5 +155,47 @@ int main()
auto filterPtr = std::make_shared<CustomHeaderFilter>("custom_header", "yes");
app().registerFilter(filterPtr);
app().setIdleConnectionTimeout(30s);
//Test AOP
app().registerBeginningAdvice([]() {
LOG_DEBUG << "Event loop is running!";
});
app().registerNewConnectionAdvice([](const trantor::InetAddress &peer, const trantor::InetAddress &local) {
LOG_DEBUG << "New connection: " << peer.toIpPort() << "-->" << local.toIpPort();
return true;
});
app().registerPreRoutingAdvice([](const drogon::HttpRequestPtr &req,
const drogon::AdviceCallback &acb,
const drogon::AdviceChainCallback &accb) {
LOG_DEBUG << "preRouting1";
accb();
});
app().registerPostRoutingAdvice([](const drogon::HttpRequestPtr &req,
const drogon::AdviceCallback &acb,
const drogon::AdviceChainCallback &accb) {
LOG_DEBUG << "postRouting1";
LOG_DEBUG << "Matched path=" << req->matchedPathPattern();
accb();
});
app().registerPreHandlingAdvice([](const drogon::HttpRequestPtr &req,
const drogon::AdviceCallback &acb,
const drogon::AdviceChainCallback &accb) {
LOG_DEBUG << "preHandling1";
// auto resp = HttpResponse::newNotFoundResponse();
// acb(resp);
// return;
accb();
});
app().registerPostHandlingAdvice([](const drogon::HttpRequestPtr &, const drogon::HttpResponsePtr &) {
LOG_DEBUG << "postHandling1";
});
app().registerPreRoutingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "preRouting observer";
});
app().registerPostRoutingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "postRouting observer";
});
app().registerPreHandlingAdvice([](const drogon::HttpRequestPtr &req) {
LOG_DEBUG << "preHanding observer";
});
app().run();
}
71 changes: 69 additions & 2 deletions lib/inc/drogon/HttpAppFramework.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ inline std::string getGitCommit()
class HttpControllerBase;
class HttpSimpleControllerBase;
class WebSocketControllerBase;
typedef std::function<void(const HttpResponsePtr &)> AdviceCallback;
typedef std::function<void()> AdviceChainCallback;

class HttpAppFramework : public trantor::NonCopyable
{
Expand Down Expand Up @@ -121,7 +123,7 @@ class HttpAppFramework : public trantor::NonCopyable
/**
* NOTE:
* This method is usually called after the framework is run.
* Calling this method in the initAndStart() method of some plugins is also valid.
* Calling this method in the initAndStart() method of plugins is also valid.
*/
template <typename T>
T *getPlugin()
Expand All @@ -137,10 +139,75 @@ class HttpAppFramework : public trantor::NonCopyable
*
* NOTE:
* This method is usually called after the framework is run.
* Calling this method in the initAndStart() method of some plugins is also valid.
* Calling this method in the initAndStart() method of plugins is also valid.
*/
virtual PluginBase *getPlugin(const std::string &name) = 0;

///The following is a series of methods of AOP

///The @param advice is called immediately after the main event loop runs.
virtual void registerBeginningAdvice(const std::function<void()> &advice) = 0;

///The @param advice is called immediately when a new connection is established.
/**
* The first parameter of the @param advice is the remote address of the new connection, the second one
* is the local address of it.
* If the @param advice returns a false value, drogon closes the connection.
* Users can use this advice to implement some security policies.
*/
virtual void registerNewConnectionAdvice(const std::function<bool(const trantor::InetAddress &, const trantor::InetAddress &)> &advice) = 0;

///The @param advice is called immediately after the request is created and before it matches any handler paths.
/**
* The parameters of the @param advice are same as those of the doFilter method of the Filter class.
*/
virtual void registerPreRoutingAdvice(const std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)> &advice) = 0;

///The @param advice is called at the same time as the above advice. It can be thought of as an observer who cannot respond to http requests.
/**
* This advice has less overhead than the above one.
* If one does not intend to intercept the http request, please use this interface.
*/
virtual void registerPreRoutingAdvice(const std::function<void(const HttpRequestPtr &)> &advice) = 0;

///The @param advice is called immediately after the request matchs a handler path
///and before any 'doFilter' method of filters applies.
/**
* The parameters of the @param advice are same as those of the doFilter method of the Filter class.
*/
virtual void registerPostRoutingAdvice(const std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)> &advice) = 0;

///The @param advice is called at the same time as the above advice. It can be thought of as an observer who cannot respond to http requests.
/**
* This advice has less overhead than the above one.
* If one does not intend to intercept the http request, please use this interface.
*/
virtual void registerPostRoutingAdvice(const std::function<void(const HttpRequestPtr &)> &advice) = 0;

///The @param advice is called immediately after the request is approved by all filters and before it is handled.
/**
* The parameters of the @param advice are same as those of the doFilter method of the Filter class.
*/
virtual void registerPreHandlingAdvice(const std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)> &advice) = 0;

///The @param advice is called at the same time as the above advice. It can be thought of as an observer who cannot respond to http requests.
/**
* This advice has less overhead than the above one.
* If one does not intend to intercept the http request, please use this interface.
*/
virtual void registerPreHandlingAdvice(const std::function<void(const HttpRequestPtr &)> &advice) = 0;

///The @param advice is called immediately after the request is handled and a response object is created by handlers or by filters.
virtual void registerPostHandlingAdvice(const std::function<void(const HttpRequestPtr &, const HttpResponsePtr &)> &advice) = 0;

///End of AOP methods

///Load the configuration file with json format.
virtual void loadConfigFile(const std::string &fileName) = 0;

Expand Down
15 changes: 12 additions & 3 deletions lib/inc/drogon/HttpFilter.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@

namespace drogon
{
typedef std::function<void(HttpResponsePtr)> FilterCallback;
typedef std::function<void(const HttpResponsePtr &)> FilterCallback;
typedef std::function<void()> FilterChainCallback;
class HttpFilterBase : public virtual DrObjectBase
{
public:
public:
/// This virtual function should be overrided in subclasses.
/**
* This method is an asynchronous interface, user should return the result via 'FilterCallback'
* or 'FilterChainCallback'.
* If @param fcb is called, the response object is send to the client by the callback,
* and doFilter methods of next filters and the handler registed on the path are not called anymore.
* If @param fccb is called, the next filter's doFilter method or the handler
* registered on the path is called.
*/
virtual void doFilter(const HttpRequestPtr &req,
const FilterCallback &fcb,
const FilterChainCallback &fccb) = 0;
Expand All @@ -36,7 +45,7 @@ class HttpFilterBase : public virtual DrObjectBase
template <typename T, bool AutoCreation = true>
class HttpFilter : public DrObject<T>, public HttpFilterBase
{
public:
public:
static const bool isAutoCreation = AutoCreation;
virtual ~HttpFilter() {}
};
Expand Down
53 changes: 53 additions & 0 deletions lib/src/AOPAdvice.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

#include "AOPAdvice.h"

namespace drogon
{

void doAdvicesChain(const std::vector<std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
std::function<void()> &&missCallback)
{
if (index < advices.size())
{
auto &advice = advices[index];
advice(req,
*callbackPtr,
[index, req, callbackPtr, &advices, missCallback = std::move(missCallback)]() mutable {
doAdvicesChain(advices, index + 1, req, callbackPtr, std::move(missCallback));
});
}
else
{
missCallback();
}
}

void doAdvicesChain(const std::deque<std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
std::function<void()> &&missCallback)
{
if (index < advices.size())
{
auto &advice = advices[index];
advice(req,
*callbackPtr,
[index, req, callbackPtr, &advices, missCallback = std::move(missCallback)]() mutable {
doAdvicesChain(advices, index + 1, req, callbackPtr, std::move(missCallback));
});
}
else
{
missCallback();
}
}

} // namespace drogon
35 changes: 35 additions & 0 deletions lib/src/AOPAdvice.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
*
* AOPAdvice.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 "HttpRequestImpl.h"
#include <drogon/HttpAppFramework.h>

namespace drogon
{
void doAdvicesChain(const std::vector<std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
std::function<void()> &&missCallback);
void doAdvicesChain(const std::deque<std::function<void(const HttpRequestPtr &,
const AdviceCallback &,
const AdviceChainCallback &)>> &advices,
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
std::function<void()> &&missCallback);
} // namespace drogon
37 changes: 21 additions & 16 deletions lib/src/FiltersFunction.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,24 @@

#include <queue>

using namespace drogon;
namespace drogon
{
namespace FiltersFunction
{

static void doFilterChains(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
bool needSetJsessionid,
const std::shared_ptr<std::string> &sessionIdPtr,
std::function<void()> &&missCallback)
size_t index,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
bool needSetJsessionid,
const std::shared_ptr<std::string> &sessionIdPtr,
std::function<void()> &&missCallback)
{

if (index < filters.size())
{
auto &filter = filters[index];
filter->doFilter(req,
[=](HttpResponsePtr res) {
[needSetJsessionid, callbackPtr, sessionIdPtr](const HttpResponsePtr &res) {
if (needSetJsessionid)
res->addCookie("JSESSIONID", *sessionIdPtr);
(*callbackPtr)(res);
Expand All @@ -48,7 +50,7 @@ static void doFilterChains(const std::vector<std::shared_ptr<HttpFilterBase>> &f
}
}

std::vector<std::shared_ptr<HttpFilterBase>> FiltersFunction::createFilters(const std::vector<std::string> &filterNames)
std::vector<std::shared_ptr<HttpFilterBase>> createFilters(const std::vector<std::string> &filterNames)
{
std::vector<std::shared_ptr<HttpFilterBase>> filters;
for (auto const &filter : filterNames)
Expand All @@ -65,13 +67,16 @@ std::vector<std::shared_ptr<HttpFilterBase>> FiltersFunction::createFilters(cons
return filters;
}

void FiltersFunction::doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
bool needSetJsessionid,
const std::shared_ptr<std::string> &sessionIdPtr,
std::function<void()> &&missCallback)
void doFilters(const std::vector<std::shared_ptr<HttpFilterBase>> &filters,
const HttpRequestImplPtr &req,
const std::shared_ptr<const std::function<void(const HttpResponsePtr &)>> &callbackPtr,
bool needSetJsessionid,
const std::shared_ptr<std::string> &sessionIdPtr,
std::function<void()> &&missCallback)
{

doFilterChains(filters, 0, req, callbackPtr, needSetJsessionid, sessionIdPtr, std::move(missCallback));
}

} // namespace FiltersFunction
} // namespace drogon
Loading

0 comments on commit 1bc07c5

Please sign in to comment.