HTTP报文解析封装模块
1 Ragel
Ragel是一个状态机编译器,类似Lex,主要是用来处理字符输入,用于语法解析。简单的文本处理工作一般用正则表达式,或者用awk/sed这些工具就可以处理了,之所以使用Ragel是为了当你的代码的核心任务是解析文本,而且需要高效地处理数据,比如一个SMTP引擎,HTTP引擎,那么Ragel可以按你定义好的语法,生成一个状态机嵌入到你的代码中。因为这个状态机是专门针对你预定义的语法,且以你的原生代码执行,效率自然比正则表达式,awk这些通用工具高的多(据说媲美汇编)。Ragel支持生成C/C++/Java/Ruby/D/C#等各种语言。
Ragel构造状态机是以字符匹配和正则表达式为基础的。从模型上说,有3中状态机构造模型:第一种时传统的Regular Expression,也就是正则表达式,第二种Scanner是扩展的模式,不知道怎么翻译,大家看看就行,最后一种也是扩展的模式,State Chart也就是状态图。这里主要用到的是第一种模式,基于正则表达式
不了解Rangel的可以查看CSDN的这篇文章
不了解有限状态机的可以查看CSDN的这篇文章
不了解正则表达式的可以查看这篇文章)
Ubuntu下Rangel需要安装:sudo apt-get install ragel
%%{ 表示Ragel代码块开始
machine 定义一个状态机
action mark {表示一个action动作,可以在指定位置解析完成调用该动作
main := ("+" | “-” %save_symbol)? (digit %save_number)+ 定义正则表达式,main是个关键字,表示状态机入口
write data 指示Ragel在代码的这个位置写入ragel运行需要的静态数据
}%% 表示Ragel代码块结束write init 指示Ragel在代码的这个位置写入Ragel运行需要的初始化代码
变量p和pe表示状态机处理的buffer起始和终止地址,状态机运行时就从这个buffer依次读入字符,变量名很重要,必须为p和pe,因为Ragel生成的代码里面使用这两个变量名
write exec 指示Ragel在代码的这个位置写入运行状态机的代码
本项目解析的时候复用了github的一个开源Web服务器项目Mongrel2中对于HTTP解析的处理,特别是里面的http11_common.h、http11_parser.h 、httpclient_parser.h、http11_parser.rl、httpclient_parsre.rl
2 约定优于配置
使用配置系统Config为HTTP请求/响应报文解析的一些参数做配置
//配置项 规定一个请求报文首部字段数据长度阈值默认4KB 来规避大数据发包攻击 static sylar::ConfigVaruint64_t>::ptr g_http_request_buffer_size = sylar::Config::Lookup("http.request.buffer_size", (uint64_t)(4 * 1024), "http request buffer size"); //配置项 规定一个请求报文报文实体数据长度阈值默认64MB static sylar::ConfigVaruint64_t>::ptr g_http_request_max_body_size = sylar::Config::Lookup("http.request.max_body_size", (uint64_t)(64 * 1024 * 1024), "http request max body size"); //配置项 规定一个响应报文首部字段数据长度阈值默认4KB 来规避大数据发包攻击 static sylar::ConfigVaruint64_t>::ptr g_http_response_buffer_size = sylar::Config::Lookup("http.response.buffer_size", (uint64_t)(4 * 1024), "http response buffer size"); //配置项 规定一个响应报文报文实体数据长度阈值默认64MB static sylar::ConfigVaruint64_t>::ptr g_http_response_max_body_size = sylar::Config::Lookup("http.response.max_body_size", (uint64_t)(64 * 1024 * 1024), "http response max body size"); 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; //当前响应报文实体数据长度
需要这些参数配置先于程序进入main( )函数之前,就要进行配置操作。并且给这些配置项设置回调,一旦发生修改就自动适应新值
//于main函数之前初始化配置项,并给这些配置项设置回调,一旦发生修改就自动适应新值 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& ov, const uint64_t& nv){ s_http_request_buffer_size = nv; } ); g_http_request_max_body_size->addListener( [](const uint64_t& ov, const uint64_t& nv){ s_http_request_max_body_size = nv; } ); g_http_response_buffer_size->addListener( [](const uint64_t& ov, const uint64_t& nv){ s_http_response_buffer_size = nv; } ); g_http_response_max_body_size->addListener( [](const uint64_t& ov, const uint64_t& nv){ s_http_response_max_body_size = nv; } ); } }; static _RequestSizeIniter _init; }
3 HTTP请求报文解析类HttpRequestParser
3.1 成员变量
class HttpRequestParser { public: typedef std::shared_ptrHttpRequestParser> ptr; … … private: http_parser m_parser; //http_parser,解析请求报文的结构体 HttpRequest::ptr m_data; //HttpRequest结构,请求报文对象智能指针 //错误码 //1000: invalid method //1001: invalid version //1002: invalid field int m_error; };
3.2 构造函数
struct http_parser的成员变量包含普通变量和函数指针,http_parser_init初始化普通成员,其他都设置为回调函数
//对解析请求报文的结构体httpclient_parser进行初始化,需要对报文每一部分的解析指定对应的一个回调函数 HttpRequestParser::HttpRequestParser() :m_error(0) { m_data.reset(new sylar::http::HttpRequest); //初始化http_parser m_parser的所有成员 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; m_parser.http_version = on_request_version; m_parser.header_done = on_request_header_done; m_parser.http_field = on_request_http_field; m_parser.data = this; }
3.3 回调函数
/*根据HTTP请求报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数 ● http11_common.h: 该头文件中给出了回调函数所需的函数签名,有两种类型 typedef void (*element_cb)(void *data, const char *at, size_t length); typedef void (*field_cb)(void *data, const char *field, size_t flen, const char *value, size_t vlen); */ //解析HTTP请求方法回调函数 void on_request_method(void *data, const char *at, size_t length) { HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 HttpMethod m = CharsToHttpMethod(at); if(m == HttpMethod::INVALID_METHOD) { SYLAR_LOG_WARN(g_logger) "invalid http request method: " std::string(at, length); parser->setError(1000); return; } parser->getData()->setMethod(m); } //解析URI回调函数 要自定义URI的解析故不使用 void on_request_uri(void *data, const char *at, size_t length) { } //解析分段标识符回调函数 void on_request_fragment(void *data, const char *at, size_t length) { //SYLAR_LOG_INFO(g_logger) HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 parser->getData()->setFragment(std::string(at, length)); } //解析资源路径回调函数 void on_request_path(void *data, const char *at, size_t length) { HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 parser->getData()->setPath(std::string(at, length)); } //解析查询参数回调函数 void on_request_query(void *data, const char *at, size_t length) { HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 parser->getData()->setQuery(std::string(at, length)); } //解析HTTP协议版本回调函数 void on_request_version(void *data, const char *at, size_t length) { HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 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{ SYLAR_LOG_WARN(g_logger) "invalid http request version: " std::string(at, length); parser->setError(1001); return; } parser->getData()->setVersion(v); } void on_request_header_done(void *data, const char *at, size_t length) { //HttpRequestParser* parser = static_cast(data); } //解析一系列首部字段的回调函数 void on_request_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen) { HttpRequestParser* parser = static_castHttpRequestParser*>(data); //拿到this指针 if(flen == 0) { SYLAR_LOG_WARN(g_logger) "invalid http request field length == 0"; //parser->setError(1002); //invalid field return; } parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen)); }
3.4 接口
uint64_t HttpRequestParser::getContentLength() { return m_data->getHeaderAs("content-length", 0); } /*核心函数:执行解析动作,进行一次对HTTP请求报文的解析。 这个借口比较特殊,使用了开源项目中的http_parser_execute(),该函数是一种状态机的调用形式: 解析不同报文部分,会自动调用不同的回调函数去进行自动的解析。 //1: 成功 //-1: 有错误 //>0: 已处理的字节数,且data有效数据为len - v; */ size_t HttpRequestParser::execute(char* data, size_t len) { size_t offset = http_parser_execute(&m_parser, data, len, 0); //power: 解析,这里是关键 //先将解析过的空间挪走 防止缓存不够 但是仍然有数据位解析完成的情况 memmove(data, data + offset, (len - offset)); //返回实际解析过的字节数 return offset; } int HttpRequestParser::isFinished() { return http_parser_finish(&m_parser); } int HttpRequestParser::hasError() { return m_error || http_parser_has_error(&m_parser); }
4 HTTP响应报文解析类HttpResponseParser
4.1 成员变量
struct httpclient_parser的成员变量包含普通变量和函数指针,http_parser_init初始化普通成员,其他都设置为回调函数
class HttpResponseParser { public: typedef std::shared_ptrHttpResponseParser> ptr; ... ... private: httpclient_parser m_parser; //解析响应报文的结构体 HttpResponse::ptr m_data; //响应报文对象 //错误码 //1001: invalid version //1002: invalid field int m_error; };
4.2 构造函数
HttpResponseParser::HttpResponseParser() :m_error(0) { m_data.reset(new sylar::http::HttpResponse); httpclient_parser_init(&m_parser); m_parser.reason_phrase = on_response_reason; m_parser.status_code = on_response_status; m_parser.chunk_size = on_response_chunk; m_parser.http_version = on_response_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指针放入 }
4.3 回调函数
/*根据HTTP响应报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数*/ //解析状态原因短语回调函数 void on_response_reason(void *data, const char *at, size_t length) { HttpResponseParser* parser = static_castHttpResponseParser*>(data); parser->getData()->setReason(std::string(at, length)); } //解析状态码回调函数 void on_response_status(void *data, const char *at, size_t length) { HttpResponseParser* parser = static_castHttpResponseParser*>(data); HttpStatus status = (HttpStatus)(atoi(at)); parser->getData()->setStatus(status); } //解析HTTP协议版本回调函数 void on_response_chunk(void *data, const char *at, size_t length) { } void on_response_version(void *data, const char *at, size_t length) { HttpResponseParser* parser = static_castHttpResponseParser*>(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{ SYLAR_LOG_WARN(g_logger) "invalid http response version: " std::string(at, length); parser->setError(1001); 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) { } //解析一系列首部字段回调函数 void on_response_http_field(void *data, const char *field, size_t flen ,const char *value, size_t vlen) { HttpResponseParser* parser = static_castHttpResponseParser*>(data); if(flen == 0) { SYLAR_LOG_WARN(g_logger) "invalid http response field length == 0"; //parser->setError(1002); //invalid field return; } parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen)); }
4.4 接口
/*执行解析动作,进行一次对HTTP响应报文的解析。这个借口比较特殊,使用了开源项目中的httpclient_parser_execute(),该函数是一种状态机的调用形式: 解析不同报文部分,会自动调用不同的回调函数去进行自动的解析,然后判断这一段是解析成功还是解析失败。 和HTTP请求报文解析不同的点在于,响应报文不一定一次就全部发送完毕。可能存在分块发送的情况,即:chunck形式。 但是复用的开源项目里并不支持对报文的分段解析,需要我们特殊处理: 如果为chunck发包形式的话,每一次解析就要重置解析结构体的状态,防止记录上一个包的状态。 */ size_t HttpResponseParser::execute(char* data, size_t len, bool chunck) { if(chunck) { //如果为chunck包需要重新初始化一下解析包 httpclient_parser_init(&m_parser); } //每一次都是从头开始解析 size_t offset = httpclient_parser_execute(&m_parser, data, len, 0); //power: 这里是关键 //先将解析过的空间挪走 memmove(data, data + offset, (len - offset)); //返回实际解析过的字节数 return offset; } int HttpResponseParser::isFinished() { return httpclient_parser_finish(&m_parser); } int HttpResponseParser::hasError() { return m_error || httpclient_parser_has_error(&m_parser); } uint64_t HttpResponseParser::getContentLength() { return m_data->getHeaderAs("content-length", 0); }