准备:认识 Ragel
概念:是一个状态机编译器,类似于Lex(词法分析器)。
功能:用于处理字符输入、语法解析。一般的简单的文本处理工作我们会使用正则表达式,或者使用Linux下自带的awk/sed这些工具。
当我们的代码核心任务是:解析文本且需要高效率的处理数据,例如搭建一个STMP引擎、HTTP引擎,Ragel就可以根据我们定义好的语法,生成一个状态机嵌入到我们的代码当中,且以我们的原生代码进行执行,效率自然会比以上的方法要高。。[
准备:复用开源项目HTTP解析的处理
复用github的一个开源Web服务器项目中对于HTTP解析的处理:
Mongrel2: The Language Agnostic Web Server
- 主要使用其中的文件:
http11_common.h、http11_parser.h、httpclient_parser.h、http11_parser.rl、httpclient_parsre.rl

** 相关配置定义
1. 配置项
功能:使用配置系统Config为HTTP请求/响应报文解析的一些参数做配置
http_parser.cpp: ```cpp //使用一个配置项 规定一个首部字段数据长度阈值默认4KB 来规避大数据发包攻击 static ConfigVar::ptr g_http_request_buffer_size = Config::LookUp(“http.request.buffer_size”, (uint64_t)(4 * 1024), “http request buffer size”);
//使用一个配置项 规定一个报文实体数据长度阈值默认64MB
static ConfigVar
//使用一个配置项 规定一个首部字段数据长度阈值默认4KB 来规避大数据发包攻击
static ConfigVar
//使用一个配置项 规定一个报文实体数据长度阈值默认64MB
static ConfigVar
static uint64_t s_http_request_buffer_size = 0; static uint64_t s_http_request_max_body_size = 0; static uint64_t s_http_response_buffer_size = 0; static uint64_t s_http_response_max_body_size = 0;
<a name="qCIPq"></a>## 2. 配置项初始化目的:需要这些参数配置先于程序进入main( )函数之前,就要进行配置操作。并且给这些配置项设置回调,一旦发生修改就自动适应新值```cpp//初始化结构放在匿名空间 防止污染namespace {struct _RequestSizeIniter{//初始化 并设置回调_RequestSizeIniter(){s_http_request_buffer_size = g_http_request_buffer_size->getValue();s_http_request_max_body_size = g_http_request_max_body_size->getValue();s_http_response_buffer_size = g_http_response_buffer_size->getValue();s_http_response_max_body_size = g_http_response_max_body_size->getValue();g_http_request_buffer_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){s_http_request_buffer_size = new_value;});g_http_response_max_body_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){s_http_response_max_body_size = new_value;});g_http_response_buffer_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){s_http_response_buffer_size = new_value;});g_http_request_max_body_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){s_http_request_max_body_size = new_value;});}};static _RequestSizeIniter _initer;}
1. HTTP请求解析类封装
真正在使用封装好的HTTP解析的接口的时候,解析数据不会一次性将HTTP报文数据接收完整,由于网络延迟、数据量大等原因,发送的时候会拆分为多个报文小段进行发送,解析的时候也是一小段一小段进行解析,解析完一次就要返回一个状态去判别下一步应该进行什么操作。
1.1 成员变量
class HttpRequestParser{........private://解析请求报文的结构体http_parser m_parser;//请求报文对象智能指针HttpRequest::ptr m_request;//1000: invalid method//1001: invalid version//1002: invalid fieldint m_error; //错误码};
1.2 接口
1.2.1 构造函数
功能:对解析请求报文的结构体http_parser进行初始化,需要对报文每一部分的解析指定对应的一个回调函数。
/*** @brief HTTP请求解析类构造函数*/HttpRequestParser();HttpRequestParser::HttpRequestParser():m_error(0){m_request.reset(new HttpRequest);// 调用初始化APIhttp_parser_init(&m_parser);m_parser.request_method = on_request_method;m_parser.request_uri = on_request_uri;m_parser.fragment = on_request_fragment;m_parser.request_path = on_request_path;m_parser.query_string = on_request_query_string;m_parser.http_version = on_request_http_version;m_parser.header_done = on_request_header_done;m_parser.http_field = on_request_http_field;m_parser.data = this; //this指针放入}
1.2.2 execute()(核心)
功能:执行解析动作,进行一次对HTTP请求报文的解析。这个借口比较特殊,使用了开源项目中的http_parser_execute(),该函数是一种状态机的调用形式:解析不同报文部分,会自动调用不同的回调函数去进行自动的解析。
/*** @brief 执行解析动作* @param[in] data 需要解析的具体数据* @param[in] len 解析数据的长度* @return size_t 1:解析成功 -1:解析有问题 >0已经处理的字节数*/size_t execute(char* data, size_t len);size_t HttpRequestParser::execute(char* data, size_t len){size_t ret = http_parser_execute(&m_parser, data, len, 0);//先将解析过的空间挪走 防止缓存不够 但是仍然有数据位解析完成的情况memmove(data, data + ret, (len - ret));//返回实际解析过的字节数return ret;}
1.2.3 isFinished()
功能:判断当前这次报文解析是否结束。复用开源项目的http_parser_finish()函数。
/*** @brief 报文解析是否结束* @return int*/int isFinished();int HttpRequestParser::isFinished(){return http_parser_finish(&m_parser);}
1.2.4 hasError()
功能:判断当前这次报文解析是否出错。复用开源项目的http_parser_has_error()函数。
/*** @brief 解析是否出错* @return int*/int hasError();int HttpRequestParser::hasError(){return m_error || http_parser_has_error(&m_parser);}
1.2.5 getData()
功能:获取HTTP请求报文
/*** @brief 获取HTTP请求报文* @return HttpRequest::ptr*/HttpRequest::ptr getData() const {return m_request;}
1.2.6 setError()
功能:设置解析时的错误码
/*** @brief 设置解析时的错误码* @param[in] v 具体错误码*/void setError(int v) {m_error = v;}
1.2.7 getContentLength()
功能:获取报文主体字段”content-length”中的长度,并将该值由string转换为uint64_t
/*** @brief 获取报文主体字段"content-length"中的长度 并转换为uint64_t* @return uint64_t*/uint64_t getContentLength();uint64_t HttpRequestParser::getContentLength(){uint64_t v = 0;return m_request->GetHeaderAs<uint64_t>("content-length", v);}
1.2.8 GetHttpRequestBufferSize()
功能:获取请求报文头部最大缓冲区大小
/*** @brief 获取请求报文头部最大缓冲区大小* @return uint64_t*/static uint64_t GetHttpRequestBufferSize();uint64_t HttpRequestParser::GetHttpRequestBufferSize(){return s_http_request_buffer_size;}
1.2.9 GetHttpMaxBodySize()
功能:获取请求报文主体最大大小
/*** @brief 获取请求报文主体最大大小* @return uint64_t*/static uint64_t GetHttpMaxBodySize();uint64_t HttpRequestParser::GetHttpMaxBodySize(){return s_http_request_max_body_size;}
1.3 解析所需回调函数
功能:根据HTTP请求报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数。
http11_common.h:
该头文件中给出了回调函数所需的函数签名,有两种类型。
/*** @brief 解析HTTP请求方法回调函数*/void on_request_method(void *data, const char *at, size_t length){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);HttpMethod method = CharsToHttpMethod(at);if(method == HttpMethod::INVALID_METHOD){KIT_LOG_WARN(g_logger) << "http request invaild method:"<< std::string(at, length);parser->setError(INVALID_METHOD);return;}parser->getData()->setMethod(method);}/*** @brief 解析URI回调函数 要自定义URI的解析故不使用*/void on_request_uri(void *data, const char *at, size_t length){}/*** @brief 解析分段标识符回调函数*/void on_request_fragment(void *data, const char *at, size_t length){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);parser->getData()->setFragment(std::string(at, length));}/*** @brief 解析资源路径回调函数*/void on_request_path(void *data, const char *at, size_t length){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);parser->getData()->setPath(std::string(at, length));}/*** @brief 解析查询参数回调函数*/void on_request_query_string(void *data, const char *at, size_t length){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);parser->getData()->setQuery(std::string(at, length));}/*** @brief 解析HTTP协议版本回调函数*/void on_request_http_version(void *data, const char *at, size_t length){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);uint8_t v = 0;if(strncmp(at, "HTTP/1.1", length) == 0){v = 0x11;}else if(strncmp(at, "HTTP/1.0", length) == 0){v = 0x10;}else{KIT_LOG_WARN(g_logger) << "http request version invaild:"<< std::string(at, length);parser->setError(INVALID_VERSION);return;}parser->getData()->setVersion(v);}void on_request_header_done(void *data, const char *at, size_t length){}/*** @brief 解析一系列首部字段的回调函数*/void on_request_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen){//拿到this指针HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);// KIT_LOG_DEBUG(g_logger) << "request parser:\n" << value;if(flen == 0){KIT_LOG_WARN(g_logger) << "http request field length=" << flen;/*不作为错误 处理 否则会返回nullptr*/// parser->setError(INVALID_FIELD);return;}parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen));}
2. HTTP响应解析类封装
2.1 成员变量
class HttpResponseParser{........private://解析响应报文的结构体httpclient_parser m_parser;//响应报文对象HttpResponse::ptr m_response;//错误码int m_error;};
2.2 接口
2.2.1 构造函数
功能:对解析请求报文的结构体httpclient_parser进行初始化,需要对报文每一部分的解析指定对应的一个回调函数
/*** @brief HTTP响应解析类构造函数*/HttpResponseParser();HttpResponseParser::HttpResponseParser():m_error(0){m_response.reset(new HttpResponse);httpclient_parser_init(&m_parser);m_parser.reason_phrase = on_response_reason_phrase;m_parser.status_code = on_response_status_code;m_parser.chunk_size = on_response_chunk_size;m_parser.http_version = on_response_http_version;m_parser.header_done = on_response_header_done;m_parser.last_chunk = on_response_last_chunk;m_parser.http_field = on_response_http_field;m_parser.data = this; //this指针放入}
2.2.2 execute()(核心)
功能:执行解析动作,进行一次对HTTP响应报文的解析。这个借口比较特殊,使用了开源项目中的httpclient_parser_execute(),该函数是一种状态机的调用形式:解析不同报文部分,会自动调用不同的回调函数去进行自动的解析。
注意:和HTTP请求报文解析不同的点在于,响应报文不一定一次就全部发送完毕。可能存在分块发送的情况,即:chunck形式。但是复用的开源项目里并不支持对报文的分段解析,需要我们特殊处理:如果为chunck发包形式的话,每一次解析就要重置解析结构体的状态,防止记录上一个包的状态。
/*** @brief 执行解析动作* @param[in] data 需要解析的具体数据* @param[in] len 解析数据的长度* @param[in] chunck 标志位 判断是否是chunck类型* @return size_t 1:解析成功 -1:解析有问题 >0已经处理的字节数*/size_t execute(char* data, size_t len, bool chunck);size_t HttpResponseParser::execute(char* data, size_t len, bool chunck){//如果为chunck包需要重新初始化一下解析包if(chunck)httpclient_parser_init(&m_parser);//每一次都是从头开始解析size_t ret = httpclient_parser_execute(&m_parser, data, len, 0);//先将解析过的空间挪走memmove((void *)data, data + ret, (len - ret));//返回实际解析过的字节数return ret;}
1.2.3 isFinished()
功能:判断当前这次报文解析是否结束。复用开源项目的httpclient_parser_finish()函数。
/*** @brief 报文解析是否结束* @return int*/int isFinished();int HttpResponseParser::isFinished(){return httpclient_parser_finish(&m_parser);}
2.2.4 hasError()
功能:判断当前这次报文解析是否出错。复用开源项目的httpclient_parser_has_error()函数。
/*** @brief 解析是否出错* @return int*/int hasError();int HttpResponseParser::hasError(){return m_error || httpclient_parser_has_error(&m_parser);}
1.2.5 getData()
功能:获取HTTP响应报文
/*** @brief 获取HTTP响应报文* @return HttpRequest::ptr*/HttpResponse::ptr getData() const {return m_response;}
1.2.6 setError()
功能:设置解析时的错误码
/*** @brief 设置解析时的错误码* @param[in] v 具体错误码*/void setError(int v) {m_error = v;}
1.2.7 getContentLength()
功能:获取报文主体字段”content-length”中的长度,并将该值由string转换为uint64_t
/*** @brief 获取报文主体字段"content-length"中的长度 并转换为uint64_t* @return uint64_t*/uint64_t getContentLength();uint64_t HttpResponseParser::getContentLength(){uint64_t v = 0;return m_response->GetHeaderAs<uint64_t>("content-length", v);}
1.2.8 GetHttpRequestBufferSize()
功能:获取请求报文头部最大缓冲区大小
/*** @brief 获取响应报文头部最大缓冲区大小* @return uint64_t*/static uint64_t GetHttpResponseBufferSize();uint64_t HttpResponseParser::GetHttpResponseBufferSize(){return s_http_response_buffer_size;}
1.2.9 GetHttpMaxBodySize()
功能:获取请求报文主体最大大小
/*** @brief 获取响应报文主体最大大小* @return uint64_t*/static uint64_t GetHttpMaxBodySize();uint64_t HttpResponseParser::GetHttpMaxBodySize(){return s_http_response_max_body_size;}
2.3 解析所需回调函数
功能:根据HTTP响应报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数。
/*** @brief 解析状态原因短语回调函数*/void on_response_reason_phrase(void *data, const char *at, size_t length){HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);parser->getData()->setReason(std::string(at, length));}/*** @brief 解析状态码回调函数*/void on_response_status_code(void *data, const char *at, size_t length){HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);HttpStatus status = (HttpStatus)(atoi(at));parser->getData()->setStatus(status);}void on_response_chunk_size(void *data, const char *at, size_t length){}/*** @brief 解析HTTP协议版本回调函数*/void on_response_http_version(void *data, const char *at, size_t length){HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);uint8_t v = 0;if(strncmp(at, "HTTP/1.1", length) == 0){v = 0x11;}else if(strncmp(at, "HTTP/1.0", length) == 0){v = 0x10;}else{KIT_LOG_WARN(g_logger) << "http response version invaild:"<< std::string(at, length);parser->setError(INVALID_VERSION);return;}parser->getData()->setVersion(v);}void on_response_header_done(void *data, const char *at, size_t length){}void on_response_last_chunk(void *data, const char *at, size_t length){}/*** @brief 解析一系列首部字段回调函数*/void on_response_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen){HttpResponseParser *parser = static_cast<HttpResponseParser*>(data);if(flen == 0){KIT_LOG_WARN(g_logger) << "http response field length=" << flen << "invaild: ";//parser->setError(INVALID_FIELD);return;}parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen));}
C\C++知识点补充复习:memmove()函数
功能:将 n 个字节从内存区域 src 复制到内存区域 dest。 内存区域可能会重叠:复制就像首先将 src 中的字节复制到不与 src 或 dest 重叠的临时数组中一样,然后将字节从临时数组复制到 dest。
- 和
memcpy()功能一模一样,但是其不允许拷贝的源地址和目标地址有内存重叠。当前该函数是允许发生拷贝过程中的内存重叠的。
#include <string.h>void *memmove(void *dest, const void *src, size_t n);
