准备:认识 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 field
int m_error; //错误码
};
1.2 接口
1.2.1 构造函数
功能:对解析请求报文的结构体http_parser
进行初始化,需要对报文每一部分的解析指定对应的一个回调函数。
/**
* @brief HTTP请求解析类构造函数
*/
HttpRequestParser();
HttpRequestParser::HttpRequestParser()
:m_error(0)
{
m_request.reset(new HttpRequest);
// 调用初始化API
http_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);