05 http连接处理(中)

news2024/9/23 1:29:20

05 http连接处理(中)

流程图与状态机

从状态机负责读取报文的一行,主状态机负责对该行数据进行解析,主状态机内部调用从状态机,从状态机驱动主状态机
在这里插入图片描述

主状态机

三种状态,标识解析位置

  • CHECK_STATE_REQUESTLINE,解析请求行
  • CHECK_STATE_HEADER,解析请求头
  • CHECK_STATE_CONTENT,解析消息体,仅用于解析POST请求

从状态机

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

  • LINE_OK,完整读取一行
  • LINE_BAD,报文语法有误
  • LINE_OPEN,读取的行不完整

代码分析-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);
}

HTTP_CODE含义

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

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

解析报文整体流程

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

  • 主状态机转移到CHECK_STATE_CONTENT,该条件涉及解析消息体
  • 从状态机转移到LINE_OK,该条件涉及解析请求行和请求头部
  • 两者为或关系,当条件为真则继续循环,否则退出

循环体

  • 从状态机读取数据
  • 调用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_check_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_status
				line_status=LINE_OPNE;
				break;
			}
			default:
			return INTERNAL_ERROR;
		}
	}
	return NO_REQUEST;
}

从状态机逻辑

在HTTP报文中,每一行的数据由\r\n作为结束字符,空行则是仅仅是字符\r\n。因此,可以通过查找\r\n将报文拆解成单独的行进行解析。
从状态机负责读取buffer中的数据,将每行数据末尾的\r\n置为\0\0,并更新从状态机在buffer中读取的位置m_checked_idx,以此驱动主状态机解析。
从状态机从m_read_buf中逐字节读取,判断当前字节是否为\r

  • 接下来的字节是\n,将\r\n修改成\0\0,将m_check_idx指向下一行的开头,则返回LINE_OK
  • 接下来达到了buffer末尾,表示buffer还需要继续接收,返回LINE_OPEN
  • 否则,表示语法错误,返回LINE_BAD

当前字节不是\r,判断是否是\n(一般是上次读取到\r就到了buffer末尾,没有接收完整,再次接收时会出现这种情况)

  • 如果前一个字符是\r,则将\r\n修改成\0\0,将m_check_idx指向下一行的开头,则返回LINE_OK

当前字节既不是\r也不是\n

  • 表示接收不完整,需要继续接收,返回LINE_OPEN
//从状态机,用于分析出一行内容
//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN

//m_read_idx指向缓冲区m_read_buf的数据末尾的下一个字节
//m_check_idx指向从状态机当前正在分析的字节
http_conn::LIEN_STATUS http_conn::parse_line()
{
	char temp;
	for(;m_checked_idx<m_read_idx;++m_checked_idx)
	{
		//temp为将要分析的字节
		temp=m_read_buf[m_checked_idx];

		//如果当前是\r字符,则有可能会读取到完整行
		if(temp=='r'){
			//下一字符达到了buffer结尾,则接收不完整,需要继续接收
			if((m_checked_idx+1)==m_read_idx)
				return LINE_OPEN;
			//下一个字符是\n,将\r\n改成\0\0
			else if(m_read_buf[m_checked_idx+1]=='\n'){
				m_read_buf[m_checked_idx++]='\0';
				m_read_buf[m_checked_idx++]='\0';
				return LINE_OK;
			}
			//如果都不符合,则返回语法错误
			return LINE_BAD;
		}

		//如果当前字符是\n,也有可能读取到完整行
		//一般是上次读取到\r就到buffer末尾了,没有接收完整,再次接收时会出现这种情况
		else if(temp=='\n')
		{
			//前一个字符是\r,则接收完整
			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_OPNE;
}

主状态机逻辑

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

  • 主状态机的初始状态,调用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任一字符的位置并返回
	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;
	}else{
	 	return 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.1
	if(strcasecmp(m_version,"HTTP/1.1")!=0)
		return BAD_REQUEST;

	//对请求资源前7个字符进行判断
	//这里主要是有些报文的请求资源中会带有http://,这里需要对这种情况进行单独处理
	if(strcasecmp(m_url,"http://",7)==0)
	{
		m_url+=7;
		m_url=strchr(m_url,'/');
	}
	
	//同样增加https的情况
	if(strcasecmp(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字符,若是,则表示当前处理的是空行,若不是,则表示当前处理的是请求头。
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(strcasecmp(text,"Connection:",11)==0)
	{
		text+=11;

		//跳过空格和\t字符
		text+=strspn(text," \t");
		if(strcasecmp(text,"keep-alive")==0)
		{
			//如果是长连接,则将linger标志设置为true
			m_linger=true;
		}
	}
	//解析请求头部内容长度字段
	else if(strcasecmp(text,"Content-length:",15)==0)
	{
		text+=15;
		text+=strspn(text," \t");
		m_content_length=atol(text);
	}
	//解析请求头部HOST字段
	else if(strcasecmp(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,可以跳出循环,完成报文解析任务。

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://www.coloradmin.cn/o/814844.html

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

相关文章

Python工具箱系列(三十九)

使用zlib对数据进行压缩 现实世界中&#xff0c;大量存在着对数据压缩的需求。为此&#xff0c;python内置了zlib压缩库&#xff0c;可以方便的对任意对象进行压缩。 下述代码演示了对字符串进行压缩&#xff1a; import zlib# 压缩一段中文 originstr 神龟虽寿&#xff0c…

风靡朋友圈的妙鸭相机,到底用了哪些底层技术?

不知道大家近期的朋友圈有没有被和海马体、天真蓝如出一辙的AI写真刷屏&#xff01; 这些面若桃花、精致到头发丝、光影充满氛围感的写真都是一款叫“妙鸭相机”的小程序生成的&#xff01;只要9.9&#xff0c;就能体验999写真&#xff01; 虽然只要9.9&#xff0c;但生成的照片…

Mac电脑目录

System&#xff08;系统&#xff09;Applications&#xff08;应用程序&#xff09;应用程序目录&#xff0c;默认所有的GUI应用程序都安装在这里User&#xff08;用户&#xff09;存放用户的个人资料和配置。每个用户有自己的单独目录Library&#xff08;资料库&#xff09;系…

定义dubbo自己的异常过滤器

起因 发现这个问题的起因是前端联调接口的时候发现统一的异常处理没有发挥作用,我们定义的处理的异常类型为AppException(国际惯例继承于RuntimeException),但是Dubbo服务端实际返回的异常变成了RuntimeException,我们自定义的异常处理没有发生作用&#xff0c;导致前端报500异…

恒运资本:A股、港股全线爆发,沪指突破3300点,恒指重返2万点上方

7月31日&#xff0c;两市股指高开高走&#xff0c;沪指在金融、地产、酿酒等权重板块的带动下一举突破3300点。截至发稿&#xff0c;沪指、深成指、创业板指涨幅均超1%&#xff0c;上证50指数涨近2%。Wind数据显现&#xff0c;北向资金净买入超25亿元。 职业方面&#xff0c;券…

清风徐来【个人】

清风徐来【个人】 前言版权清风徐来【个人】我的博客我的专栏我的粉丝我获得的奖品我的其他平台我的投稿 最后 前言 2023-7-29 10:57:54 花若向阳花自开 人若向暖清风徐来 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https…

笔记本数据恢复,这5个方法记好了!

我的笔记本从大学就开始用了&#xff0c;里面有很多重要的资料和文件。但昨天打开时&#xff0c;它突然卡着了&#xff0c;等到恢复过来之后&#xff0c;我发现我有些数据就是莫名其妙就消失了。有什么方法能帮我恢复笔记本的数据吗&#xff1f;” 随着笔记本电脑在我们生活中扮…

【LeetCode】不同路劲(动态规划)

不同路劲 题目描述算法流程编程代码 链接: 不同路劲 题目描述 算法流程 编程代码 class Solution { public:int uniquePaths(int m, int n) {vector<vector<int>> dp(m 1,vector<int>(n 1));dp[1][0] 1;for(int i 1;i < m;i){for(int j 1;j < n…

【MySQL】存储过程(十一)

🚗MySQL学习第十一站~ 🚩本文已收录至专栏:MySQL通关路 ❤️文末附全文思维导图,感谢各位点赞收藏支持~ 一.引入 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程可以简化应用开发人员的工作,可以减少数据在数据库和应用服务器之间的传输,…

基于SSM 球鞋资讯交流平台-计算机毕设 附源码11819

SSM 球鞋资讯交流平台 摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;球鞋资讯交流平台当然也不能排除在外。球鞋资讯交流平台是以实际运用为开发背景&#xff0c;运用…

Cesium态势标绘专题-钳击箭头(标绘+编辑)

标绘专题介绍:态势标绘专题介绍_总要学点什么的博客-CSDN博客 入口文件:Cesium态势标绘专题-入口_总要学点什么的博客-CSDN博客 辅助文件:Cesium态势标绘专题-辅助文件_总要学点什么的博客-CSDN博客 本专题没有废话,只有代码,代码中涉及到的引入文件方法,从上面三个链…

HDFS高阶优化方案:短路本地读取,节点负载平衡器

HDFS高阶优化方案 短路本地读取&#xff1a;short circuit local reads背景实现老版本的设计实现安全性改进版设计实现Unix domain socket 配置配置一----libhadoop.so配置二---hdfs-site.xml 节点block负载平衡器&#xff1a;balancer背景命令行配置运行balancer 短路本地读取…

进程创建大盘点

进程创建回顾 通过 fork() 创建子进程&#xff0c;然后通过 execve(...) 将子进程的进程空间替代为 path 所指定程序的进程空间&#xff0c;随后执行 path 所指定的程序 问题 进程创建是否只能依赖于 fork() 和 execve(...) ? 再轮进程创建 fork() 通过完整复制当前进程的方…

金蝶云星空和旺店通·旗舰奇门单据接口对接

金蝶云星空和旺店通旗舰奇门单据接口对接 对接系统&#xff1a;旺店通旗舰奇门 旺店通是北京掌上先机网络科技有限公司旗下品牌&#xff0c;国内的零售云服务提供商&#xff0c;基于云计算SaaS服务模式&#xff0c;以体系化解决方案&#xff0c;助力零售企业数字化智能化管理升…

参数量仅有50KB的超轻量级unet变种网络egeunet【参数和计算量降低494和160倍】医疗图像分割实践

今天看到一篇挺有意思的文章&#xff0c;做的是跟医疗图像分割相关的工作&#xff0c;但是不像之前看到的一些工作一味地去追求高精度&#xff0c;因为医疗领域本身就是一个相对特殊的行业&#xff0c;对于模型产生的结果的精确性要求是很高的&#xff0c;带来的是参数量级的庞…

Pytorch深度学习-----神经网络之卷积层用法详解

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

交通运输安全大数据分析解决方案

当前运输市场竞争激烈&#xff0c;道路运输企业受传统经营观念影响&#xff0c;企业管理者安全意识淡薄&#xff0c;从业人员规范化、流程化的管理水平较低&#xff0c;导致制度规范在落实过程中未能有效监督与管理&#xff0c;执行过程中出现较严重的偏差&#xff0c;其营运车…

【C++入门到精通】C++入门 —— 类和对象(初始化列表、Static成员、友元、内部类、匿名对象)

目录 一、初始化列表 ⭕初始化列表概念 ⭕初始化列表的优点 ⭕使用场景 ⭕explicit关键字 二、Static成员 ⭕Static成员概念 &#x1f534;静态数据成员&#xff1a; &#x1f534;静态函数成员&#xff1a; ⭕使用静态成员的优点 ⭕使用静态成员的注意事项 三、友…

国际化警告Fall back to translate ‘creator‘ key with ‘zn‘ locale.

发现是自己粗心写错了一个单词 这个需要改成zh

OC对象内存布局与isa指针

文章目录 一、Objective-C的本质二、一个objc对象如何进行内存布局&#xff1f;考虑父类的情况三、一个objc对象的isa指针指向什么&#xff1f;有什么作用四、objc对象的类方法和实例方法有什么本质区别和联系&#xff1f; 一、Objective-C的本质 Objc的底层实现是C\C代码&…