低代码API服务器设计

在前后端分离开发模式下的项目开发中,由于渲染逻辑向前端移动而后端在此情景下仅承担业务逻辑和数据存储校验功能。而对于中小型前后端分离项目中前端与后端交互时,只有少部分数据需要进行复杂的后端业务逻辑运算,大部分前端发送的数据仅需要进行权限判定和数据校验就可直接送入持久层中存储。

而在当前前后端开发流程中,后端需要将这逻辑相似、功能相似的代码根据不同API多次重写,导致后端开发效率在一定程度上降低。将这部分代码提取出来成为独立的服务器模块并以配置文件对其功能进行控制,可以有效提高后端开发效率。同时通过配置文件控制服务器功能的方法,该服务器可获得极强拓展性。

服务器架构设计

本文将从整体架构、模块化、配置化、基础库设计、部分核心模块设计五个方面对低代码API服务器进行设计分析。

整体架构设计

服务器整体架构为Reactor多线程模式架构。该设计模式基于IO复用技术,以主线程为IO多路复用器上端口监听器,多个子线程等待主线程向从线程IO多路复用器上注册事件产生特定响应。Reactor模式由于其非阻塞且避免回调地狱的特性几乎成为高性能服务端程序必选的设计模式。

同时为了提供灵活的拓展能力,服务器主要功能被划分为了各个模块,每个模块都拥有一个继承于模块公用接口的基础抽象类,服务器核心代码均使用公共接口交互。而每个模块处于一个基于服务器根命名空间的独立命名空间下。

Reactor模式是一种高性能事件驱动型设计模式,主要由事件处理器、事件分派器、多路分配器三个部件组成。其中核心最为核心的部分为事件分发器,该部分用于在存在感兴趣的事件时通知对应事件处理器,同时该部分具有添加、修改、注册事件处理器至感兴趣事件的能力。事件处理器为一公共接口,用户需要实现该接口以将具体化事件处理器注册至事件分发器中。Reactor模式主要部分类图如图所示。

该模式基于IO多路复用技术解决了传统WEB服务器一连接一线程模式,为高性能WEB服务器设计与开发提供了良好的理论基础。

模块化设计

服务器绝大部分功能都将模块化,功能模块化所带来的最重要的好处就是可以方便的对服务器功能进行拓展和修改。同时模块化服务器功能可以为基于配置文件控制服务器功能提供结构基础,每个模块都可以进行独立的配置文件编写,同时模块与模块之间可以以层级关系相互嵌套,极大提高服务器系统的可拓展性。

模块化设计参考了Nginx服务器中对所有功能模块化的优秀设计实践,该设计为Nginx配置文件解析、校验和模块编写都带来巨大便利。

模块分类及核心模块设计

WEB服务器应有的主要功能在模块化的过程中被拆分为了七种核心模块。

(1)配置模块,即加载配置文件,为核心模块提供配置文件解析入口的模块,该类型模块为服务器第一个解析并启动的模块。

(2)缓存模块,即为服务器提供高效缓存能力的模块,是服务器进行字符快速解析、发送的基础。

(3)事件模块,服务器基于Reactor模式进行设计,Reactor模式中事件分发器、反应器两个主要部分归类为事件模块功能,而事件模块基础抽象类下可以根据不同操作系统上不同种类IO事件分发器进行具体事件模块开发。同时事件处理器部分则被归类为事件消费模块,根据不同事件类型而开发不同模块。同时该模块中存放有主线程向子线程分发事件时的事件分发函数。

上述三类模块模块基类类图如图所示。

配置模块、缓存模块、事件模块类图

(1)事件消费者模块,即Reactor模式中Event Handler类功能,在WEB服务器的大部分场景下为HTTP事件消费模块,HTTP模块需要拥有向事件模块中注册连接事件接收器和HTTP事件处理器的能力。

(2)线程模块,即底层线程库封装模块,为服务器提供基础的多线程支持能力。

(3)持久化模块,即提供持久化存储统一接口的模块,为服务器业务数据提供持久化存储的API。

上述三类模块基类类图如图所示。

线程模块、事件消费模块、持久化模块类图

(4)日志模块,即提供日志服务的模块,为服务器运行时产生的运行时记录以特定方式持久化存储到本地。服务器默认日志模块为同步阻塞日志模块。

(5)脚本模块,即提供脚本调用能力的模块,为服务器提供脚本执行能力。通过脚本模块可以为服务器事件处理流程提供脚本注入能力,可以便捷的修改服务器功能。

所有非核心模块进行配置初始化方法由其所属核心模块进行调用,核心模块配置加载顺序由于不同模块之间对功能等方面相互依赖,因此加载顺序规定为日志模块、缓存模块、持久层模块、线程模块、事件模块、事件消费模块、脚本模块。

大部分模块都无法感知是否处于多线程模式,即对所有子线程提供统一API,但返回数据基于每个线程不同。该设计主要为了兼容部分代码跨线程问题,例如SQLite数据库连接对象跨线程使用会出现多线程问题。

模块公共接口

模块公共接口应拥有配置文件解析入口函数同时应持有模块名称和模块,同时拥有初始化、析构钩子函数,模块基类类图如图所示。

模块基类类图

上图中模块基类类图为所有服务器功能模块基类,该类中定义了一个模块所拥有的最基本能力,即解析配置文件的能力。该方法是服务器功能配置化的根本所在。

程序及模块生命周期

所有备用模块都需要在指定位置添加该模块工厂方法,模块的生命周期与程序整体生命周期一致,即模块一旦由工厂方法实例化后将不会在程序运行时进行析构,仅在程序接收到退出信号时析构。

配置化设计

服务器大部分功能都实现了基于模块化的配置能力。在此处定义服务器配置文件类型数据格式为JSON,该JSON文件根对象下必须存在名为core包含所有核心模块的模块类型作为键而模块名作为值的键值对节点,该节点用于所有核心模块解析。

配置加载入口

在程序运行时,第一个进行初始化的模块是核心模块中承担配置加载及核心模块解析的配置模块,服务器运行时核心代码会通过默认配置文件路径“/resources/config/”寻找JSON配置文件,找到该文件后将会将其解析,同时寻找该JSON文件根节点下第一个名为config的节点,而后解析该节点值并以该值作为模块名在模块工厂列表中搜索该模块,获得该模块工厂方法后初始化为配置模块对象并根据模块类别存入全局模块实例数组中,然后调用该模块配置解析函数,至此将模块解析控制权交予配置模块,如图 5所示。

配置模块加载函数执行流程

配置模块将会将读入的JSON配置文件内容中有效配置文件信息进行解析并调用特定模块配置解析函数进行模块内部数据初始化。

配置加载流程

配置文件解析主要在配置模块中配置解析函数进行,以默认配置模块为例,该函数首先对读入的JSON配置文件core节点module子节点进行解析,该节点中包含服务器使用的所有核心模块,core节点包含对所有核心模块指定的能力,可以根据用户需求更换用户开发的功能模块,提供更强的拓展性。获得核心模块模块名后,程序将自动通过工厂方法数组寻找并实例化具体模块,当不存在某一节点或无法通过模块名找到该模块时,服务器将会使用该模块类型默认核心模块启动。

在将所有核心模块实例化后,配置模块将对每一核心模块在根节点下查找以该模块类型或该模块名为节点名的JSON节点,而后调用核心模块中配置解析函数并以该节点作为参数传入,至此配置文件解析控制权交予核心模块实例。

每一个核心模块实例配置解析函数中实现各有不同,配置模块仅将其作为module实例看待,解除了配置加载过程中配置模块和核心模块的耦合。

基础库设计

整个服务器需要大量基础代码作为运行及开发依赖。根据代码功能,基础设施代码被分为三个命名空间。

net命名空间下有包含对套接字、文件描述符等底层网络API封装。infrastructure命名空间包含模块基类及服务器启动器基类等核心接口类。Event命名空间下包含服务器对系统IO多路复用器的抽象,同时包含基于Reactor模式设计的基础代码。

套接字封装需要将底层API与上层实现隔离,如同时内部应封装常用socket可选参数修改函数,为用户修改socket阻塞方式等socket参数提供便捷的API调用。其中核心函数应为IP及端口绑定方法、监听方法、接收连接方法等。

核心模块设计

HTTP事件消费模块

HTTP事件消费模块以下简称为HTTP模块。HTTP模块为WEB服务器提供HTTP服务最为核心的一部分,该模块主要包含以下能力。提供具有HTTP事件分派能力的端口监听事件处理器;提供HTTP事件处理能力;提供HTTP事件路由能力;提供特定URL校验及鉴权能力。

HTTP模块将整个HTTP解析及响应流程分割为解析请求、路由请求、请求鉴权、请求校验、请求处理、请求响应六个部分,HTTP请求响应流程如图所示。HTTP模块处理HTTP请求步骤为。

(1)客户端请求建立连接,事件模块发现被监听端口存在可读事件。

(2)事件模块通知HTTP请求事件监听器,该监听器与客户端建立连接后将该Socket注册可读事件到被监听事件列表中。

(3)事件模块发现与客户端连接的Socket上存在可读事件,将该事件交予HTTP事件处理器进行处理。

(4)HTTP事件处理器先将HTTP请求使用解析器解析为封装好的HTTP上下文对象,然后通过将该请求路径路由获取具体HTTP鉴权器、校验器、处理器。

(5)将HTTP上下文送入鉴权器判断该用户是否有权限使用该API,然后使用校验器校验数据

(6)将HTTP上下文送入HTTP处理器进行处理,并返回处理结果给客户端

(7)结束当前HTTP响应,将控制权归还给事件模块。

HTTP模块响应请求时序图

HTTP事件消费模块配置文件设计。HTTP模块配置块主要有以下四个部分,WEB服务器监听配置块、WEB服务器HTTP处理流程组件配置块、WEB服务器路由配置块、WEB静态服务配置块。

(1)WEB服务器监听配置块包含服务器对外提供服务的IP及服务端口。该配置块用于HTTP模块启动时指定服务器绑定端口。

(2)WEB服务器HTTP处理流程组件配置块主要为整个HTTP模块提供处理流程组件配置化能力。

(3)WEB服务器静态服务配置块用于指定静态文件路径、静态文件URL前缀及配置静态文件发送的HTTP处理器。

(4)WEB服务器路由配置块是整个HTTP模块最为核心的配置文件,该配置块指定了不同URL路径下所使用的鉴权器、校验器、处理器。下方JSON对象则为该配置块列表中一对象。该对象中handler指定使用默认鉴权登录HTTP处理器,而其中与handler值同名子对象则为该HTTP处理器内部配置。

下方代码为配置文件示例。

{

​    "path": "/common/login",//路由路径

​    "handler": "default_auth_login_handler",//使用默认登录鉴权HTTP处理器

​    "usage": "api",//用于普通API

​    "default_auth_login_handler": {//HTTP处理器内部配置

​     "table_name": "user",

​     "account_field": "user_name",

​     "password_field": "user_password",

​     "roles_field": "roles"

​    }

 }

数据库RESTful-HTTP处理器设计。前后端通信过程中数据交互格式多种多样,而主流前后端通信格式则为JSON格式。JSON即JavaScript Object Notation,为ECMAScript规范下一个子集,该格式具有轻量高效且易于机器解析、生成的特点。因此采用JSON作为数据传输格式。

RESTful风格是一种将每一种网络资源绑定到特定URI上,并通过不同请求方法对该资源进行增删改查的API设计风格。对于前后端分离条件下,RESTful风格API具有可扩展性、易用的特点。

在前后端分离场景下,由于渲染逻辑及部分业务逻辑从后端转移到前端,大部分后端API在接收前端发送的数据后仅需要将接收的数据在校验后送入数据库,而数据库RESTful HTTP处理器则用于简化这个过程。

数据库RESTful-HTTP处理器需要在配置文件中确定每种方法所需要的权限,同时提供自定义数据库查询语句的能力。

路由设计在接收到HTTP请求并解析为HTTP上下文后,HTTP模块将会使用HTTP路由组件对请求进行路由。由于URL具有树形结构的特点,因此路由组件需要内部维护一个从“/”节点出发到所有用户配置路径的路由节点树。

路由节点类图

图中路由节点类中sibling、parent、child节点分别指向其兄弟节点、双亲节点、儿子节点,整个路由树由这三个指针构建其整个搜索路径,而path属性定义了从根节点至该节点全路径路径,以下方路由树结构实例中login节点为例,path为“/common/login”,而name属性则包含该节点名即“login”。节点中wildcard和forced属性用于指定该节点是否为通配符节点和强制通配符节点。在配置文件中通过在路由路径末端加入设置通配符属性开启。例如“/common/test/”。在该属性开启时,该路由配置节点块将被解析为名为“*”的节点对象,同时父节点优先以该对象作为child指针指向点。

在HTTP请求路由函数中,以“/common/test/all”为例,路由函数将优先匹配path与请求路径完全相同的节点,若不存在完全匹配的节点,则返回具有wildcard属性的节点。而forced属性用于强制匹配该节点,若存在“/common/test/**”为路径的节点对象,则“/common/test/”下所有子URL都将强制使用启用forced属性的兄弟节点。

同时在进行配置文件解析时,具有forced属性的节点具有将直接被双亲节点设为child指针指向对象。

路由节点树示例

如图为一颗简单的路由节点树,该树中实线箭头为child、parent指针,虚先箭头为sibling指针。以“/common/login”为例进行路由匹配,首先从“/”节点匹配,而后再使用sibling指针匹配“common”节点,匹配成功,进入其子节点匹配。若“common”节点直接子节点为强制通配符节点则直接返回该节点直接子节点。若存在“login”节点则匹配,若不存在则查找“common”节点直接子节点通配符属性值,若存在通配符节点则返回该节点。

线程模块设计

线程模块主要用于对底层模块进行封装,并提供简单易用的线程管理、操作接口。在使用Reactor多线程模式开发WEB服务器时,主线程用于等接受并初始化来自客户端的Socket连接,而后将Socket可读事件和具体的事件处理器注册到子线程中IO多路复用器中,因此一个简单易用的线程模块是Reactor多线程模式的基础,默认线程模块类图如图所示。

默认线程模块设计思路为简单实用仅提供基础的线程封装,因此不具有线程池管理及自动扩容、收缩的能力。通过传入类型为std::function<void (thread_data* data)>类型的函数绑定、线程名称、线程初始化回调函数生成thread类型对象,该对象为对底层线程API的封装类。而后在需要启动线程的位置调用thread对象公有函数start并传入thread_data类型对象启动线程。

默认线程模块类图

线程对象中包含一个类型为std::function<void()>数组的静态变量,该变量用于存储其他模块需要在线程开始运行时,针对每一个线程初始化模块数据的函数绑定对象。

事件模块设计

事件模块是Reactor模式的核心,该模块对IO多路复用器进行抽象并分离为四部分,事件多路分配器、事件模块、事件、事件处理器。其中时间分发器对是多IO多路复用API的封装及抽象,事件类则是IO事件封装类。

由于每种IO多路复用器API、设计理念、调用方法都有所不同,因此此处对该技术进行了高度抽象,具体实现则由实例模块封装在内部。

事件模块处于整体架构的核心位置,该模块上接事件消费模块执行具体业务逻辑,下接事件基础API,是整个服务器的数据中枢。而对于Reactor多线程模式下服务器,主线程将所有模块初始化后注册连接事件接收器在主线程IO多路复用器上,而后若有连接事件接入,IO多路复用器将会唤醒主线程并调用连接事件接收器。连接事件接收器将会使用用户提供的事件分发算法将事件分发给子处理线程。整个过程中主线程不对任何业务逻辑负责,仅将事件封装后发送给子线程,由子线程处理具体业务逻辑。这样的设计方法也给后续拓展事件消费模块功能提供了巨大便利。事件模块基类类图如图所示。

事件模块类图

服务器主要模块实现

服务器被命名为foxr,因此大部分核心代码中类命名都包含foxr前缀。服务器所有源码均在foxr命名空间下,其下还有多个根据功能划分的具名命名空间。 服务器代码命名空间根据其功能分为基础设施代码infrastructure、事件模块代码event、缓存模块代码buffer、事件消费模块代码consume、日志模块代码log、网络基础代码net、线程模块代码thread、持久化模块代码persistence、配置模块代码config。各个命名空间下代码构成了整个服务器代码集合。

整体架构实现

服务器源码从核心与非核心的角度分服务器抽象、基础、工具类代码和服务器模块实例代码。服务器基础功能实现大概需要六千到七千行代码,其中基础、工具类代码占四成。

当前实现的功能模块有std::vector<char>封装的简单缓存模块、提供epollIO多路复用封装的事件模块、提供阻塞日志功能的日志模块、提供LUA脚本接入的LUA脚本模块、提供基础HTTP服务功能的事件消费模块、提供配置解析能力的配置模块、提供SQLite API封装的持久层模块、异步日志模块。工程文件目录图如图所示。

工程文件目录图

模块化实现

所有模块都继承自模块基类,模块基类拥有基本的配置解析函数及模块名。在C++代码中模块需要用户在全局变量foxr_module_factories字典中注册备用模块的模块名名及其工厂方法,配置文件中对指定特定模块的唯一标识即该字典中存入的模块名。下列代码分别为模块工厂模板方法、模块注册方法、具名模块工厂数组。

//工厂方法类

template <class T>

foxr_module* factory() {return (foxr_module*) new T();} 

//模块工厂添加函数,对map插入键值对提供简单封装

void foxr::add_module(std::string name, foxr::module_factory fac) {

  foxr_module_factories->insert(std::map<std::string,module_factory>::

value_type(name,fac));

}

 

//工厂方法数组

std::map<string,module_factory> foxr_module_factories

=new std::map<string,module_factory>();

服务器中几乎所有功能模块都继承于foxr_module模块基类,该类定义了每一个模块应具备的最基本方法和属性,其中用于配置文件指定特定模块的属性为name属性,该属性定义了模块的唯一名称。module_type属性为指定模块种类的枚举类型,该枚举类型包含所有核心模块和一个名为子模块SUB_MODULE的总共九个枚举值,子模块枚举值用于指明该模块为非核心模块。所有模块都需要在构造函数中调用foxr_module含参构造函数传入模块名称与模块类型。

由于每个模块都需要通过JSON配置文件对自身功能进行配置,因此在foxr_module中拥有名为create_conf的虚函数,在父模块对子模块传入JSON配置文件节点时能对该节点下配置进行解析。下面为foxr_module核心代码。

class foxr_module {

​    public:

​      //含参构造函数,子模块需要在实例化是确定模块类型及名称

​      foxr_module(foxr_core_module_type type,std::string& name);

//模块名称

​      std::string name;

​      //该模块属于核心模块或或者子模块中哪种。

​      foxr_core_module_type module_type;

​      //配置文件解析函数,需要通过配置文件进行配置的模块进行重写

​      virtual bool create_conf(rapidjson::Value& doc){ return true;};

​      //在模块析构前提供一个析构资源释放钩子

​      virtual int destroy()=0;

};

配置化实现

服务器模块解析配置的能力来自于模块基类foxr_module中create_conf方法。第一个被解析的模块为配置模块,而后解析流程则由具体配置模块中实现决定。

服务器运行配置解析阶段由非模块类config_module_loader开始,该类通过从命令行传入的参数或默认配置文件路径寻找JSON配置文件,默认情况下该文件名为foxr_config.json。而后读取该文件,并解析获取该JSON文件根节点对象下名为“config”的节点值,并以改值作为config模块名在foxr_module_factories中寻找该具名模块,若不存在该名称模块,则使用默认配置模块default_config_module。而后使用该模块工厂方法创建该模块对象并调用该配置模块实例parse_config方法,至此将控制权移交至配置模块。config_module_loader核心代码如下。

static config::foxr_config_module*load_config(std::string config_name="foxr_config.json", std::string config_path = "./resources/config/")

{

 //初始化模块工厂数组

 foxr::init_module_factory();

 //读取配置文件

 std::string config_content = read_file((config_path + config_name).c_str());

 rapidjson::Document root;

 //解析配置文件

 root.Parse(config_content.c_str());

 config::foxr_config_module *config_module = NULL;

 //根据config节点加载配置模块

 if (root.HasMember("config") && 

(*foxr::foxr_module_factories)[root["config"].GetString()] != NULL){

  //尝试加载用户自定义配置模块

   config_module = (config::foxr_config_module *)(*foxr_module_factories)

[root["config"].GetString()]();

 }

 if(config_module == NULL){

  //使用默认配置模块

  config_module = new config::foxr_default_config_module();

 }

 //将控制权移交给config_module

 config_module->parse_config();

 return config_module;

}

在配置文件中通过core节点下module子节点使用模块名指定特定核心模块。module子节点中应存在以模块类型即buffer、consume、event、log、thread、script、persistence为名并以模块名为值的键值对集合。module节点配置实例如下。

{

"module" : 

  {"consume" : "foxr_http_consumer_module",

  "buffer" : "foxr_default_buffer_module",

  "event" : "foxr_epoll_event_module",

  "persistence" : "foxr_sqlite_persistence_module",

  "log" : "foxr_async_log_module",

  "thread" : "foxr_default_threadpool_module",

  "script" : "foxr_lua_script_moudle"

}

}

以默认配置模块为例,在该模块parse_config方法被调用后,该函数将首先重新加载配置文件内容,同时使用RapidJSON库进行解析,而后读取根节点下core节点的子节点module以获取核心模块名,根据配置文件中核心模块名加载模块。而后按顺序调用核心模块create_conf函数加载各核心模块配置文件。

基础库实现

在foxr服务器中foxr主命名空间下infrastructure、event、net子命名空间中都拥有大量基础代码。foxr::infrastructure命名空间提供了模块化的基础设施代码,其中最核心代码为foxr_module类,该类为整个模块化及配置化提供了最基本的入口,即create_conf函数。同时该命名空间下有部分用于启动配置模块的启动类config_module_loader,该类用于服务器开始运行时寻找并加载配置文件。

foxr::event命名空间中提供了对事件最基础的抽象foxr_event,该类中包含了foxr::net::foxr_channel作为对文件描述符事件的封装,同时拥有一个void指针用于用户自定义数据存储。而foxr_demultiplexer类则为IO多路复用器抽象接口,该类拥有对IO多路复用器中事件的增删改查能力,其中get_active_events函数为服务器运行时等待事件派发时阻塞位置,以使用epoll作为IO多路复用器的foxr_epoll_dmultiplexer类为例,该函数中使用epoll_wait函数等待事件触发。

foxr::infrastructure命名空间中包含模块基类、类型定义、非模块类型核心代码。

foxr::net命名空间中包含对文件描述符、Socket对象的简单封装foxr_channel及foxr_socket,上述两个类用户一般不会在用户代码直接调用,而是通过他们的封装对象foxr_tcp_connection进行操作,在HTTP模块中则是使用foxr_http_context将底层API对用户屏蔽。

基础库中包含所有公共代码,而在网络基础代码中最为重要的就是套接字封装代码即封装套接字创建、绑定、监听的封装类foxr_socket和封装套接字读、写能力的foxr_tcp_connection类。服务器中socket封装类foxr_socket核心代码如下。

class foxr_socket{

public:

 foxr_socket(int fd) : sock_fd(fd) {}

 ~foxr_socket();

  void listen(); //监听Socket

  void bind(const inet_address &addr); //绑定IP、端口至Socket

 file_descriptor accept(inet_address *addr); //accept TCP连接请求

 int set_flag(bool f, int level, int opt){//设置socket参数

  int v = f ? 1 : 0;  //设置Socket参数

   if (setsockopt(this->sock_fd, level, opt, (const void *)&v, sizeof(v)) < 0){

LOG_FATAL("set socket opt failed");//程序停止运行。

return 0;//返回代码0表示该操作错误

}

return 1; //返回代码1表示该操作正确执行

}

 //常用Socket参数设置

 void set_nodelay(bool f) { set_flag(f, IPPROTO_TCP, TCP_NODELAY); }

//.......

 //持有socket 文件描述符的属性

 file_descriptor sock_fd;

};

部分核心模块实现

HTTP事件消费模块实现

HTTP模块为当前已实现模块中最为复杂的模块,该模块内包含整个HTTP事件处理流程,该模块文件目录如图 12。

HTTP模块文件目录图

该模块根目录下存放所有模块HTTP模块、HTTP上下文对象及请求和响应对象。handler子目录下存放有作为事件消费模块必须提供的foxr_http_entry_handler即连接事件监听处理器和http_event_handler即HTTP请求事件处理器。process目录下存放着HTTP处理流程中所需要的鉴权器、HTTP请求解析器、HTTP响应编码器、HTTP处理器、校验器。

解析请求、请求鉴权、请求校验、请求处理、请求响应流程为继承foxr_module的抽象类模块foxr_http_decoder、foxr_http_authenticator、foxr_http_handler、foxr_http_encoder子类处理,这种方法为foxr WEB服务器在HTTP响应流程上提供了极大的拓展能力,用户可以在编写多个模块后,可以快速通过配置文件对特定HTTP流程模块进行启用和关闭,同时这种方法为其他模块,例如脚本模块、持久层模块向HTTP模块注入其他特殊能力提供了巨大方便。

如图用户可以通过配置文件控制的模块均在process目录下,以默认鉴权登录HTTP处理器为例,该HTTP处理器用于提供简易登录验证并通过加密cookie提供用户登录凭据,该模块具体配置块可通过4.4.1节“/common/login”路由节点配置块查看。下面为默认鉴权登录HTTP处理器核心代码,该代码对于用户自定义HTTP处理器中具体逻辑处理方法具有一定参考性。。

默认鉴权登录HTTP处理器功能为通过用户提供的数据库表名、账户字段、密码字段、权限字段自动查表然后将登录结果返回,如果登录成功则通过一个简易加密算法生成加密cookie。该HTTP处理器所指定的roles字段中存储以“|”为分割线的权限id,权限id由HTTP模块配置块下roles指定。下面为该HTTP处理器中handle函数。。

HTTP模块目录下还存在有存放请求路由代码的router目录和该模块根目录下的基础代码。HTTP模块中路由组件为一独立于模块的组件。该模块根目录下存有对HTTP请求及响应的高级封装foxr_http_context即HTTP请求上下文。而在HTTP处理流程中foxr_decoder则承担将HTTP请求转换为请求上下文的职责。

HTTP请求路由组件是HTTP模块中逻辑最为复杂的部分,该部分设计理念及方法已在模块设计中关于HTTP模块相关部分给出,由于篇幅原因此处不在赘述。下面为路由组件核心方法及属性。

class foxr_router : public foxr::http::foxr_http_sub_module{

public:

 std::string wildcard = "*"; //通配符字符

 foxr_router(std::string &name = *new std::string("foxr_router"));

 //路由节点在HTTP路由组件中以向量的形式存储

 std::vector<route_item *> items;

 //用于对没有匹配结果的请求进行处理

 route_item *not_found_route;

 //最大请求深度,当存在强制匹配路由节点时为-1代表无穷大

 int max_size = 0;

 //对传入参数进行路由

 route_item *route(const std::string &path);

 //将一路由节点挂在在当前路由组件上

 virtual bool mount(route_item &item);

};

基于epoll技术事件模块实现

服务器中所有与事件有关的核心代码都存放于foxr::event命名空间下,其中包括事件模块的基础抽象类foxr_event_module、提供IO多路复用器功能的foxr_event_demultiplexer抽象类和提供事件处理器接口的event_handler类。服务器默认使用epoll IO多路复用器作为事件模块。epoll事件模块需要被配置文件初始化的属性仅包含在epoll_wait方法上的超时等待事件即timeout。

日志模块实现

日志模块提供阻塞式及非阻塞式文件日志记录功能,日志文件路径、文件名、记录级别由配置文件决定。以异步日志为例,该模块主要有两个部分,将日志器在解析配置文件过程中设置为异步日志记录器的异步日志模块async_log_module和继承自日志器基类foxr_logger的非阻塞日志器async_logger。下方代码为日志记录器提供日志记录能力时所定义的宏。

\#define LOG_DEBUG(log_message) \

if(foxr::log::foxr_logger::get_log_level() <= foxr::log::log_level_enum::log_level_debug ) \

foxr::log::foxr_logger::logger()->debug(__FILE__,__LINE__,log_message);

该宏在需要记录DEBUG级别日志的代码中使用。该宏用于判断如果当前日志级别比当前被记录日志等级宽松则记录日志。在程序编译时,该宏将被编译器展开成为一段实际代码。同时FILELINE为预定义宏,在编译时会展开为当前文件名和当前行数。

异步日志模块中环形缓存数据结构在实现过程中采用双向环形链表,日志记录器持有一指向当前被使用缓存块的指针。若当前缓存块被写满,则将指针指向下一缓存块,同时将fulled属性值加一指示当前被写满缓存块。当前缓存块被写满而下一缓存块也有被写入数据时,线程将自动创建一块空缓存块并插入到下一缓存块前,继续写入数据。当异步日志模块中fulled属性值大于用户设置最大允许值时,日志文件写入线程将被唤醒进行日志写入,该线程将获取日志记录器锁并将一部分被写满缓存块数据写入日志文件中,而后释放锁并进入阻塞状态,直到下一次被唤醒或阻塞超时。

数据库RESTful-HTTP处理器配置文件示例

{

 //该路由节点所处路径,即URL访问路径

 "path": "/student",

 //指定使用sqlite数据库RESTful HTTP处理器

 "processor": "sqlite:user",

 //该API用途为restful_api

 "usage": "restful_api",

 //指定使用预置LUA与权限鉴权器鉴权器

 //该鉴权器将完全匹配roles的对象通过鉴权

 "authenticator": "lua:test_and_authenticator",

 //鉴权器内部配置

 "lua:test_and_authenticator": {

  //鉴权器使用Cookie名

  "authenticator_cookie_name": "Auth",

 },

 //该URL下默认权限为所有人都有权访问

 "roles": "nobody",

 //sqlite处理器内部配置

 "sqlite:user": {

  //get方法配置块

  "get": [

   {

​    //使用默认路径即/student 访问该get配置sql块

​    //根据id查询用户名及密码

​    "sql": "select user_name,id,user_password from user where id=:id",

​    //参数列表

​    "params": [

​     {

​      //参数名,与sql中具名参数同

​      "name": "id",

​      //数据来源未查询字符串

​      "from": "query"

​     }

​    ],

​    //该API权限

​    "roles": "user",

​    //返回结果为单一对象

​    "result_type": "entity"

   },

   //get方法配置块

   {

​    //配置URL访问路径即/student/count

​    "path": "/count",

​    //查询用户表用户数

​    "sql": "select count(*) as count from user",

​    "roles": "user",

​    "result_type": "entity"

   }

  ],

  //post方法

  "post": [

   {

​    //插入用户

​    "sql": "insert into user(user_name,user_password) 

values(:user_name,:user_password);",

​    "roles": "user",

​    "authenticator": "lua:test_and_authenticator",

​    "lua:test_and_authenticator": {

​     "authenticator_cookie_name": "Auth",

​    },

​    "params": [

​     {

​      "name": "user_name",

​      //即前端发送该数据时用的数据名

​      "nick_name": "id",

​      //字符串长度数据校验器

​      "validator": "string_length_validator",

​      //校验器内部配置

​      "string_length_validator": {

​       //最大长度

​       "max_length": 16,

​       //最小长度

​       "min_length": 3

​      },

​      //参数来源

​      "from": "body"

​     },{

​      "name": "user_password",

​      //正则匹配数据校验器

​      "validator": "regex_match_validator",

​      "regex_match_validator": {

​       //正则表达式

​       //匹配不含空格同时有数据、字母、特殊字符的字符串

​       "regex": "^(?=.+\\d)(?=.+\\w)(?=.+\\W)\\S+$"},

​      //参数来源

​      "from": "body"

​     }

​    ]

   }

  ],

  "delete": [

   {

​    "sql": "delete from user where id=:id",

​    "roles": "user",

​    "params": [

​     {"name": "id","from": "query"}

​    ]

   }

  ]

 }

}


v1.0 wep 整理草稿发布文章

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注