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

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
云解析DNS,个人版 1个月
简介: 本项目仅仅是个人的玩具项目, 其中缺陷很多, 问题也很多. 首先整个网络部分是自己看了两三章UNP自己封出来的. 老版本还有一点点错误处理, 新版本完全忽略了错误处理. 另外就是过度设计, 许多的地方没有必要预留变更空间, 我都预留了.

本项目仅仅是个人的玩具项目, 其中缺陷很多, 问题也很多. 首先整个网络部分是自己看了两三章UNP自己封出来的. 老版本还有一点点错误处理, 新版本完全忽略了错误处理. 另外就是过度设计, 许多的地方没有必要预留变更空间, 我都预留了.

项目地址:https://github.com/lzxZz/dlagon

http通信

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


62.png当然了,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的结构如下图所示:


63.jpg

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

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

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


目前开发版本

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


64.jpg

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

IServer接口

首先介绍核心的 IServer接口.

//作为接口类, 
class IServer{
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 服务器中间件抽象
 * 
 * 在服务器流程中, 会自动的沿着中间件链一直执行, 通过返回值判断是否继续执行
 * 
 */
class Midware{
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;
};
dlagon/interface/midware.h
/**
 * @brief 服务器中间件抽象
 * 
 * 在服务器流程中, 会自动的沿着中间件链一直执行, 通过返回值判断是否继续执行
 * 
 */
class Midware{
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的内容没有介绍, 都是一些脏活, 项目中用到的大致上有下面的内容:

  1. HTTP解析(字符串解析, 基本技能)
  2. MIME-Type(判断请求uri的后缀)
  3. cookie, session(这个在老版本有实现, 新版本暂时没有开始)

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

对于模板页, 配置文件等暂时都没有开发计划.

相关文章
|
4天前
|
数据安全/隐私保护 C++ 计算机视觉
Qt(C++)开发一款图片防盗用水印制作小工具
文本水印是一种常用的防盗用手段,可以将文本信息嵌入到图片、视频等文件中,用于识别和证明文件的版权归属。在数字化和网络化的时代,大量的原创作品容易被不法分子盗用或侵犯版权,因此加入文本水印成为了保护原创作品和维护知识产权的必要手段。 通常情况下,文本水印可以包含版权声明、制作者姓名、日期、网址等信息,以帮助识别文件的来源和版权归属。同时,为了增强防盗用效果,文本水印通常会采用字体、颜色、角度等多种组合方式,使得水印难以被删除或篡改,有效地降低了盗用意愿和风险。 开发人员可以使用图像处理技术和编程语言实现文本水印的功能,例如使用Qt的QPainter类进行文本绘制操作,将文本信息嵌入到图片中,
14 1
Qt(C++)开发一款图片防盗用水印制作小工具
|
8天前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
27 0
|
3天前
|
设计模式 Rust 安全
深入理解PHP 7的新特性及其对现代Web开发的影响
本文通过数据驱动的分析,探讨了PHP 7的发布如何革新了Web开发的面貌。文章首先概述了PHP 7带来的性能提升与新特性,然后通过实际案例和性能测试数据,详细讨论了这些新特性对提高代码效率、增强安全性和支持现代编程范式的具体影响。最后,文章将评估PHP 7在当前Web开发环境中的地位,并对其未来的发展做出展望。
|
2天前
|
Java 应用服务中间件 Linux
Tomcat安装部署[单机软件],可以让用户开发的WEB应用程序,变成可以被访问的网页,Tomcat的使用需要jdk环境
Tomcat安装部署[单机软件],可以让用户开发的WEB应用程序,变成可以被访问的网页,Tomcat的使用需要jdk环境
|
3天前
|
JSON 安全 编译器
PHP 8的新特性及其对现代Web开发的影响
随着PHP 8的发布,这一流行的服务器端脚本语言带来了诸多改进和新增特性,这些变化不仅提升了语言本身的性能和安全性,还对现代Web开发实践产生了深远影响。本文将深入探讨PHP 8的关键新特性,包括JIT编译器、联合类型、命名参数、匹配表达式等,并分析它们如何优化代码编写、强化类型安全以及提高执行效率。同时,我们还将讨论这些新特性对开发者构建更快、更可靠应用程序的能力所产生的积极效应。
6 0
|
3天前
|
IDE 编译器 测试技术
PHP 8新特性解析及其对现代Web开发的影响
本文深入探讨了PHP 8版本中引入的新特性,并分析了这些变化如何影响现代Web开发的实践。通过引用最新的性能测试数据和开发者社区反馈,本文揭示了PHP 8在提升开发效率、增强代码安全性及优化性能方面所做出的贡献。同时,文章还讨论了PHP 8新特性对于现有项目升级路径的实际指导意义,为读者提供了关于是否以及如何迁移至PHP 8的洞见。
8 0
|
4天前
|
搜索推荐 编译器 测试技术
PHP 8新特性及其对现代Web开发的影响
随着PHP 8的发布,开发者社区迎来了一系列创新特性,这些特性旨在提升语言性能、增强类型系统并改进错误处理机制。本文将深入探讨PHP 8中的JIT编译器、联合类型、错误处理等关键更新,并通过实证数据展示这些变化如何优化代码执行和提高开发效率。我们将通过案例分析,阐述新特性在实际项目中的应用,以及它们如何影响未来PHP Web开发的走向。
|
4天前
|
编译器 PHP 开发者
探索PHP 8的新特性及其对现代Web开发的影响
【7月更文挑战第3天】随着PHP 8的发布,这个广受欢迎的服务器端脚本语言迎来了一系列创新的功能和性能提升。本文将深入探讨PHP 8中的新特性,分析它们如何优化代码编写流程、提高应用程序性能,并讨论这些变化给现代Web开发带来的深远影响。从联合类型到JIT编译器,我们将一窥PHP未来的发展蓝图。
|
5天前
|
存储 JavaScript 安全
深入理解与应用:在Web框架中高效管理环境变量
【7月更文挑战第3天】本文阐述了在Web开发中使用环境变量的重要性,如增强安全性和灵活性,并以Django、Flask和Express为例展示了如何管理这些变量。通过`os.environ`或特定库,开发者可以从环境中读取配置,避免敏感信息硬编码。最佳实践包括最小权限、加密、默认值、文档化和环境隔离,确保项目安全和易维护。
28 0
|
6天前
|
安全 编译器 测试技术
PHP 8新特性深度解析及其对现代Web开发的影响
本文旨在深入探讨PHP 8的新增特性及其在现代Web开发中的应用与影响。通过分析PHP 8引入的类型系统、性能改进、语言结构更新等关键变化,文章将展示这些新特性如何提升代码质量、加快执行速度,以及简化开发流程。引用权威数据和案例研究,结合科学方法论,本文将论证PHP 8为开发者带来的具体益处,并预测其对未来Web技术趋势的潜在影响。