http连接处理(中)(四)

2. 结合代码分析请求报文解析

上一节我们对http连接的基础知识、服务器接收请求的处理流程进行了介绍,接下来将结合流程图和代码分别对状态机和服务器解析请求报文进行详解。

流程图部分,描述主、从状态机调用关系与状态转移过程。

代码部分,结合代码对http请求报文的解析进行详解。

2.1 流程图与状态机

从状态机负责读取报文的一行,主状态机负责对该行数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机。

  主状态机

三种状态,标识解析位置。

  1. CHECK_STATE_REQUESTLINE,解析请求行
  2. CHECK_STATE_HEADER,解析请求头
  3. CHECK_STATE_CONTENT,解析消息体,仅用于解析POST请求

从状态机

三种状态,标识解析一行的读取状态。

  1. LINE_OK,完整读取一行
  2. LINE_BAD,报文语法有误
  3. LINE_OPEN,读取的行不完整

2.2 代码分析-http报文解析

上节中介绍了服务器接收http请求的流程与细节,简单来讲,浏览器端发出http连接请求,服务器端主线程创建http对象接收请求并将所有数据读入对应buffer,将该对象插入任务队列后,工作线程从任务队列中取出一个任务进行处理。

各子线程通过process函数对任务进行处理,调用process_read函数和process_write函数分别完成报文解析与报文响应两个任务。

void http_conn::process()
{HTTP_CODE read_ret=process_read();//NO_REQUEST,表示请求不完整,需要继续接收请求数据if(read_ret==NO_REQUEST){//注册并监听读事件modfd(m_epollfd,m_sockfd,EPOLLIN);return;}//调用process_write完成报文响应bool write_ret=process_write(read_ret);if(!write_ret){close_conn();}//注册并监听写事件modfd(m_epollfd,m_sockfd,EPOLLOUT);
}

本节将对报文解析的流程和process_read函数细节进行详细介绍。

HTTP_CODE含义:

表示HTTP请求的处理结果,在头文件中初始化了八种情形,在报文解析时只涉及到四种。

  1. NO_REQUEST
    • 请求不完整,需要继续读取请求报文数据
  2. GET_REQUEST
    • 获得了完整的HTTP请求
  3. BAD_REQUEST
    • HTTP请求报文有语法错误
  4. INTERNAL_ERROR
    • 服务器内部错误,该结果在主状态机逻辑switch的default下,一般不会触发

解析报文整体流程:

process_read通过while循环,将主从状态机进行封装,对报文的每一行进行循环处理。

  1. 判断条件
    • 主状态机转移到CHECK_STATE_CONTENT,该条件涉及解析消息体
    • 从状态机转移到LINE_OK,该条件涉及解析请求行和请求头部
    • 两者为或关系,当条件为真则继续循环,否则退出
  2. 循环体
    • 从状态机读取数据
    • 调用get_line函数,通过m_start_line将从状态机读取数据间接赋给text
    • 主状态机解析text
//m_start_line是行在buffer中的起始位置,将该位置后面的数据赋给text
//此时从状态机已提前将一行的末尾字符\r\n变为\0\0,所以text可以直接取出完整的行进行解析
char* get_line(){return m_read_buf+m_start_line;
}http_conn::HTTP_CODE http_conn::process_read()
{//初始化从状态机状态、HTTP请求解析结果LINE_STATUS line_status=LINE_OK;HTTP_CODE ret=NO_REQUEST;char* text=0;//这里为什么要写两个判断条件?第一个判断条件为什么这样写?//具体的在主状态机逻辑中会讲解。//parse_line为从状态机的具体实现while((m_check_state==CHECK_STATE_CONTENT && line_status==LINE_OK)||((line_status=parse_line())==LINE_OK)){text=get_line();//m_start_line是每一个数据行在m_read_buf中的起始位置//m_checked_idx表示从状态机在m_read_buf中读取的位置m_start_line=m_checked_idx;//主状态机的三种状态转移逻辑switch(m_check_state){case CHECK_STATE_REQUESTLINE:{//解析请求行ret=parse_request_line(text);if(ret==BAD_REQUEST)return BAD_REQUEST;break;}case CHECK_STATE_HEADER:{//解析请求头ret=parse_headers(text);if(ret==BAD_REQUEST)return BAD_REQUEST;//完整解析GET请求后,跳转到报文响应函数else if(ret==GET_REQUEST){return do_request();}break;}case CHECK_STATE_CONTENT:{//解析消息体ret=parse_content(text);//完整解析POST请求后,跳转到报文响应函数if(ret==GET_REQUEST)return do_request();//解析完消息体即完成报文解析,避免再次进入循环,更新line_statusline_status=LINE_OPEN;break;}default:return INTERNAL_ERROR;}}return NO_REQUEST;
}

从状态机逻辑:

上面基础知识讲解中,对于HTTP报文的讲解遗漏了一点细节,在这里作为补充。

在HTTP报文中,每一行的数据由\r\n作为结束字符,空行则是仅仅是字符\r\n。因此,可以通过查找\r\n将报文拆解成单独的行进行解析,项目中便是利用了这一点。

从状态机负责读取buffer中的数据,将每行数据末尾的\r\n置为\0\0,并更新从状态机在buffer中读取的位置m_checked_idx,以此来驱动主状态机解析。

  1. 从状态机从m_read_buf中逐字节读取,判断当前字节是否为\r
    • 接下来的字符是\n,将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK
    • 接下来达到了buffer末尾,表示buffer还需要继续接收,返回LINE_OPEN
    • 否则,表示语法错误,返回LINE_BAD
  2. 当前字节不是\r,判断是否是\n(一般是上次读取到\r就到了buffer末尾,没有接收完整,再次接收时会出现这种情况)
    • 如果前一个字符是\r,则将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK
  3. 当前字节既不是\r,也不是\n
    • 表示接收不完整,需要继续接收,返回LINE_OPEN
 if(m_checked_idx>1&&m_read_buf[m_checked_idx-1]=='\r'){m_read_buf[m_checked_idx-1]='\0';m_read_buf[m_checked_idx++]='\0';return LINE_OK;}return LINE_BAD;}}//并没有找到\r\n,需要继续接收return LINE_OPEN;
}

主状态机逻辑:

主状态机初始状态是CHECK_STATE_REQUESTLINE,通过调用从状态机来驱动主状态机,在主状态机进行解析前,从状态机已经将每一行的末尾\r\n符号改为\0\0,以便于主状态机直接取出对应字符串进行处理。

  1. CHECK_STATE_REQUESTLINE
    • 主状态机的初始状态,调用parse_request_line函数解析请求行
    • 解析函数从m_read_buf中解析HTTP请求行,获得请求方法、目标URL及HTTP版本号
    • 解析完成后主状态机的状态变为CHECK_STATE_HEADER
//解析http请求行,获得请求方法,目标url及http版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{//在HTTP报文中,请求行用来说明请求类型,要访问的资源以及所使用的HTTP版本,其中各个部分之间通过\t或空格分隔。//请求行中最先含有空格和\t任一字符的位置并返回m_url=strpbrk(text," \t");//如果没有空格或\t,则报文格式有误if(!m_url){return BAD_REQUEST;}//将该位置改为\0,用于将前面数据取出*m_url++='\0';//取出数据,并通过与GET和POST比较,以确定请求方式char *method=text;if(strcasecmp(method,"GET")==0)m_method=GET;else if(strcasecmp(method,"POST")==0){m_method=POST;cgi=1;}elsereturn BAD_REQUEST;//m_url此时跳过了第一个空格或\t字符,但不知道之后是否还有//将m_url向后偏移,通过查找,继续跳过空格和\t字符,指向请求资源的第一个字符m_url+=strspn(m_url," \t");//使用与判断请求方式的相同逻辑,判断HTTP版本号m_version=strpbrk(m_url," \t");if(!m_version)return BAD_REQUEST;*m_version++='\0';m_version+=strspn(m_version," \t");//仅支持HTTP/1.1if(strcasecmp(m_version,"HTTP/1.1")!=0)return BAD_REQUEST;//对请求资源前7个字符进行判断//这里主要是有些报文的请求资源中会带有http://,这里需要对这种情况进行单独处理if(strncasecmp(m_url,"http://",7)==0){m_url+=7;m_url=strchr(m_url,'/');}//同样增加https情况if(strncasecmp(m_url,"https://",8)==0){m_url+=8;m_url=strchr(m_url,'/');}//一般的不会带有上述两种符号,直接是单独的/或/后面带访问资源if(!m_url||m_url[0]!='/')return BAD_REQUEST;//当url为/时,显示欢迎界面if(strlen(m_url)==1)strcat(m_url,"judge.html");//请求行处理完毕,将主状态机转移处理请求头m_check_state=CHECK_STATE_HEADER;return NO_REQUEST;
}

解析完请求行后,主状态机继续分析请求头。在报文中,请求头和空行的处理使用的同一个函数,这里通过判断当前的text首位是不是\0字符,若是,则表示当前处理的是空行,若不是,则表示当前处理的是请求头。

  1. CHECK_STATE_HEADER
    • 调用parse_headers函数解析请求头部信息
    • 判断是空行还是请求头,若是空行,进而判断content-length是否为0,如果不是0,表明是POST请求,则状态转移到CHECK_STATE_CONTENT,否则说明是GET请求,则报文解析结束。
    • 若解析的是请求头部字段,则主要分析connection字段,content-length字段,其他字段可以直接跳过,各位也可以根据需求继续分析。
    • connection字段判断是keep-alive还是close,决定是长连接还是短连接
    • content-length字段,这里用于读取post请求的消息体长度
//解析http请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{//判断是空行还是请求头if(text[0]=='\0'){//判断是GET还是POST请求if(m_content_length!=0){//POST需要跳转到消息体处理状态m_check_state=CHECK_STATE_CONTENT;return NO_REQUEST;}return GET_REQUEST;}//解析请求头部连接字段else if(strncasecmp(text,"Connection:",11)==0){text+=11;//跳过空格和\t字符text+=strspn(text," \t");if(strcasecmp(text,"keep-alive")==0){//如果是长连接,则将linger标志设置为truem_linger=true;}}//解析请求头部内容长度字段else if(strncasecmp(text,"Content-length:",15)==0){text+=15;text+=strspn(text," \t");m_content_length=atol(text);}//解析请求头部HOST字段else if(strncasecmp(text,"Host:",5)==0){text+=5;text+=strspn(text," \t");m_host=text;}else{printf("oop!unknow header: %s\n",text);}return NO_REQUEST;
}

如果仅仅是GET请求,如项目中的欢迎界面,那么主状态机只设置之前的两个状态足矣。

前面提到过GET和POST请求报文的区别之一是有无消息体部分,GET请求没有消息体,当解析完空行之后,便完成了报文的解析。

但后续的登录和注册功能,为了避免将用户名和密码直接暴露在URL中,我们在项目中改用了POST请求,将用户名和密码添加在报文中作为消息体进行了封装。

为此,我们需要在解析报文的部分添加解析消息体的模块。

while((m_check_state==CHECK_STATE_CONTENT && line_status==LINE_OK)||((line_status=parse_line())==LINE_OK))

那么,这里的判断条件为什么要写成这样呢?

在GET请求报文中,每一行都是\r\n作为结束,所以对报文进行拆解时,仅用从状态机的状态line_status=parse_line())==LINE_OK语句即可。

但,在POST请求报文中,消息体的末尾没有任何字符,所以不能使用从状态机的状态,这里转而使用主状态机的状态作为循环入口条件。

那后面的&& line_status==LINE_OK又是为什么?

解析完消息体后,报文的完整解析就完成了,但此时主状态机的状态还是CHECK_STATE_CONTENT,也就是说,符合循环入口条件,还会再次进入循环,这并不是我们所希望的。

为此,增加了该语句,并在完成消息体解析后,将line_status变量更改为LINE_OPEN,此时可以跳出循环,完成报文解析任务。

  1. CHECK_STATE_CONTENT
    • 仅用于解析POST请求,调用parse_content函数解析消息体
    • 用于保存post请求消息体,为后面的登录和注册做准备
//判断http请求是否被完整读入
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{//判断buffer中是否读取了消息体if(m_read_idx>=(m_content_length+m_checked_idx)){text[m_content_length]='\0';//POST请求中最后为输入的用户名和密码m_string = text;return GET_REQUEST;}return NO_REQUEST;
}

状态机和HTTP报文解析是项目中最繁琐的部分,这次我们一举解决掉它,希望对各位小伙伴在理解项目的过程中有所帮助。

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

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

相关文章

【云原生】k8s之Ingress

1.Ingress的相关知识 1.1 Ingress的简介 service的作用体现在两个方面,对集群内部,它不断跟踪pod的变化,更新endpoint中对应pod的对象,提供了ip不断变化的pod的服务发现机制;对集群外部,他类似负载均衡器…

HTML5学习简记(更新中~)

目录 HTML定义 标签 HTML基本骨架 常见标签 标题标签 段落标签 换行与水平线标签 文本格式化标签 图像标签 绝对路径与相对路径 超链接标签 音频与视频标签 列表标签 无序列表 有序列表 定义列表 表格标签 表格结构标签 合并单元格 表单标签 input标签 input标签占…

linux中的sendmail发送邮件

Linux/UNIX 下的老牌邮件服务器。 Sendmail 作为一种免费的邮件服务器软件,已被广泛的应用于各种服务器中,它在稳定性、可移植性、及确保没有 bug 等方面具有一定的特色,且可以在网络中搜索到大量的使用资料。 一、邮件发送原理图 MUA&#x…

腾讯云对象存储联合DataBend云数仓打通数据湖和数据仓库

随着数字化进程不断深入,数据呈大规模、多样性的爆发式增长。为满足更多样、更复杂的业务数据处理分析的诉求,湖仓一体应运而生。在Gartner发布的《Hype Cycle for Data Management 2021》中,湖仓一体(Lake house)首次…

性能测试学习阶段性总结

目录 1.前言 2.概念部分 2.1不同角度看软件性能 2.2关键词 2.3测试的方法 2.4应用领域 3.性能测试过程模型(PTGM) 2.1测试前期准备 2.2测试工具引入 2.3测试计划 2.4测试设计与开发 2.5测试执行和管理 2.6测试分析 总结: 1.前言…

Django admin管理工具TabularInline表格内联

详解 TabularInline 是 Django Admin 中的一个内联模型选项,用于在父模型的编辑页面中以表格形式显示关联的子模型对象。下面是对 TabularInline 的一些详解: 显示方式:TabularInline 以表格的形式显示子模型对象。每个子模型对象将以一行的…

vue实现左右布局(右侧超出的时候换行展示)

目录 vue实现左右布局(右侧超出的时候换行展示)code效果 vue实现左右布局(右侧超出的时候换行展示) code <ul class"body-detail"><li><div class"li-label">姓名</div><div class"li-value">XXXXXXXXXXXXXXXXXX…

SpringBoot 实现 elasticsearch 查询操作(RestHighLevelClient 的案例实战)

文章目录 1. 环境准备1. 查询全部2. 根据 name 查询 match 分词查询3. 根据 name 和 品牌查询 multiMatch 分词查询4. 根据 brand 查询 match 分词查询5. 按照价格 范围查询6. 精确查询7. boolQuery8. 分页9. 高亮查询9. 公共解析 上一节讲述了 SpringBoot 实现 elasticsearch …

Sentinel整合OpenFegin

之前学习了openFeign的使用&#xff0c;我是超链接 现在学习通过Sentinel来进行整合OpenFegin。 引入OpenFegin 我们需要在当前的8084项目中引入对应的依赖 <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-sta…

栈练习题(逆波兰表达式,有效括号,出入栈次序匹配,最小栈)

目录 基础知识: 中缀表达式和后缀表达式(逆波兰式) 中缀表达式转后缀表达式 后缀表达式求结果 有效括号 栈的压入,弹出序列 最小元素栈 基础知识: 栈:是一种先入后出的数据结构,它的底层是由数组实现的 入栈:push(),出栈pop(),查看栈顶元素peek() 中缀表达式和后缀表…

SpringBoot 集成 EasyExcel 3.x 实现 Excel 导出

目录 EasyExcel官方文档 EasyExcel是什么&#xff1f; EasyExcel注解 springboot集成EasyExcel 简单入门导出 &#xff1a; 实体类 自定义转换类 测试一下 复杂表头一对多导出 &#xff1a; 自定义注解 定义实体类 自定义单元格合并策略 测试一下 EasyExcel官方文档 …

SpringCloud学习路线(5)—— Nacos配置管理

一、统一配置管理 需求&#xff1a; 微服务配置能实现统一的管理&#xff0c;比如希望改动多个配置&#xff0c;但不希望逐个配置&#xff0c;而是在一个位置中改动&#xff0c;并且服务不用重启即用&#xff08;热更新&#xff09;。 &#xff08;一&#xff09;使用配置管理…

AN OVERVIEW OF LANGUAGE MODELS RECENT DEVELOPMENTS AND OUTLOOK

LLM系列相关文章&#xff0c;针对《AN OVERVIEW OF LANGUAGE MODELS: RECENT DEVELOPMENTS AND OUTLOOK》的翻译。 语言模型综述&#xff1a;近年来的发展与展望 摘要1 引言2 语言模型的类型2.1 结构化LM2.2 双向LM2.3 置换LM 3 语言单元3.1 字符3.2 单词和子单词3.2.1 基于统…

十八、Unity游戏引擎入门

1、下载 首先需要下载Unity Hub,下载网址:https://unity.com/cn。 然后在其中下载Unity编辑器并安装,可选择最新版本。 接着需要选择适合的开发环境,例如Android Studio或Xcode,以便进行手机游戏开发。在安装完Unity后,需要根据项目需求下载对应的模块和插件…

CRM排名前三的的系统有哪些特点?

crm经过多年的发展&#xff0c;不仅可以管理好客户关系还是企业重要的战略武器。让企业的销售、市场营销和客服服务部门建立密切联系&#xff0c;在crm一个平台上处理商机&#xff0c;简化业务流程&#xff0c;为组织降本增效。国内crm系统排名哪些技术商更靠前&#xff1f; 1…

微服务sleuth+zipkin——链路追踪

一、链路追踪&#x1f349; 1.什么是链路追踪&#xff1f;&#x1f95d; 在大型系统的微服务化构建中&#xff0c;一个系统被拆分成了许多模块。这些模块负责不同的功能&#xff0c;组合成系统&#xff0c;最终可以提供丰富的功能。在这种架构中&#xff0c;一次请求往往需要…

vue或react当中canvas实现电子签名组件和使用canvas进行图片压缩

<template><div><h1>vue3</h1><canvas id"canvasWrite"> 浏览器不支持Canvas,请升级浏览器 </canvas><div><button class"submit" click"submitWrite">提交签名</button><button clas…

vscode debug的方式

在.vscode文件夹下建立launch.json 例子1&#xff1a;调试python 来自 https://github.com/chunleili/tiPBD/tree/amg {"version": "0.2.0","configurations": [{"name": "hpbd 5 5","type": "python&quo…

Java开发中使用sql简化开发

引语&#xff1a; 在Java开发中&#xff0c;我们更希望数据库能直接给我们必要的数据&#xff0c;然后在业务层面直接进行使用&#xff0c;所以写一个简单的sql语句有助于提高Java开发效率&#xff0c;本文由简单到复杂的小白吸收&#xff0c;还请多多指教。 使用MySQL数据库…

Stable Diffusion学习笔记

一些零散笔记 灰常好的模型网站 LiblibAI哩布哩布AI-中国领先原创AI模型分享社区 出图效率倍增&#xff01;47个高质量的 Stable Diffusion 常用模型推荐 - 优设网 - 学设计上优设 关键词Prompt顺序 画质 风格 主体 外表、描述 表情、情绪 姿势 背景 杂项 同时可以…