使用C++开发的web框架dlagon

作者 : 慕源网 本文共3550个字,预计阅读时间需要9分钟 发布时间: 2021-09-20 共561人阅读

 

一个web服务器, 其通信是基于请求/响应的. 因此一个最基本的web服务器如下图所示:

  当然了,web是基于HTTP协议的, 因此请求和响应的格式都需要符合HTTP协议的格式. 请求的协议格式由浏览器控制, 响应的格式由服务端控制. 因此在开发的时候, 我们需要控制的只有响应的格式. 但是请求的格式也是需要关注的. 对于不同的参数我们需要进行不同的控制.

 

一个最基本的HTTP响应如下所示:

 

200 OK  HTTP/1.1

 

Content-Type:text/html

 

<h1>Hello World</h1>

 

响应中的三个部分(响应行, 响应头, 响应体)都是有的. 需要注意的是,在响应头和响应体之间有一个空行. (更多的信息请查看http协议的格式, 这里有一点偏离主题了).

 

初代机版本

 

一个web框架在通信上也无非是和上面做的事情一样. 框架和应用的区别就在于框架封装了应用的稳定部分流程. 假如从接受数据开始,一直编写到数据发送的web应用. 其中要经过以下步骤:

 

1.数据读取

 

2.协议解析(长链接还可能需要处理粘包的问题)

 

3.cookie读取, 判断属于哪一个session

 

4.请求转发到具体的处理函数(对于不同的页面有不同的处理, 例如静态页面和动态页面就需要分开处理)

 

5.具体处理, 生成响应

 

6.将响应转化为字符串序列

 

7.数据发送

 

这七个步骤, 除去4,5 步是根据需求变更的, 其他的5个步骤都是固定的. 那么框架所作的任务就是将固定的5个步骤封装起来. 预留出接口, 等待用户(web开发人员)编写处理函数, 注册路由信息即可. 在一些现代的web框架中, 路由的注册往往是通过文件来实现的. 这样一来, 代码中会变动的只有处理函数了.

 

因此, dlagon的结构如下图所示:

  其中缺少了cookie和session的处理, 不过也无伤大雅, 开发的时候自己加上即可.

 

其中需要用户编写的部分就是路由和处理函数部分了.路由表我使用了 key-value的形式, key为请求路径, value为处理函数指针. 在项目的 server/route.h中可以看到定义.

 

这个版本在能够完成静态文件(只有html, js, css三种文件格式)服务之后, 渐渐的就停止了开发. 然后就对项目又一次的进行了重构. 参考了 koa的洋葱模型.

 

目前开发版本

 

上个版本中有不够灵活的问题, 例如我想添加 cookie和 session部分, 就需要对整个框架进行修改, 在路由和解析中间强行加入一个组件.违背了设计模式中的对扩展开放, 对修改封闭的原则. 因此新的架构如下:

  目前这个版本正处于开发之中, 在分支 new_architecture中.

 

IServer接口

 

首先介绍核心的 IServer接口.

 

//作为接口类,

 

classIServer{

 

public:

 

IServer(IProtocolObjectFactory*factory,

 

INetServerSocketAdapter*server,

 

Midware*midware)

 

:factory_(factory),s

 

erver_socket_(server),

 

midware_(midware){}

 

// IServer() = delete; // 保留默认构造函数, 用于工厂构造.

 

/**

 

* @brief 用户使用的唯一程序, 其作用为绑定本地任意ip的端口,并开始监听,处理

 

*

 

* @param port

 

*/

 

void Run(int port){

 

server_socket_->Bind(port);

 

server_socket_->Listen(1024);

 

for(;;){

 

INetClientSocketAdapter*client=server_socket_->Accept();

 

// TODO 应该使用线程池

 

std::thread th(Work,this,client);

 

th.detach();

 

}

 

}

 

virtual~IServer(){}

 

private:

 

// 实际工作流程

 

static void Work(IServer*self,INetClientSocketAdapter*client){

 

// 获取请求

 

std::string str=client->Receviced();

 

Request*req=self->factory_->RequestFromString(str);

 

Response*res=self->factory_->GetResponse();

 

// 调用中间件, 开始处理请求

 

self->midware_->WorkFlow(*req,*res);

 

//返回响应

 

const std::string result=res->ToString();

 

client->Send(result);

 

delete client;

 

delete req;

 

delete res;

 

}

 

protected:

 

IProtocolObjectFactory*factory_;

 

INetServerSocketAdapter*server_socket_;

 

Midware*midware_;

 

};

 

首先介绍三个 protected成员

 

factory_是一个抽象工厂, 用来将读取到的消息转化为对应的消息对象.

 

server_socket_是一个网络套接字, 使用了适配器模式, 方便替换其实现为别的网络库.

 

midware_是中间件部分, 所有的消息都通过中间件进行处理.

 

然后就是 Work方法.这是本类中最最核心的内容. 因为需要用到多线程, 线程只能绑定静态成员函数. 因此函数的首个参数就是 this指针.

 

然后调用网络客户端的 Recevied方法, 接收信息.

 

然后使用协议的构造工厂, 构造出请求对象和响应对象.(由于没有做长链接的处理, 所有的套接字都是用一次, 因此没有粘包问题, 这里也就没有特殊处理. 如果加上了长链接这个是需要特殊处理的.)

 

之后就是调用中间件处理请求. 同时也将响应对象传递进去.

 

处理完之后, 调用响应对象的 ToString方法, 然后将信息发送回去.

 

最后清理一下内存(这里打算使用智能指针, 将内存管理全部交给RAII).

 

接下来介绍一下中间件

 

Midware

 

dlagon/interface/midware.h

 

/**

 

* @brief 服务器中间件抽象

 

*

 

* 在服务器流程中, 会自动的沿着中间件链一直执行, 通过返回值判断是否继续执行

 

*

 

*/

 

classMidware{

 

public:

 

enum class MidwareState{

 

kStop,

kContinue,

 

};

 

public:

 

void SetNext(Midware*next){

 

next_=next;

 

}

 

//中间件执行流程

 

void WorkFlow(const Request &req,Response &res);

 

protected:

 

// 中间件的操作, 返回值指示是否需要继续执行

 

virtual MidwareState Handler(const Request &req, Response &res)=0;

 

Midware*next_=nullptr;

 

};

 

这个组件十分简单,

 

next指针, 指向下一个中间件.

 

纯虚函数 Handler, 继承后重载该函数,来进行业务流程.

 

WorkFlow函数, 用于判断是否继续执行.

 

WrokFlow实现如下:

 

void Midware::WorkFlow(const Request &req,Response &res){

 

MidwareState state= Handler(req,res);

 

// 判断链是否继续调用

 

if(state==MidwareState::kContinue){

 

if(next_){

 

next_->Handler(req,res);

 

}

 

}

 

}

 

这里在使用职责链的时候,做了一些变化, 并没有使用常见的成员变量表示是否继续的方法, 因为考虑到多线程的问题, 而中间件又是单例的, 因此, 如果是数据成员, 就会存在数据竞争的问题. 因此我使用了函数的返回值作为状态标识.

 

以上就是整个框架的流程部分了. 后面的就是一些实现的细节内容. 只需要继承 Midware, 然后根据自己的需要编写流程函数.

 

此外还有关于 HTTP的内容没有介绍, 都是一些脏活, 项目中用到的大致上有下面的内容:

 

HTTP解析(字符串解析, 基本技能)

 

MIME-Type(判断请求uri的后缀)

 

cookie, session(这个在老版本有实现, 新版本暂时没有开始)

 

这里先说一个踩过的坑吧, HTTP请求的行分隔符是 , 在使用 getline函数的时候, 会自动的省略掉 , 因此行结尾和空行一定是一个

 


慕源网 » 使用C++开发的web框架dlagon

常见问题FAQ

程序仅供学习研究,请勿用于非法用途,不得违反国家法律,否则后果自负,一切法律责任与本站无关。
请仔细阅读以上条款再购买,拍下即代表同意条款并遵守约定,谢谢大家支持理解!

发表评论

开通VIP 享更多特权,建议使用QQ登录