HttpContext模块 --- http上下文模块

            

目录

模块设计思想

模块代码实现


模块设计思想

上下文模块是Http协议模块中最重要的一个模块,他需要控制请求处理的节奏,需要保存一个HttpRequest对象,后续关于这个连接的http的处理的信息全部都是在这个上下文中保存。

首先,上下文模块需要控制http请求接收和处理的节奏,那么我们其实是需要用一个变量来表明当前处于处理报文的哪一个阶段。可以用一个枚举量来作为处理的进度或者状态。

//处理状态
enum HttpRecvStatu{RECV_ERR,   //接收错误RECV_LINE,  //接收请求行RECV_HEAD,  //接收头部RECV_BODY,  //接收正文RECV_OVER   //接收完毕
};

同时,由于收到的http请求报文可能是会出错的,而出错的话,我们是不会将这个报文进行业务的处理的,而是直接返回一个请求错误的状态码的报文,那么我们的上下文当中不可避免的还需要保存一个变量用来保存状态码。

当然还需要保存一个HttpRequest对象用来存储获取到的请求的要素。

//请求处理上下文
class HttpContext
{
public:HttpRecvStatu _recv_statu;  //处理进度int _resp_statu;            //响应状态码HttpRequest _req;           //请求
public:HttpContext():_recv_statu(RECV_LINE),_resp_statu(200){}
};

接口分析

他提供给外部的接口其实很少,一个是获取响应状态码,一个是获取处理进度,还有就是获取到内部的Request对象,以及接收并解析http请求的接口,当然也还需要一个Reset接口,还是一样的,可能是长连接,后续还有报文需要处理。

    HttpContext():_recv_statu(RECV_LINE),_resp_statu(200){}void Reset();int RespStatu()const;HttpRecvStatu RecvStatu()const;HttpRequest& GetRequest();void RecvHttpRequest();

这其中最复杂的接口就是RecvHttpRequest也就是接收并解析Http请求的接口,这个接口我们需要使用多个接口进行辅助处理。

模块代码实现

首先实现前四个简单的接口:

void Reset(){_recv_statu = RECV_LINE;_resp_statu = 200;_req.Reset();}int RespStatu()const {return _resp_statu;}HttpRecvStatu RecvStatu()const {return _recv_statu;}HttpRequest& GetRequest() {return _req;}

然后就是最重要得接收数据得接口了。

我们先来把解析请求的几个小接口写出来。

首先,我们第一步需要解析请求行,要解析请求行首先需要获取请求行,那么我们就需要两个接口,一个是提取出请求行,一个是解析请求行。

解析请求行的时候我们使用的正则表达式是这个:

(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?

在这个正则表达式的匹配结果中,如果我们的url中没有携带参数,那么参数部分的匹配结果就是一个空串,他也是在matches里面的,这一点我们不需要关心,因为后续我们解析参数的时候会将这种情况给他处理了。

//解析处理请求行void RecvLine(Buffer* buffer) {if(_recv_statu != RECV_LINE) return;//1 获取请求行std::string line = buffer->GetLineAndPop();if(line == "")  //没有获取到一行,此时需要判断是数据不够还是因为数据接受错误了{if(buffer->ReadSize() > MAX_LINE_SIZE)  //大于8192{_recv_statu = RECV_ERR;                 //说明解析错误,报文在接收的时候有问题_resp_statu = 414;                      //url too long}   return;}//2 解析请求行return HandlerLine(line);}void HandlerLine(const std::string& line){if(_recv_statu != RECV_LINE) return;//使用正则表达式进行解析std::smatch matches;std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");bool ret = std::regex_match(line,matches,e);if(ret == false)        //说明请求出错{_recv_statu = RECV_ERR;_resp_statu = 400;      //Bad Requestreturn;}//走到这里说明正则匹配成功,而smacth是重载了方括号运算符的,我们可以直接使用_req._method = matches[1];//这里我们需要对方法进行处理,将其转换成大写,因为可能会有不标准的请求将方法写成小写。std::transform(_req._method.begin(),_req._method.end(),_req._method.begin(),::toupper);//前两个参数表示转换的数据的范围,第三个参数表示转换之后的目的地址,第四个参数表示转换的方法,使用C库的全局的toupper函数来转大写_req._path = Util::UrlDecode(matches[2],false);     //url需要进行解码,不需要+转空格std::string params = Util::UrlDecode(matches[3],true); //参数需要及逆行解码,需要+转空格//然后就是将参数解析为kv的格式std::vector<std::string> arr;Util::Split(params,"&",&arr);   //  参数以param进行分割for(auto&s:arr) //然后逐个提取每一个kv式的参数{  std::vector<std::string> kv;int ret = Util::Split(s,"=",&kv);if(ret != 2) //如果不是一个kv,那么就报错{_recv_statu = RECV_ERR;_resp_statu = 400;  //Bad Requestreturn;}//提取出来就放到参数的 map 中_req.AddParam(kv[0],kv[1]);}//最后就是提取版本号_req._version = matches[4];}   

然后就是获取解析头部字段

头部字段和正文之间的间隔就是一个 \r\n ,也就是说如果我们某一次提取一行内容,只提取到一个 \r\n 或者\n,那么说明这就是我们的空行了,头部字段提取完了。

//获取解析头部字段void RecvHeader(Buffer* buffer){if(_recv_statu != RECV_HEAD) return;//提取每一行while(1){//1 获取头部字段std::string line = buffer->GetLineAndPop();if(line == "")  //没有获取到一行,此时需要判断是数据不够还是因为数据接受错误了{if(buffer->ReadSize() > MAX_LINE_SIZE)  //大于8192{_recv_statu = RECV_ERR;                 //说明解析错误,报文在接收的时候有问题_resp_statu = 414;                      //url too long}   return;}//2 解析头部字段if(line == "\r\n" || line == "\n"){//头部字段解析完了_recv_statu = RECV_BODY;return;}//否则解析这一行bool ret = HandlerHeader(line);if(!ret) return;    //因为头部字段可能会有问题,需要使用一个返回值来判别}}bool HandlerHeader(std::string& line){if(_recv_statu != RECV_HEAD) return false; //先去掉回车和换行if(line.back() == '\n') line.pop_back();if(line.back() == '\r') line.pop_back();std::vector<std::string> kv;int ret = Util::Split(line,": ",&kv);if(ret != 2) {_recv_statu = RECV_ERR;_resp_statu = 400;      //Bad Requestreturn false;}_req.AddHeader(kv[0],kv[1]);return true;}

最后就是获取正文的接口,正文的获取其实也很简答。 如果请求中携带正文,那么头部字段中就一定会携带 Content-Length 字段,所以我们可以通过这个正文长度字段来决定我们要获取的正文的大小。 但是并不是说必须一次就把所有的正文全部接受,有可能当前缓冲区的数据并不是完整的正文,而是正文的一部分,这时候我们也要先接收,不能让他烂在缓冲区中。 那么未来我们需要根据_body.size 和正文长度这两个字段来判断从缓冲区中拿多少数据。

//获取正文void RecvBody(Buffer* buffer){if(_recv_statu != RECV_BODY) return;int len = _req.ContentLength();if(len ==0) {_recv_statu = RECV_OVER; //没有正文,直接返回return;}int size = len - _req._body.size(); //还需要接收的正文的长度if(size > buffer->ReadSize()) //说明不够,能读多少就读多少{_req._body.append(buffer->ReadPosition(),buffer->ReadSize());buffer->MoveReadOffset(buffer->ReadSize());return;}//走到这里说明能读完当前请求的正文_req._body.append(buffer->ReadPosition(),size);buffer->MoveReadOffset(size);_recv_statu = RECV_OVER;}

那么功能接口都写出来了,读取请求的接口无非就是调用上面的这些接口了,那么怎么设计呢?很简单,使用一个switch case 语句就完事了。

    void RecvHttpRequest(Buffer* buffer){switch(_recv_statu){case RECV_LINE: RecvLine(buffer);   //不需要break,因为可能提取完请求行之后还有后续的内容可以提取,就算请求行没提取完,每一个提取的函数前面有一个判断,可以直接返回case RECV_HEAD: RecvHeader(buffer);case RECV_BODY: RecvBody(buffer);}}

那么我们的上下文模块的接口就实现完了,目前我们也只进行编译,没有发现问题。

后续测试整个服务器的时候如果出现问题我们会再来修正bug。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/456334.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

高边坡稳定安全监测预警系统解决方案

一、项目背景 高边坡的滑坡和崩塌是一种常见的自然地质灾害&#xff0c;一但发生而没有提前预告将给人民的生命财产和社会危害产生严重影响。对高边坡可能产生的灾害提前预警、必将有利于决策者采取应对措施、减少和降低灾害造成的损失。现有的高边坡监测技术有人工巡查和利用测…

100个候选人,没一个能讲明白什么是自动化框架?

什么是自动化测试框架 01 什么是框架 框架是整个或部分系统的可重用设计&#xff0c;表现为一组抽象构件及构件实例间交互的方法。它规定了应用的体系结构&#xff0c;阐明了整个设计、协作构件之间的依赖关系、责任分配和控制流程&#xff0c;表现为一组抽象类以及其实例之间…

格姗知识圈博客网站开源了!

格姗知识圈博客 一个基于 Spring Boot、Spring Security、Vue3、Element Plus 的前后端分离的博客网站&#xff01;本项目基本上是小格子一个人开发&#xff0c;由于工作和个人能力原因&#xff0c;部分技术都是边学习边开发&#xff0c;特别是前端&#xff08;工作中是后端开…

MySQL~表的操作(创建表,查看表,修改表,删除表)

1.创建表 1.1.创建表 首先要选择需要操作的数据库&#xff0c;USE 数据库名&#xff0c;后续可以根据实际情况操作时添加。 USE fruitsales;建表语法&#xff1a; create table 表名( 字段名1 数据类型, 字段名2 数据类型, ); 实例&#xff1a;创建fruit_bak1表。 create t…

[linux]软件安装

安装方式 二进制发布包安装: 软件已经针对具体平台编译打包发布&#xff0c;只要解压修改配置即可 rpm安装: 软件已经按照redhat的包管理规范进行打包, 使用rpm命令进行安装&#xff0c;不能自行解决库依赖问题 yum安装: 一种在线软件安装方式, 本质上还是rpm安装, 自动下载…

【vim】手动安装 Leader-F

LeaderF 是一个功能强大的 Vim 插件&#xff0c;主要用于快速导航和搜索。它可以帮助用户在 Vim 中高效地查找文件、缓冲区、标签、函数等各种元素&#xff0c;极大地提高了编辑效率。 LeaderF 的安装如果按照仓库中的教程来的话可以很方便的实现安装&#xff0c;这里介绍一下…

【记录】VSCode|自用设置项

文章目录 1 基础配置1.1 自动保存1.2 编辑区自动换行1.3 选项卡换行1.4 空格代替制表符1.5 开启滚轮缩放 2 进阶设置2.1 选项卡不自我覆盖2.2 选项卡限制宽度2.3 选项卡组限制高度2.4 字体设置2.5 字体加粗2.6 侧边栏2.7 沉浸式代码模式 Zen Mode2.8 设置 Zen 模式的选项卡组 3…

家用wifi的ip地址固定吗?换wifi就是换ip地址吗

在探讨家用WiFi的IP地址是否固定&#xff0c;以及换WiFi是否就意味着换IP地址这两个问题时&#xff0c;我们首先需要明确几个关键概念&#xff1a;IP地址、家用WiFi网络、以及它们之间的相互作用。 一、家用WiFi的IP地址固定性 家用WiFi环境中的IP地址通常涉及两类&#xff1a…

文档透明加密系统怎么用?五款透明加密软件汇总!2024热门推荐,实测分享!

数据泄露事件频发&#xff0c;让无数企业谈之色变。 想要自动对存储在计算机上的文档进行加密吗&#xff1f; 怎么在不影响日常工作的前提&#xff0c;确保文档在存储和传输过程中的安全&#xff1f; 透明加密系统来助力&#xff01; 本文&#xff0c;将详细介绍文档透明加密…

解决vue使用pdfdist-mergeofd插件时报错polyfills

pdfdist-mergeofd 该插件主要是为了解决pdf-js和ofd-js共同使用时产生的依赖冲突问题&#xff0c;具体可看这位博主的文章同时使用ofdjs和pdfjs遇到的问题&#xff0c;和解决方法——懒加载 首先看下报错信息 ERROR in ./node_modules/.pnpm/pdfdist-mergeofd2.2.228_webpa…

人工智能算法之双倍体遗传算法(DGA)

人工智能算法之双倍体遗传算法&#xff08;DGA&#xff09; 双倍体遗传算法是一种改进的遗传算法&#xff0c;借鉴了生物中双倍体&#xff08;每个体细胞中具有两套染色体&#xff09;的遗传机制。传统遗传算法中的个体通常是单倍体&#xff08;单套基因&#xff09;&#xff0…

使用 v-html 指令渲染的标签, 标签内绑定的 click 事件不生效

背景 在项目开发中&#xff0c;实现用户友好的输入交互是提升用户体验的关键之一。例如&#xff0c;在客服对话框中&#xff0c;其中有包含多个快捷选项用于快速问答&#xff0c;每个快捷选项都是一个可点击的按钮&#xff0c;并需要绑定点击事件来执行相应操作。然而&#xf…

数据类型【MySQL】

文章目录 建立表查看表删除表数据类型floatcharvarcharchar&&varchar 时间日期类型enum和setenum和set查找 建立表 mysql> create table if not exists user1(-> id int ,-> name varchar (20) comment 用户名 ,-> password char (32) comment 用户名的…

软考(中级-软件设计师)算法分析篇(1024)

三、算法设计与分析 #1024程序员节|正文# 一、分治法 1.1 分而治之 对于一个规模为n的问题&#xff0c;若该问题可以容易的解决&#xff08;比如说规模较小&#xff0c;则直接解决&#xff0c;否则将其分解为k个规模较小的问题&#xff0c;这些子问题相互独立且与原问题形…

数组类型应用举例

在main.cpp里输入程序如下&#xff1a; #include "stdio.h" //使能printf()函数 #include <stdlib.h> //使能exit(); #define My_array_Size 10 //定义用My_array_Size代替 unsigned char My_array[My_array_Size]; //声明数组My_arra…

集群分发脚本

我的后端学习大纲 我的Linux环境搭建学习大纲 8.2.scp安全拷贝: 1.命令格式&#xff1a;scp -r $pdir/$fname $user$host:$pdir/$fname2.具体命令&#xff1a; scp -r jdk1.8.0_321/ rootHadoop104:/opt/module 3.实际操作&#xff1a; 3.1.在hadoop2和hadoop3&#xff0c;had…

Verilog 0x01 基础

硬件描述语言 0x00 数电逻辑符号 与 & 或 | 异或 ^ 同或 ~^0x01 基本结构 1.1 线网&#xff08;wire&#xff09; wire 类型表示硬件单元之间的物理连线&#xff0c;由其连接的器件输出端连续驱动 如果没有驱动元件连接到 wire 型变量&#xff0c;缺省值一般为 “Z” …

h5页面与小程序页面互相跳转

小程序跳转h5页面 一个home页 /pages/home/home 一个含有点击事件的元素&#xff1a;<button type"primary" bind:tap"toWebView">点击跳转h5页面</button>toWebView(){ wx.navigateTo({ url: /pages/webview/webview }) } 一个webView页 /pa…

数据结构——队列和栈

目录 一、栈 1、概念与结构 2、栈的结构与初始化 3、入栈 4、出栈 5、取栈顶元素 6、取栈中有效元素个数 7、栈是否为空 二、队列 1、概念与结构 2、队列的结构与初始化 3、入队列 4、出队列 5、取队头数据 6、取队尾数据 7、队列判空 8、队列中有效元素个数 练习题目链 一…

(一)Mysql篇---Mysql整体架构

MySql框架浅析 首先&#xff0c;上一张图先让各位看看大致结构&#xff1a; 从上到下&#xff0c;依次说一下结构&#xff1a; 连接层&#xff1a;这里主要是处理客户端和数据库连接的&#xff0c;直接使用的Tomcat的连接池&#xff0c;可以调整最大连接数&#xff1b; 服务…