本文主要是介绍Sylar C++高性能服务器学习记录23 【Http模块-知识储备篇】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
早在19年5月就在某站上看到sylar的视频了,一直认为这是一个非常不错的视频。
由于本人一直是自学编程,基础不扎实,也没有任何人的督促,没能坚持下去。
每每想起倍感惋惜,遂提笔再续前缘。
为了能更好的看懂sylar,本套笔记会分两步走,每个系统都会分为两篇博客。
分别是【知识储备篇】和【代码分析篇】
(ps:纯粹做笔记的形式给自己记录下,欢迎大家评论,不足之处请多多赐教)
QQ交流群:957100923
Http模块-知识储备篇
一、Http的基础知识
如果你对Http协议比较陌生,那么请先看下 菜鸟教程关于Http 的介绍。
只要看完了菜鸟教程Http相关的介绍我们就可以开始本篇的讲解了,阅读本篇不需要深入了解Http协议。
深入的知识需要自行学习。
为了方便讲解,摘录部分内容到下面。
客户端请求消息
客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
请求行(Request Line):
- 方法:如 GET、POST、PUT、DELETE等,指定要执行的操作。 请求
URI(统一资源标识符):请求的资源路径,通常包括主机名、端口号(如果非默认)、路径和查询字符串。 HTTP 版本:如 HTTP/1.1
或 HTTP/2。 请求行的格式示例:GET /index.html HTTP/1.1
请求头(Request Headers):
- 包含了客户端环境信息、请求体的大小(如果有)、客户端支持的压缩类型等。
常见的请求头包括Host、User-Agent、Accept、Accept-Encoding、Content-Length等。
空行:
- 请求头和请求体之间的分隔符,表示请求头的结束。
请求体(可选):
- 在某些类型的HTTP请求(如 POST 和 PUT)中,请求体包含要发送给服务器的数据。
客户端请求:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding: gzip, deflate
Connection: keep-alive
服务器响应消息
HTTP 响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。
状态行(Status Line):
- HTTP 版本:与请求消息中的版本相匹配。 状态码:三位数,表示请求的处理结果,如 200 表示成功,404 表示未找到资源。
状态信息:状态码的简短描述。 状态行的格式示例:HTTP/1.1 200 OK
响应头(Response Headers):
- 包含了服务器环境信息、响应体的大小、服务器支持的压缩类型等。
常见的响应头包括Content-Type、Content-Length、Server、Set-Cookie等。
空行:
- 响应头和响应体之间的分隔符,表示响应头的结束。
响应体(可选):
- 包含服务器返回的数据,如请求的网页内容、图片、JSON数据等。
服务端响应:
HTTP/1.1 200 OK
Date: Wed, 18 Apr 2024 12:00:00 GMT
Server: Apache/2.4.1 (Unix)
Last-Modified: Wed, 18 Apr 2024 11:00:00 GMT
Content-Length: 12345
Content-Type: text/html; charset=UTF-8<!DOCTYPE html>
<html>
<head><title>Example Page</title>
</head>
<body><h1>Hello, World!</h1><!-- The rest of the HTML content -->
</body>
</html>
总结下来就是Http协议消息体分两大块,分别是【请求】和【响应】。
请求:请求行 请求头 空行 请求体
响应:状态行 响应头 空行 响应体
了解这些后我们继续…
二、明确目标
为了方便的处理请求和响应,我们需要对Http协议进行封装。
千万不要看到 协议 就觉得很高大上,说白了就是一定格式的文本而已。
那么:协议报文的封装 = 对一段固定格式的文本的封装。
我们要封装成什么样呢?
1.得到一段固定格式的文本后,能解析成C++的对象,方便读取文本中的内容和修改该文本中的内容。
2.根据C++的封装对象,能快速的构建出一段对应格式的文本。
ok 这就是我们要封装的目的,和需要实现的目标。
三、梳理封装内容
由于消息体分 请求 和 响应,所以我们就需要单独封装这两个。
这就出现了 HttpRequest(请求封装对象) 和 HttpResponse(响应封装对象)。
由于这两个消息体都有各自的 【行 头 体】,所以 HttpRequest 和 HttpResponse必然有对应的字段来存储 【行 头 体】 信息。
也应该有对应的方法对各自的 【行 头 体】 进行 【增 删 改 查】。
当然,对应的 【行 头 体】 也有可能需要再次封装,我们分别来梳理一下。
HttpRequest 应该有的字段:
请求行:(可封装)
请求行 = 请求方法 + URI + 协议版本
URI = 协议protocol + 主机名host(主机域名hostname+端口号port)+ 文件路径 + 参数 + 锚
- 请求方法:(可封装)
- 协议protocol:(字符串)
- 主机名host:(字符串)
- 文件路径:(字符串)
- 参数:(字符串,可封装成map)
- 锚:(字符串)
请求头:(可封装成map,其中cookie可再次封装成map)
请求体:(字符串)
HttpRequest 应该有的方法:
- 请求方法的get/set方法
- 版本信息的get/set方法
- 请求路径的get/set方法
- 查询参数的get/set方法
- 请求参数的增删改查方法
- 请求头的增删改查方法
- cookie的增删改查方法
- body的get/set方法
- body的追加方法
- 对象转换为字符串的方法
…
HttpResponse 应该有的字段:
响应行:
响应行 = 协议 + 版本 + 状态号 + 状态描述
- 状态:(可封装)
- 版本:(字符串)
响应头:(可封装为map)
响应体:(字符串)
HttpResponse 应该有的方法:
- 响应状态的get/set方法
- 响应版本的get/set方法
- 响应头的增删改查方法
- 响应体的增删改查方法
- 响应体的追加方法
- 对象转为字符串的方法
…
如此,我们就可以开始编写对应的代码了。
四、http_parser的使用
参考博客
源码地址 http-parser
初始化
/*
* @brief 用于初始化 http_parser 结构体,为后续的 HTTP 消息解析做好准备
* @param [IN] parser 指向一个待初始化的 http_parser 结构体实例
* @param [IN] type 枚举值,指定 parser 应该解析 HTTP 请求还是响应
* HTTP_REQUEST - 解析请求
* HTTP_RESPONSE - 解析响应
* HTTP_BOTH - 两者都可以
*/
void http_parser_init(http_parser *parser, enum http_parser_type type);
设置回调函数
/*
* @brief 设置默认的回调函数指针,这些回调函数会在解析HTTP消息的不同阶段被调用。
* 通知用户程序有关HTTP请求或响应的详细信息。
* @param [IN] settings 指向一个 http_parser_settings 结构体的指针
* 这个结构体包含了指向各种回调函数的指针,具体如下
* on_message_begin - 开始解析时回调
* on_url - 解析URL时回调
* on_status - 解析响应状态描述信息时回调
* on_header_field - 解析请求头key值时回调
* on_header_value - 解析请求头value时回调
* on_headers_complete - 开始解析请求头时回调
* on_body - 解析请求或响应体时回调
* on_message_complete - 解析完成时回调
*
*/
void http_parser_settings_init(http_parser_settings *settings);
解析数据
/*
* @brief 解析HTTP请求或响应的消息数据
* @param [IN] parser 指向一个已初始化的 http_parser 结构体实例
* 该结构体包含了 HTTP 消息的解析状态和其他相关信息,主要有以下参数
* status_code - http响应状态码
* method - http请求方式。HTTP_GET,HTTP_POST
* @param [IN] settings 指向一个 http_parser_settings 结构体,其中定义了一系列回调函数。
* 当解析到 HTTP 消息的不同部分(比如请求行、头部、主体等)时,对应的回调函数会被调用。
* @param [IN] data 待解析的HTTP数据
* @param [IN] len 待解析的HTTP数据长度
* @return 解析过程中成功处理了多少字节的数据,
* 如果小于len,可能是因为遇到了解析错误,或者是缓冲区中的数据不足以构成一个完整的 HTTP 消息片段。
*/
size_t http_parser_execute(http_parser *parser, const http_parser_settings *settings, const char *data, size_t len);
示例代码
#include <stdio.h>
#include "http_parser.h"
#include <string>
#include <map>// 用于解析的全局变量
std::map<std::string, std::string> mapReqHeadField;
std::string strReqUrl;
std::string strReqBody;
std::string strReqFieldKey;std::map<std::string, std::string> mapRespHeadField;
std::string strRespStatus;
std::string strRespBody;
std::string strRespFieldKey;// 用于解析http请求的回调函数
int onReqMessageBegin(http_parser* pParser);
int onReqHeaderComplete(http_parser* pParser);
int onReqMessageComplete(http_parser* pParser);
int onReqURL(http_parser* pParser, const char *at, size_t length);
int onReqHeaderField(http_parser* pParser, const char *at, size_t length);
int onReqHeaderValue(http_parser* pParser, const char *at, size_t length);
int onReqBody(http_parser* pParser, const char *at, size_t length);// 用于解析http响应的回调函数
int onRespMessageBegin(http_parser* pParser);
int onRespHeaderComplete(http_parser* pParser);
int onRespMessageComplete(http_parser* pParser);
int onRespStatus(http_parser* pParser, const char *at, size_t length);
int onRespHeaderField(http_parser* pParser, const char *at, size_t length);
int onRespHeaderValue(http_parser* pParser, const char *at, size_t length);
int onRespBody(http_parser* pParser, const char *at, size_t length);int main(int argc, char* argv[])
{// 解析http请求// 待解析的请求报文std::string strHttpReq;strHttpReq += "POST /http-parser HTTP/1.1\r\n";strHttpReq += "Host: 127.0.0.1:10010\r\n";strHttpReq += "Accept: */*\r\n";strHttpReq += "Content-Type: application/json\r\n";strHttpReq += "Content-Length: 25\r\n";strHttpReq += "\r\n";strHttpReq += "{\"reqmsg\": \"Hello World\"}";http_parser httpReqParser;http_parser_settings httpReqSettings;// 初使化解析器http_parser_init(&httpReqParser, HTTP_REQUEST);// 设置回调函数http_parser_settings_init(&httpReqSettings);httpReqSettings.on_message_begin = onReqMessageBegin;httpReqSettings.on_headers_complete = onReqHeaderComplete;httpReqSettings.on_message_complete = onReqMessageComplete;httpReqSettings.on_url = onReqURL;httpReqSettings.on_header_field = onReqHeaderField;httpReqSettings.on_header_value = onReqHeaderValue;httpReqSettings.on_body = onReqBody;// 解析请求int reqSize = strHttpReq.size();int nParseSize = http_parser_execute(&httpReqParser, &httpReqSettings, strHttpReq.c_str(), reqSize);if(nParseSize < reqSize){printf("http_parser_execute http request failed.\n");return -1;}// 解析成功,打印解析结果if(httpReqParser.method == HTTP_GET){printf("method: Get\n");}else if(httpReqParser.method == HTTP_POST){printf("method: Post\n");}else if(httpReqParser.method == HTTP_HEAD){printf("method: Head\n");}else{printf("method: other\n");}printf("url: %s \n", strReqUrl.c_str());printf("req heads:\n");for (std::map<std::string, std::string>::iterator iter = mapReqHeadField.begin(); iter != mapReqHeadField.end(); ++iter){printf("\t %s : %s \n", iter->first.c_str(), iter->second.c_str());}printf("req body: %s \n", strReqBody.c_str());printf("==========================================\n");// 解析http响应// 待解析的响应报文std::string strHttpResponse;strHttpResponse += "HTTP/1.1 200 OK\r\n";strHttpResponse += "Server: nginx/1.18.0\r\n";strHttpResponse += "Accept: */*\r\n";strHttpResponse += "Connection: keep-alive\r\n";strHttpResponse += "\r\n";strHttpResponse += "{\"respmsg\": \"Welcome to http-parser\"}\r\n";http_parser httpRespParser;http_parser_settings httpRespSettings;// 初使化解析器http_parser_init(&httpRespParser, HTTP_RESPONSE);// 设置回调函数http_parser_settings_init(&httpRespSettings);httpRespSettings.on_message_begin = onRespMessageBegin;httpRespSettings.on_headers_complete = onRespHeaderComplete;httpRespSettings.on_message_complete = onRespMessageComplete;httpRespSettings.on_status = onRespStatus;httpRespSettings.on_header_field = onRespHeaderField;httpRespSettings.on_header_value = onRespHeaderValue;httpRespSettings.on_body = onRespBody;// 解析响应int responseSize = strHttpResponse.size();nParseSize = http_parser_execute(&httpRespParser, &httpRespSettings, strHttpResponse.c_str(), responseSize);if(nParseSize < responseSize){printf("http_parser_execute http response failed.\n");return -1;}// 解析成功,打印解析结果printf("code: %d\n", httpRespParser.status_code); printf("status: %s \n", strRespStatus.c_str());printf("resp heads:\n");for (std::map<std::string, std::string>::iterator iter = mapRespHeadField.begin(); iter != mapRespHeadField.end(); ++iter){printf("\t %s : %s \n", iter->first.c_str(), iter->second.c_str());}printf("resp body: %s \n", strRespBody.c_str());return 0;
}int onReqMessageBegin(http_parser* pParser)
{// 开始解析报文printf("onReqMessageBegin call \n");return 0;
}
int onReqHeaderComplete(http_parser* pParser)
{// 报文头解析完成// HTTP报文头是以两个 \r\n 结尾, 如果解析不到两个 \r\n, 说明http报文格式有问题或者报文不完整,这个回调不会被调用printf("onReqHeaderComplete call \n");return 0;
}
int onReqMessageComplete(http_parser* pParser)
{// 全部解析完成printf("onReqMessageComplete call \n");return 0;
}
int onReqURL(http_parser* pParser, const char *at, size_t length)
{// 解析URLstrReqUrl.assign(at, length);return 0;
}int onReqHeaderField(http_parser* pParser, const char *at, size_t length)
{// 解析请求头keystrReqFieldKey.assign(at, length);return 0;
}
int onReqHeaderValue(http_parser* pParser, const char *at, size_t length)
{// 解析请求头valuestd::string strValue(at, length);mapReqHeadField.insert(std::make_pair(strReqFieldKey, strValue));return 0;
}
int onReqBody(http_parser* pParser, const char *at, size_t length)
{// 解析请求或响应体strReqBody.append(at, length);return 0;
}// ==================int onRespMessageBegin(http_parser* pParser)
{// 开始解析报文printf("onRespMessageBegin call \n");return 0;
}
int onRespHeaderComplete(http_parser* pParser)
{// 报文头解析完成// HTTP报文头是以两个 \r\n 结尾, 如果解析不到两个 \r\n, 说明http报文格式有问题或者报文不完整,这个回调不会被调用printf("onRespHeaderComplete call \n");return 0;
}
int onRespMessageComplete(http_parser* pParser)
{// 全部解析完成printf("onRespMessageComplete call \n");return 0;
}int onRespStatus(http_parser* pParser, const char *at, size_t length)
{// 解析响应状态码strRespStatus.assign(at, length);return 0;
}
int onRespHeaderField(http_parser* pParser, const char *at, size_t length)
{// 解析响应头keystrRespFieldKey.assign(at, length);return 0;
}
int onRespHeaderValue(http_parser* pParser, const char *at, size_t length)
{// 解析响应头valuestd::string strValue(at, length);mapRespHeadField.insert(std::make_pair(strRespFieldKey, strValue));return 0;
}
int onRespBody(http_parser* pParser, const char *at, size_t length)
{// 解析请求或响应体strRespBody.append(at, length);return 0;
}
打印结果
五、总结
Http模块的基本封装其实就是对固定格式的文本内容进行封装,没有什么高深的知识。
只要把看似复杂的专业术语翻译成白话文,再看源码一定会很轻松。
Http模块基础知识篇,思来想去也就这些东西了。
【最后求关注、点赞、转发】
QQ交流群:957100923
这篇关于Sylar C++高性能服务器学习记录23 【Http模块-知识储备篇】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!