HTTP协议详解 - 通过C++实现HTTP服务剖析HTTP协议

news2024/9/27 17:27:31

前言

  • C/C++程序员一般很少会接触到HTTP服务端的东西,所以对HTTP的理解一般停留在理论。 本文章实现通过C++实现了一个http服务,可以通过代码对HTTP协议有更深的理解,并且通过抓包工具对HTTP协议进行更为详细的分析。

HTTP协议简介

  • HTTP(hypertext transport protocol 超文本传输协议):一种无状态的,以请求/应答方式运行的协议,它使用可扩展的语义和自描述消息格式,与基于网络的超文本信息系统灵活的互动。

HTTP报文格式

  • 请求报文:由请求行,头部字段集合,消息正文三大部分组成。
    请添加图片描述

    • 请求行:描述请求的基本信息

      • 请求方法
        • 请求方法说明
          GET请求服务器发送某个资源
          POST用来传输实体的主体
          PUT用来传输文件
          HEAD获取报文首部,用于确认URI的有效性及资源更新的日期时间等
          DELETE删除文件
          OPTIONS查询针对请求URI指定的资源支持的方法
          TRACE用于追踪路径
          CONNECT要求与代理服务器通信时建立隧道,实现用隧道协议进行TCP通信
      • URI:统一资源标识符(Uniform Resource Identifier)
      • HTTP版本
        • HTTP版本说明
          HTTP/0.91991年制定,只支持GET方法
          HTTP/1.01996年诞生,增加了POST,HEAD方法
          HTTP/1.11999年发布并成为标准,增加了PUT方法,并允许持久连接
    • 头部字段集合:使用key-value形式更详细地说明报文。主要分为四类:通用首部,请求首部,响应首部,实体首部

      • 通用首部:提供与报文相关的基本信息。既可以出现在请求报文中,也可以出现在响应报文中。
        • 首部字段名说明
          CacheControl控制缓存的行为
          Connection允许客户端和服务端指定与请求/响应连接有关的选项
          Date创建报文的日期时间
          Pragma另一种随报文传送指示的方式,但并不专用于缓存
          Transfer-Encoding告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式
          Trailer报文末端的首部一览
          Update给出了发送端可能想要"升级"使用的新版本或协议
          Via显示了报文经过的中间节点(代理,网关)
      • 请求首部:只在请求报文中有意义的首部。用于说明是谁或什么在发送请求,请求源自何处,或者客户端的喜好和及能力
        • 首部字段名说明
          Accept告诉服务器能够发送哪些媒体类型
          Accept-Charset告诉服务器能够发送哪些字符集
          Accept-Encoding告诉服务器能够发送哪些编码方式
          Accept-Language告诉服务器能够发送哪些语言
          Authorization包含了客户端提供给服务器,以便对其自身进行认证的数据
          From提供了客户端用户的E-mail地址
          Host给出了接收请求的服务器的主机名和端口号
          If-Match如果实体标记与文档当前的实体标记相匹配,就获取这份文档
          If-Modified-Since除非在某个指定的日期之后资源被修改过,否则就限制这个请求
          If-None-Match如果提供的实体标记与当前文档的实体标记不相符,就获取文档
          If-Range允许对文档的某个范围进行条件请求
          If-Unmodified-Since除非在某个指定日期之后资源没有被修改过,否则就限制这个请求
          Max-Forward将请求转发给其他代理或网关的最大次数
          Proxy-Authorization与Authorization首部相同,但这个首部实在与代理进行认证时使用
          Range如果服务器支持范围请求,就请求资源的指定范围
          Referer提供包含当前请求URI的文档的URL
          TE告诉服务器可以使用哪些扩展传输编码
          User-Agent将发起请求的应用程序名称告知服务器
      • 实体首部:提供有关实体及其内容得到大量信息
        • 首部字段名说明
          Allow资源可支持的HTTP方法
          Content-Encoding实体主体适用的编码方式
          Content-Language实体主体的自然语言
          Content-Length实体主体的大小(单位:字节)
          Content-Location替代对应资源的URI
          Content-MD5实体主体的报文摘要
          Content-Range实体主体的位置范围
          Content-Type实体主体的媒体类型
          Expires实体主体过期的日期时间
          Last-Modified资源的最后修改日期
    • 消息正文:实际传输的数据,可以是纯文本,也可以是图片、视频等二进制数据

  • 响应报文:由响应行,头部字段集合,消息正文三大部分组成。
    请添加图片描述

    • 响应行:描述响应的基本信息
      • HTTP版本:上面已经介绍过了
      • 状态码:状态码的职责是当客户端向服务端发送请求时,描述返回的请求结果
        • 状态码类别原因短语
          1XX信息性状态码接收的请求正在处理
          2XX成功状态码请求正常处理完毕
          3XX重定向状态码需要进行附加操作以完成请求
          4XX客户端错误状态码服务端无法处理请求
          5XX服务端错误状态码服务器处理请求出错
        • 常用的错误码主要有14种
        • 错误码错误码描述详细描述
          200OK表示从客户端发来的请求在服务端被正常处理了
          204No Content无内容。服务器成功处理,但未返回内容
          206Partial Content部分内容。服务器成功处理了部分GET请求
          301Moved Permanently永久重定向,意思是本地请求的资源以及不存在,使用新的URI再次访问
          302Found临时重定向,临时则所请求的资源暂时还在,但是目前需要用另一个URI访问
          303See Other与301类似,使用GET和POST请求查看
          304Not Modified运用于缓存控制。它用于 If-Modified-Since 等条件请求,表示资源未修改,可以理解成"重定向已到缓存的文件"
          307Temporary Redirect临时重定向,与302类似,使用GET请求重定向
          400Bad Request客户端请求的语法错误,服务器无法理解
          401Unauthorized表示发送的请求需要有通过HTTP认证的认证信息
          403Forbidden这一个是表示服务器禁止访问资源。原因比如涉及到敏感词汇、法律禁止等
          404Not Found服务器无法根据客户端的请求找到资源
          500Internal Server Error服务器内部错误,无法完成请求
          503Service Unavailable表示服务器当前很忙,暂时无法响应服务,我们上网时有时候遇到的"网络服务正忙,请稍后重试"的提示信息就是状态码 503
      • 状态码描述:作为状态码补充,是更详细的解释文字,帮助理解原因
    • 头部字段合集:上面已经介绍过,这里只介绍下响应首部字段
      • 响应首部
        • 首部字段名说明
          Accept-Ranges对此资源来说,服务器可接受的范围类型
          Age响应持续时间
          ETag资源的匹配信息
          Location令客户端重定向至指定URI
          Proxy-Authenticate代码服务器对客户端的认证信息
          Retry-After如果资源不可用,在此日期或时间重试
          Server服务器应用程序软件的名称和版本
          Vary代理服务器缓存的管理信息
          WWW-Authenticate服务器对客户端的认证信息
    • 消息正文

C++实现http服务

  • 我参考TinyHttpd项目,使用C++实现了一个http服务,功能比较简单,目前只支持GET和POST请求。并且也只是对http请求报文进行了解析,然后进行简单回应,未实现其他功能。使用第三方json解析库json11对json报文体进行解析处理。
  • 项目源代码可以从这里下载:项目地址
  • 主要代码
    •   #include "httpd.h"
        
        void threadFunc(void* arg, int conn){
        	Httpd* httpd = (Httpd*)arg;
      
        	// 接收http请求
        	char bodyBuf[1024] = {0};
        	int recvSize = recv(conn, bodyBuf, sizeof(bodyBuf), 0);
        	printf("%s\n", bodyBuf);
      
        	std::string strMethod;
        	std::string strUri;
        	std::string strVersion;
        	std::map<std::string, std::string> requestHead;
        	std::string requestBody;
        	// 解析http请求,包括请求方式(目前只支持GET和POST请求),URI,http版本
        	httpd->parseHttpRequestInfo(bodyBuf, strMethod, strUri, strVersion);
        	// 解析http请求头
        	httpd->parseHttpRequestHead(bodyBuf, requestHead);
        	//解析http请求体
        	httpd->parseHttpRequestBody(bodyBuf, requestBody);
      
        	//根据不同请求方式进行响应
        	if(strMethod.compare("GET") == 0){
        		httpd->httpResponseHtml(conn);
        	 }else if(strMethod.compare("POST") == 0){
        		std::string data1;
        		std::string data2;
        		if(httpd->parseBodyJson(requestBody, data1, data2)){
            		httpd->httpResponseJson(conn, data1, data2);
        		}
        	}
        	//关闭套接字
        	close(conn);
        }
      
        bool Httpd::start(){
        	//定义sockfd
        	int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
      
        	///定义sockaddr_in
        	struct sockaddr_in server_sockaddr;
        	server_sockaddr.sin_family = AF_INET;
        	server_sockaddr.sin_port = htons(4000);
        	server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
      
        	//bind,成功返回0,出错返回-1
        	if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        		perror("bind");
        		return false;
        	}
      
        	//listen,成功返回0,出错返回-1
        	if(listen(server_sockfd, 5) == -1){
        		perror("listen");
        		return false;
        	}
      
        	//客户端套接字
        	char buffer[1024] = {0};
        	struct sockaddr_in client_addr;
        	socklen_t length = sizeof(client_addr);
        	int conn = 0;
        	while(1){
        		//成功返回非负描述字,出错返回-1
        		conn = accept(server_sockfd, (struct sockaddr*)&client_addr, &length);
        		if(conn < 0){
            		perror("connect");
            		return false;
        		}
      
        		//开启线程处理请求
        		std::thread th;
        		th = std::thread(threadFunc, this, conn);
        		th.join();
        	}
        	close(server_sockfd);
      
        	return true;
        }
      
        bool Httpd::parseHttpRequestInfo(std::string httpRequest, std::string& method, std::string& uri, std::string& version){
        	int recvSize = httpRequest.size();
      
        	//查找请求头
        	std::string strRequestHead;
            int pos = httpRequest.find("\r\n");
        	strRequestHead = httpRequest.substr(0, pos);
      
        	//解析请求类型
        	method = strRequestHead.substr(0, strRequestHead.find(" "));
        	//解析uri
        	uri = strRequestHead.substr(strRequestHead.find(" ") + 1, strRequestHead.find(" ", strRequestHead.find(" ") + 1) - strRequestHead.find(" "));
        	//解析http版本
        	version = strRequestHead.substr(strRequestHead.rfind(" "), strRequestHead.size() - strRequestHead.rfind(" "));
        	return true;
        }
      
      
        bool Httpd::parseHttpRequestHead(std::string httpRequest, std::map<std::string, std::string>& requestHead){
        	int recvSize = httpRequest.size();
        	int headPos = httpRequest.find("\r\n");
      
        	int bodySize = parseBodySize(httpRequest);
      
        	std::string strRequestH;
        	do{
        		int iPos = httpRequest.find("\r\n", headPos + strlen("\r\n"));
        		strRequestH = httpRequest.substr(headPos, iPos - headPos);
        		if(strRequestH.find(":") != std::string::npos){
            		std::string strKey = strRequestH.substr(0, strRequestH.find(":"));
            		std::string strValue = strRequestH.substr(strRequestH.find(":") + 1, strRequestH.size() - strRequestH.find(":"));
            		requestHead.insert(std::pair<std::string, std::string>(strKey, strValue));
        		}
        		headPos = iPos;
        	} while(headPos < recvSize - bodySize && headPos > 0);
      
        	return true;
        }
      
        bool Httpd::parseHttpRequestBody(std::string httpRequest, std::string& requestBody){
        	int recvSize = httpRequest.size();
      
        	int bodySize = parseBodySize(httpRequest);
        	if(bodySize == 0){
        		return false;
        	}
      
        	requestBody = httpRequest.substr(recvSize - bodySize, bodySize);
        	return true;
        }
      
        int Httpd::parseBodySize(std::string httpRequest){
        	std::string strContentLength;
        	int posLengthStart = httpRequest.find("Content-Length: ") + strlen("Content-Length: ");
        	int posLengthEnd = httpRequest.find("\r\n", httpRequest.find("Content-Length: ") + strlen("Content-Length: "));
        	strContentLength =  httpRequest.substr(posLengthStart, posLengthEnd - posLengthStart);
        	return atoi(strContentLength.c_str());
        }
      
  • 通过代码我们可以看到,其实底层还是TCP编程,只不过TCP通信时,我们是直接拿数据,不用遵守什么规则。但如果要进行HTTP通信,就要遵守人家的规则,按照请求报文的格式去进行解析,才能拿到服务端想要的信息,然后再根据响应报文去组装数据,返回给客户端。

演示

  • POST请求演示,我通过postman演示下post请求,目前实现的功能是将请求数据拼接后返回。
    • postman界面演示
      在这里插入图片描述
    • 服务端打印
      请添加图片描述
  • GET请求演示,直接在浏览器中访问,返回一个html格式的页面
    • 浏览器页面
      在这里插入图片描述
    • 服务端打印
      在这里插入图片描述

抓包分析

  • 下面我们通过wireShark工具抓包分析下http协议的通信过程,发送一个post请求。
    在这里插入图片描述
  • 通过抓包可以看到,在http通信前,先要通过TCP三次握手建立连接,并且一次请求结束后,进行TCP四次挥手断开连接(http协议目前是支持长连接的,也就是建立连接后,可以发送多个http请求,我这里为了分析方便,在发送一次http请求后就关闭了套接字)。
  • 先看下前三行,是TCP建立连接的过程,我在使用wireShark抓包分析TCP协议进行了详细介绍,这里就不再过多阐述了。
  • 第四行开始是http通信,可以看到http请求的所有信息
    在这里插入图片描述
  • 再看第七行,是http服务的响应
    在这里插入图片描述
  • 后面是TCP四次挥手过程,这里也不过多阐述了。

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

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

相关文章

Linux--多线程

目录1. 什么是线程2. 创建线程3. 线程等待3.1 pthread_join函数3.2 线程分离3.2 线程终止的方案4. 线程ID1. 什么是线程 Linux中没有专门为线程设计TCB&#xff0c;而是用进程的PCB来模拟进程。 这也是为什么有种观点会说Linux下没有真正意义上的线程。 对于线程来说&#xf…

Elasticsearch搜索引擎(二)——SpringData Elasticsearch

SpringData Elasticsearch SpringData介绍 Spring Data是一个用于简化数据库访问&#xff0c;并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷&#xff0c;并支持map-reduce框架和云计算数据服务。 Spring Data可以极大的简化JPA的写法&#xff0c;可以在…

CSND近期推出的猿如意到底怎么样?

CSND近期推出的猿如意到底怎么样&#xff1f; 投稿测评正文 猿如意传送门 猿如意下载地址&#xff1a;猿如意-程序员的如意兵器,工具代码,一搜就有 猿如意使用了几次了&#xff0c;今天来想分享一下我对于猿如意的使用感受吧&#xff01;&#xff01; 先说结论&#xff1a…

吴恩达《机器学习》——Logistics回归代码实现

Logisitc回归1. Sigmoid与二分类Sigmoid函数为什么Sigmoid函数可以表示二分类概率&#xff1f;2. Logistics回归交叉熵损失函数梯度过拟合与欠拟合正则化3. Python代码实现4. 单维与多维Logistic分类单维数据分类多维数据分类数据集、源文件可以在Github项目中获得 链接: https…

04 kafka 中一些常用的配置的使用

前言 呵呵 也是最近有一些 搭建 kafka 的环境的需求 然后 从新看了一下 一部分的配置情况, 这里 大致理一下 一些我这里比较关心的配置 那些配置关联了 kafka 服务器绑定服务 绑定 tcp 服务的配置来自于这里, 读取的是 config.dataPlaneListeners config.dataPlaneListen…

Java母婴商城母婴店孕妇商城婴幼儿商城网站系统源码

简介 java使用ssm开发的母婴商城系统&#xff0c;用户可以注册浏览商品&#xff0c;加入购物车或者直接下单购买&#xff0c;在个人中心管理收货地址和订单&#xff0c;管理员也就是商家登录后台可以发布商品&#xff0c;上下架商品&#xff0c;处理待发货订单等。 演示视频&…

Allegro如何在PCB上查看pin number的三种方法操作指导

Allegro如何在PCB上查看pin number的三种方法操作指导 Allegro支持快捷的在PCB上查看pin number,如下图 具体操作如下 方法一:show element 选择Show Element命令Find选择Pins

2022年学习机器人和人工智能的一些期待

2022年学习机器人和人工智能的一些体会 2023年即将到来&#xff0c;满满的期待。 做好规划是非常非常重要的&#xff0c;有时候甚至比认真做事本身更为重要。 《礼记中庸》&#xff1a;“凡事豫则立&#xff0c;不豫则废。言前定则不跲&#xff0c;事前定则不困&#xff0c;行…

视频解码学习备忘

媒体文件知识 日常都是播放器直接播&#xff0c;其实这里面还有不少内容的。 首先是视频容器&#xff0c;就是所谓的.mp4 .mkv 这类文件,其目的主要就是用来存放音频视频字幕等内容&#xff0c;所以叫做容器。这些都有一定规范&#xff0c;比如mp4&#xff0c;叫ISO 14496-12…

流程图+BPMN+脑图 JointJS++ 3.6.3 Crack

一个库&#xff0c;‍ 无限的UI选择&#xff0c;直接在您的应用程序中享受交互式流程图、BPMN 和其他图表工作室。利用我们的模板化应用程序&#xff0c;将开发时间缩短至几天。 经过十多年和数百万行代码的编写&#xff0c;我们推出了一组随时可用的功能&#xff0c;以帮助您创…

数据结构进阶 二叉树OJ题二

作者&#xff1a;小萌新 专栏&#xff1a;数据结构进阶 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍几道二叉树的oj题 题目一 二叉搜索树与双向链表 输入一棵二叉搜索树&#xff0c;将该二叉搜索树转换成一个排序的双向链表…

帆软—图表专题

饼图 优点&#xff1a;能很直观的看到每一个部分在整体中所占的比例。 缺点&#xff1a;不适合较大的数据集&#xff08;分类&#xff09;展现&#xff0c;数据项中不能有负值&#xff0c;当比例接近时&#xff0c;人眼很难准确判别。 变形饼图 柱形图 特点 优点&#xff…

SpringBoot HandlerMethodArgumentResolver接口自定义参数解析器

参考资料 【SpringBoot】HandlerMethodArgumentResolver的简单使用HandlerMethodArgumentResolver(参数解析器)的作用使用小案例HandlerMethodArgumentResolver用于统一获取当前登录用户 目录一. 需求二. 前期准备三. 实现HandlerMethodArgumentResolver接口四. 将自定义的方法…

一套javassf门户网站管理系统源码分享

淘源码&#xff1a;国内专业的免费源码下载平台 JAVA SSF项目框架源码 后台管理系统源码 一套以SpringMVCSpringHibernateFreemarkerShiroLuceneHtml5jQuery为技术核心架构的门户网站管理系统源码。 源码免费分享&#xff0c;需要源码学习可私信我。 主要功能&#xff1a; 内…

笑对过往、活在当下、期盼未来

哈喽大家好&#xff0c;我是阿Q。 去年今日&#xff0c;也是安静的午后&#xff0c;拿起铅笔在纸上寥寥草草的写下几个年终关键词&#xff0c;思索良久&#xff0c;迟迟未能下笔。 时至今日&#xff0c;年末将至&#xff0c;再次执笔已时隔一年&#xff0c;再不拿笔记录这转瞬…

Python制作粒子烟花,程序员的跨年姿势

跨年倒计时还有半天&#xff1f;我已经开始整烟花了&#xff0c;虽然不是很好看吧&#xff0c;但是也能将就看看 &#x1f625; 这个的背景图&#xff0c;音乐&#xff0c;还有文字都是可以自己修改的哦 效果展示 依次导入本次需要使用到的模块 import random import pygame…

免费的JPEG 恢复软件 - 照片删除了怎么恢复?

照片删除了怎么恢复? 照片在我们的生活中起着至关重要的作用&#xff0c;因为它们可以承载我们的快乐回忆&#xff0c;记录我们的日常生活&#xff0c;分享我们的快乐等。我们将在智能手机、相机或计算机上拍摄并存储大量 JPEG 图片。但是&#xff0c;无论我们多么小心地保留它…

Python和MySQL对比(2):用Pandas 实现MySQL的 union 和 join 语法效果

文章目录一、前言二、语法对比数据表unionjoin1、两表 inner join2、两表 left join3、两表 right join4、两表 outer join5、多表 join三、小结一、前言 环境&#xff1a; windows11 64位 Python3.9 MySQL8 pandas1.4.2 本文主要介绍 MySQL 中的union和join如何使用pandas实现…

Spring 核心概念 IOC/DI IOC容器 Bean

目录 一&#xff1a;代码书写现状 二&#xff1a;核心思想 一&#xff1a;代码书写现状 常规操作如上,但存在着问题,将项目上线后&#xff0c;需要将数据层进行更换,更换如下&#xff1a; 数据层更换后&#xff0c;业务层也需要进行更换&#xff0c;更换如下&#xff1a; 数据…

NLP学习笔记(五) 注意力机制

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲注意力机制 (Attention Mechanism) 在序列到序列模型中的应用 在上一篇文章中&#xff0c;我们介绍了序列到序列模型&#xff0c;其工作流程可以概括为以下两个步骤 首先&#xff0c;用编码器将输入序列编码成上下文向量&a…