Drogon是C++开发的后端服务框架,在看示例代码时,简短的语法一下是真的美,于是打算花点时间学习其中的实现细节。看到request解析到响应的业务处理部分,有个地方处理很是巧妙,跟Qt的信号槽机制神似。
请求到后端服务后,首先做HTTP的协议解析,然后根据URL获取访问路径及请求参数,查找匹配的业务处理接口,接口调用拿到响应结果返回给客户端。查找匹配业务接口的过程,库中是这样的处理:
METHOD_ADD(CustomCtrl::hello, //业务处理接口
"/{userName}", //URL
Get, //按方法过滤
"CustomHeaderFilter"); // path is /customctrl/{arg1}
METHOD_LIST_END
// SFINAE技法确定当前类型是否有">>"定义
template <typename T>
struct CanConvertFromStringStream
{
private:
using yes = std::true_type;
using no = std::false_type;
template <typename U>
static auto test(U *p, std::stringstream &&ss)
-> decltype((ss >> *p), yes()); //构造的校验结构,通常为表达式
template <typename>
static no test(...);
public:
static constexpr bool value =
std::is_same<decltype(test<T>(nullptr, std::stringstream())),
yes>::value;
};
// 能够通过流输出操作获取,则通过如下方式
template <typename T>
typename std::enable_if<CanConvertFromStringStream<T>::value, void>::type
getHandlerArgumentValue(T &value, std::string &&p)
{
if (!p.empty())
{
std::stringstream ss(std::move(p));
ss >> value;
}
}
// 重载同名方法,通过stoi调用来实现
当然其中要解决:
1) 怎么将url的位置参数与接口对应索引位置关联起来?如何拿到要转换的参数类型?
2) 参数指针是如何保存的?在参数转换完成后又是如何调用的?
针对上述问题,关键代码如下:
// 定义类模板,模板参数为函数指针类型,其函数签名与业务处理接口一致;
template <typename FUNCTION>
class HttpBinder : public HttpBinderBase
{
public:
// 通过构造函数的入参类型推导FUNCTION的模板实参类型
HttpBinder(FUNCTION &&func) : func_(std::forward<FUNCTION>(func))
{
static_assert(traits::isHTTPFunction,
"Your API handler function interface is wrong!");
handlerName_ = DrClassMap::demangle(typeid(FUNCTION).name());
}
private:
FUNCTION func_; // 接口函数以成员变量方式保存,待匹配参数匹配转换完成后调用
// 接口信息获取的Traits,主要用于提取接口回调敢兴趣的特性,比如参数个数,
// 是否为类接口以及类名等。后面展开讲述;
using traits = FunctionTraits<FUNCTION>;
template <std::size_t Index>
using nth_argument_type = typename traits::template argument<Index>;
static const size_t argument_count = traits::arity;
//url中获取参数集解析逻辑如下,pathArguments中保存了所有的从路径中获取到string类型的参数。
//values为可变参数集,随着递归调用此次越来越深,该可变参数列表越长。
//初始个数为0,终止个数为响应处理接口的入参个数。
template <typename... Values, std::size_t Boundary = argument_count>
typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run(
std::deque<std::string> &pathArguments,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&... values)
{
// Call this function recursively until parameter's count equals to the
// count of target function parameters
static_assert(
BinderArgTypeTraits<nth_argument_type<sizeof...(Values)>>::isValid,
"your handler argument type must be value type or const left "
"reference type or right reference type");
//nth_argument_type能够获取到FUNCTION形参列表中,
// sizeof...(Values对应索引位置的参数类型。怎么获取到的后面再看。可
// 以把他当成特特殊的函数,入参为索引位置n,出参为数据类型。
using ValueType =
typename std::remove_cv<typename std::remove_reference<
nth_argument_type<sizeof...(Values)>>::type>::type;
ValueType value = ValueType();
if (!pathArguments.empty())
{
// 从参数集中获取首个,由于后面有弹出操作,所以是按序逐个获取的
std::string v = std::move(pathArguments.front());
pathArguments.pop_front();
try
{
if (v.empty() == false)
// 这里就是根据实参类型调对应的转换接口了
getHandlerArgumentValue(value, std::move(v));
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
}
else
{
try
{
value = req->as<ValueType>();
}
catch (const std::exception &e)
{
handleException(e, req, std::move(callback));
return;
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
// 将新解析的出来的value参数,作为入参追加道变参列表中
run(pathArguments,
req,
std::move(callback),
std::forward<Values>(values)...,
std::move(value));
}
// sizeof...(Values) == Boundary,即从参数集中拿到了接口调用所需的所有参数
template <typename... Values,
std::size_t Boundary = argument_count,
bool isCoroutine = traits::isCoroutine>
typename std::enable_if<(sizeof...(Values) == Boundary) && !isCoroutine,
void>::type
run(std::deque<std::string> &,
const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback,
Values &&... values)
{
try
{
// 调用保存下来的FUNCTION函数
callFunction(req, callback, std::move(values)...);
}
catch (const std::exception &except)
{
handleException(except, req, std::move(callback));
}
catch (...)
{
LOG_ERROR << "Exception not derived from std::exception";
return;
}
}
至此,url中的参数集到接口实际参数之间的转换和调用过程就看完了。其中还有两个疑问点,FunctionTraits干了啥?假如调用接口为类的静态接口,callFunction又做了些什么?
首先来看FunctionTraits,关键代码如下:
// 通用模板定义
template <typename>
struct FunctionTraits;
… //此处省去非成员函数的特化模板
// 类成员函数的特化模板,通过模板匹配获取到了ClassType,ReturnType和形参列表Arguments
//还是没有看到获取第n个形参类型的实现。由于通过返回值和形参列表又重新构造了基类模板,所
// 以猜测肯定是在非成员函数特化模板中定义的。
template <typename ClassType, typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (ClassType::*)(Arguments...) const>
: FunctionTraits<ReturnType (*)(Arguments...)> //重新构造非成员函数模板,提取其他特性
{
static const bool isClassFunction = true;
static const bool isDrObjectClass =
std::is_base_of<DrObject<ClassType>, ClassType>::value;
using class_type = ClassType;
static const std::string name()
{
return std::string("Class Function");
}
};
// 就是这里,argument就是辅助模板
template <typename ReturnType, typename... Arguments>
struct FunctionTraits<ReturnType (*)(Arguments...)>
{
using result_type = ReturnType;
// 借助标注库的tuple,获取指定索引位置的形参类型。其实也可以理解为另一个特殊函数的调用:
// 入参为Index形参列表,输出为形参类型
template <std::size_t Index>
using argument =
typename std::tuple_element<Index, std::tuple<Arguments...>>::type;
static const std::size_t arity = sizeof...(Arguments);
using class_type = void;
using return_type = ReturnType;
static const bool isHTTPFunction = false;
static const bool isClassFunction = false;
static const bool isDrObjectClass = false;
static const bool isCoroutine = false;
static const std::string name()
{
return std::string("Normal or Static Function");
}
};
至此,获取第n个位置的形参问题已经解决了。再看第二个函数调用问题,关键代码如下:
// 有了FunctionTraits,各种函数特性皆可认为已有了。这里只看类成员函数的场景
template <typename... Values,
bool isClassFunction = traits::isClassFunction,
bool isDrObjectClass = traits::isDrObjectClass,
bool isNormal = std::is_same<typename traits::first_param_type,
HttpRequestPtr>::value>
typename std::enable_if<isClassFunction && isDrObjectClass && isNormal,
typename traits::return_type>::type
callFunction(const HttpRequestPtr &req, Values &&... values)
{
// 这里根据类型获取对象,可以理解为获取该类型的单例对象,至于该单例对象是
// 如何获取管理的又是如何获取的,后面再展开看。至此我们已经看清了调用过程的全貌
static auto objPtr =
DrClassMap::getSingleInstance<typename traits::class_type>();
return (*objPtr.*func_)(req, std::move(values)...);
}
整个从url参数集到业务接口调用的过程我们已经撕完了,那么还有就是回调对象的注册管理问题了。
在Drogon库中可以通过继承HTTPController,增加业务处理接口,并注册到框架的方式来完成url到处理接口注册过程。看个典型代码:
class User : public drogon::HttpController<User>
{
public:
METHOD_LIST_BEGIN
//use METHOD_ADD to add your custom processing function here;
METHOD_ADD(User::getInfo, "/{id}", Get); //path is /api/v1/User/{arg1}
METHOD_ADD(User::getDetailInfo, "/{id}/detailinfo", Get); //path is /api/v1/User/{arg1}/detailinfo
METHOD_ADD(User::newUser, "/{name}", Post); //path is /api/v1/User/{arg1}
METHOD_LIST_END
//your declaration of processing function maybe like this:
void getInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
void getDetailInfo(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, int userId) const;
void newUser(const HttpRequestPtr &req, std::function<void(const HttpResponsePtr &)> &&callback, std::string &&userName);
public:
User()
{
LOG_DEBUG << "User constructor!";
}
};
#define METHOD_ADD(method, pattern, ...) \
// 这里有一点宏定义使用的小技巧,变参是宏参数可以在函数调用时自动将部分参数
// 检出用作构造函数的初始化列表。
registerMethod(&method, pattern, {__VA_ARGS__}, true, #method)
想必getSingleInstance获取到的就是这里的User单例对象。METHOD_ADD自然是要创建HttpBinder对象并保存起来,然后在请求到来时根据path进行查找,然后就是刚讲完的那一套调用过程。HttpBinder的保存和查找过程不是我们所感兴趣的。我们感兴趣的是HttpController和getObject之间有什么关联。
template <typename T, bool AutoCreation = true>
class HttpController : public DrObject<T>, public HttpControllerBase
{
…
namespace drogon
{
/**
* @brief The base class for all drogon reflection classes.
*
*/
class DROGON_EXPORT DrObjectBase
{
public:
/**
* @brief Get the class name
*
* @return const std::string& the class name
*/
virtual const std::string &className() const
{
static const std::string name{"DrObjectBase"};
return name;
}
/**
* @brief Return true if the class name is 'class_name'
*/
virtual bool isClass(const std::string &class_name) const
{
return (className() == class_name);
}
virtual ~DrObjectBase()
{
}
};
/**
* a class template to
* implement the reflection function of creating the class object by class name
*/
template <typename T>
class DrObject : public virtual DrObjectBase
{
public:
virtual const std::string &className() const override
{
return alloc_.className();
}
static const std::string &classTypeName()
{
return alloc_.className();
}
virtual bool isClass(const std::string &class_name) const override
{
return (className() == class_name);
}
protected:
// protect constructor to make this class only inheritable
DrObject() = default;
~DrObject() override = default;
private:
class DrAllocator
{
public:
DrAllocator()
{
// 构造函数中完成T的注册逻辑
registerClass<T>();
}
const std::string &className() const
{
// typeid(T).name()获取到T的类名,如"class test::MyClass"
static std::string className =
DrClassMap::demangle(typeid(T).name());
return className;
}
template <typename D>
typename std::enable_if<std::is_default_constructible<D>::value,
void>::type
registerClass()
{
// 以类名为key,将创建单例的回调函数,向DrClassMap进行注册
DrClassMap::registerClass(className(),
[]() -> DrObjectBase * { return new T; });
}
template <typename D>
typename std::enable_if<!std::is_default_constructible<D>::value,
void>::type
registerClass()
{
}
};
// use static val to register allocator function for class T;
static DrAllocator alloc_; // 静态成员,该静态对象构造时完成T的注册
};
template <typename T>
typename DrObject<T>::DrAllocator DrObject<T>::alloc_;
} // namespace drogon
// 注册到map表中
void DrClassMap::registerClass(const std::string &className,
const DrAllocFunc &func)
{
LOG_TRACE << "Register class:" << className;
getMap().insert(std::make_pair(className, func));
}
// 根据classname找到map表中单例对象,有则返回无则创建
const std::shared_ptr<DrObjectBase> &DrClassMap::getSingleInstance(
const std::string &className)
{
auto &mtx = internal::getMapMutex();
auto &singleInstanceMap = internal::getObjsMap();
{
std::lock_guard<std::mutex> lock(mtx);
auto iter = singleInstanceMap.find(className);
if (iter != singleInstanceMap.end())
return iter->second;
}
auto newObj = std::shared_ptr<DrObjectBase>(newObject(className));
{
std::lock_guard<std::mutex> lock(mtx);
auto ret = singleInstanceMap.insert(
std::make_pair(className, std::move(newObj)));
return ret.first->second;
}
}
// 调用注册时创建对象的回调函数,创建对象
DrObjectBase *DrClassMap::newObject(const std::string &className)
{
auto iter = getMap().find(className);
if (iter != getMap().end())
{
return iter->second();
}
else
return nullptr;
}
DrObject的实现为反射机制的简单实现,即通过类名获取构造指定类型的对象,类名到类型的转换过程即为反射。这种机制在java中用的比较多,库作者自己简单实现了一套反射机制,主要做单例对象的管理用。
该部分代码,库作者对模板技术的应用也是看着很爽,算得上是模板应用的实例教程了。