准备:认识 Ragel

概念:是一个状态机编译器,类似于Lex(词法分析器)。
功能:用于处理字符输入、语法解析。一般的简单的文本处理工作我们会使用正则表达式,或者使用Linux下自带的awk/sed这些工具。
当我们的代码核心任务是:解析文本且需要高效率的处理数据,例如搭建一个STMP引擎、HTTP引擎,Ragel就可以根据我们定义好的语法,生成一个状态机嵌入到我们的代码当中,且以我们的原生代码进行执行,效率自然会比以上的方法要高。。[

](https://blog.csdn.net/weixin_43798887/article/details/116100949?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522164266638816780255259033%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=164266638816780255259033&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-3-116100949.pc_search_result_cache&utm_term=ragel&spm=1018.2226.3001.4187)
$ sudo apt-get install ragel即可安装

准备:复用开源项目HTTP解析的处理

复用github的一个开源Web服务器项目中对于HTTP解析的处理:
Mongrel2: The Language Agnostic Web Server

  • 主要使用其中的文件:http11_common.hhttp11_parser.hhttpclient_parser.hhttp11_parser.rlhttpclient_parsre.rl

image.png

** 相关配置定义

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::ptr g_http_request_max_body_size = Config::LookUp(“http.request.max_body_size”, (uint64_t)(64 1024 1024), “http request max body size”);

//使用一个配置项 规定一个首部字段数据长度阈值默认4KB 来规避大数据发包攻击 static ConfigVar::ptr g_http_response_buffer_size = Config::LookUp(“http.response.buffer_size”, (uint64_t)(4 * 1024), “http response buffer size”);

//使用一个配置项 规定一个报文实体数据长度阈值默认64MB static ConfigVar::ptr g_http_response_max_body_size = 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;

  1. <a name="qCIPq"></a>
  2. ## 2. 配置项初始化
  3. 目的:需要这些参数配置先于程序进入main( )函数之前,就要进行配置操作。并且给这些配置项设置回调,一旦发生修改就自动适应新值
  4. ```cpp
  5. //初始化结构放在匿名空间 防止污染
  6. namespace {
  7. struct _RequestSizeIniter
  8. {
  9. //初始化 并设置回调
  10. _RequestSizeIniter()
  11. {
  12. s_http_request_buffer_size = g_http_request_buffer_size->getValue();
  13. s_http_request_max_body_size = g_http_request_max_body_size->getValue();
  14. s_http_response_buffer_size = g_http_response_buffer_size->getValue();
  15. s_http_response_max_body_size = g_http_response_max_body_size->getValue();
  16. g_http_request_buffer_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){
  17. s_http_request_buffer_size = new_value;
  18. });
  19. g_http_response_max_body_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){
  20. s_http_response_max_body_size = new_value;
  21. });
  22. g_http_response_buffer_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){
  23. s_http_response_buffer_size = new_value;
  24. });
  25. g_http_request_max_body_size->addListener([](const uint64_t &old_value, const uint64_t &new_value){
  26. s_http_request_max_body_size = new_value;
  27. });
  28. }
  29. };
  30. static _RequestSizeIniter _initer;
  31. }

1. HTTP请求解析类封装

真正在使用封装好的HTTP解析的接口的时候,解析数据不会一次性将HTTP报文数据接收完整,由于网络延迟、数据量大等原因,发送的时候会拆分为多个报文小段进行发送,解析的时候也是一小段一小段进行解析,解析完一次就要返回一个状态去判别下一步应该进行什么操作。

1.1 成员变量

  1. class HttpRequestParser
  2. {
  3. ....
  4. ....
  5. private:
  6. //解析请求报文的结构体
  7. http_parser m_parser;
  8. //请求报文对象智能指针
  9. HttpRequest::ptr m_request;
  10. //1000: invalid method
  11. //1001: invalid version
  12. //1002: invalid field
  13. int m_error; //错误码
  14. };

1.2 接口

1.2.1 构造函数

功能:对解析请求报文的结构体http_parser进行初始化,需要对报文每一部分的解析指定对应的一个回调函数。

  1. /**
  2. * @brief HTTP请求解析类构造函数
  3. */
  4. HttpRequestParser();
  5. HttpRequestParser::HttpRequestParser()
  6. :m_error(0)
  7. {
  8. m_request.reset(new HttpRequest);
  9. // 调用初始化API
  10. http_parser_init(&m_parser);
  11. m_parser.request_method = on_request_method;
  12. m_parser.request_uri = on_request_uri;
  13. m_parser.fragment = on_request_fragment;
  14. m_parser.request_path = on_request_path;
  15. m_parser.query_string = on_request_query_string;
  16. m_parser.http_version = on_request_http_version;
  17. m_parser.header_done = on_request_header_done;
  18. m_parser.http_field = on_request_http_field;
  19. m_parser.data = this; //this指针放入
  20. }

1.2.2 execute()(核心)

功能:执行解析动作,进行一次对HTTP请求报文的解析。这个借口比较特殊,使用了开源项目中的http_parser_execute(),该函数是一种状态机的调用形式:解析不同报文部分,会自动调用不同的回调函数去进行自动的解析。

  1. /**
  2. * @brief 执行解析动作
  3. * @param[in] data 需要解析的具体数据
  4. * @param[in] len 解析数据的长度
  5. * @return size_t 1:解析成功 -1:解析有问题 >0已经处理的字节数
  6. */
  7. size_t execute(char* data, size_t len);
  8. size_t HttpRequestParser::execute(char* data, size_t len)
  9. {
  10. size_t ret = http_parser_execute(&m_parser, data, len, 0);
  11. //先将解析过的空间挪走 防止缓存不够 但是仍然有数据位解析完成的情况
  12. memmove(data, data + ret, (len - ret));
  13. //返回实际解析过的字节数
  14. return ret;
  15. }

1.2.3 isFinished()

功能:判断当前这次报文解析是否结束。复用开源项目的http_parser_finish()函数。

  1. /**
  2. * @brief 报文解析是否结束
  3. * @return int
  4. */
  5. int isFinished();
  6. int HttpRequestParser::isFinished()
  7. {
  8. return http_parser_finish(&m_parser);
  9. }

1.2.4 hasError()

功能:判断当前这次报文解析是否出错。复用开源项目的http_parser_has_error()函数。

  1. /**
  2. * @brief 解析是否出错
  3. * @return int
  4. */
  5. int hasError();
  6. int HttpRequestParser::hasError()
  7. {
  8. return m_error || http_parser_has_error(&m_parser);
  9. }

1.2.5 getData()

功能:获取HTTP请求报文

  1. /**
  2. * @brief 获取HTTP请求报文
  3. * @return HttpRequest::ptr
  4. */
  5. HttpRequest::ptr getData() const {return m_request;}

1.2.6 setError()

功能:设置解析时的错误码

  1. /**
  2. * @brief 设置解析时的错误码
  3. * @param[in] v 具体错误码
  4. */
  5. void setError(int v) {m_error = v;}

1.2.7 getContentLength()

功能:获取报文主体字段”content-length”中的长度,并将该值由string转换为uint64_t

  1. /**
  2. * @brief 获取报文主体字段"content-length"中的长度 并转换为uint64_t
  3. * @return uint64_t
  4. */
  5. uint64_t getContentLength();
  6. uint64_t HttpRequestParser::getContentLength()
  7. {
  8. uint64_t v = 0;
  9. return m_request->GetHeaderAs<uint64_t>("content-length", v);
  10. }

1.2.8 GetHttpRequestBufferSize()

功能:获取请求报文头部最大缓冲区大小

  1. /**
  2. * @brief 获取请求报文头部最大缓冲区大小
  3. * @return uint64_t
  4. */
  5. static uint64_t GetHttpRequestBufferSize();
  6. uint64_t HttpRequestParser::GetHttpRequestBufferSize()
  7. {
  8. return s_http_request_buffer_size;
  9. }

1.2.9 GetHttpMaxBodySize()

功能:获取请求报文主体最大大小

  1. /**
  2. * @brief 获取请求报文主体最大大小
  3. * @return uint64_t
  4. */
  5. static uint64_t GetHttpMaxBodySize();
  6. uint64_t HttpRequestParser::GetHttpMaxBodySize()
  7. {
  8. return s_http_request_max_body_size;
  9. }

1.3 解析所需回调函数

功能:根据HTTP请求报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数。

  • http11_common.h:

该头文件中给出了回调函数所需的函数签名,有两种类型。
image.png

  1. /**
  2. * @brief 解析HTTP请求方法回调函数
  3. */
  4. void on_request_method(void *data, const char *at, size_t length)
  5. {
  6. //拿到this指针
  7. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  8. HttpMethod method = CharsToHttpMethod(at);
  9. if(method == HttpMethod::INVALID_METHOD)
  10. {
  11. KIT_LOG_WARN(g_logger) << "http request invaild method:"
  12. << std::string(at, length);
  13. parser->setError(INVALID_METHOD);
  14. return;
  15. }
  16. parser->getData()->setMethod(method);
  17. }
  18. /**
  19. * @brief 解析URI回调函数 要自定义URI的解析故不使用
  20. */
  21. void on_request_uri(void *data, const char *at, size_t length)
  22. {
  23. }
  24. /**
  25. * @brief 解析分段标识符回调函数
  26. */
  27. void on_request_fragment(void *data, const char *at, size_t length)
  28. {
  29. //拿到this指针
  30. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  31. parser->getData()->setFragment(std::string(at, length));
  32. }
  33. /**
  34. * @brief 解析资源路径回调函数
  35. */
  36. void on_request_path(void *data, const char *at, size_t length)
  37. {
  38. //拿到this指针
  39. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  40. parser->getData()->setPath(std::string(at, length));
  41. }
  42. /**
  43. * @brief 解析查询参数回调函数
  44. */
  45. void on_request_query_string(void *data, const char *at, size_t length)
  46. {
  47. //拿到this指针
  48. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  49. parser->getData()->setQuery(std::string(at, length));
  50. }
  51. /**
  52. * @brief 解析HTTP协议版本回调函数
  53. */
  54. void on_request_http_version(void *data, const char *at, size_t length)
  55. {
  56. //拿到this指针
  57. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  58. uint8_t v = 0;
  59. if(strncmp(at, "HTTP/1.1", length) == 0)
  60. {
  61. v = 0x11;
  62. }
  63. else if(strncmp(at, "HTTP/1.0", length) == 0)
  64. {
  65. v = 0x10;
  66. }
  67. else
  68. {
  69. KIT_LOG_WARN(g_logger) << "http request version invaild:"
  70. << std::string(at, length);
  71. parser->setError(INVALID_VERSION);
  72. return;
  73. }
  74. parser->getData()->setVersion(v);
  75. }
  76. void on_request_header_done(void *data, const char *at, size_t length)
  77. {
  78. }
  79. /**
  80. * @brief 解析一系列首部字段的回调函数
  81. */
  82. void on_request_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
  83. {
  84. //拿到this指针
  85. HttpRequestParser* parser = static_cast<HttpRequestParser*>(data);
  86. // KIT_LOG_DEBUG(g_logger) << "request parser:\n" << value;
  87. if(flen == 0)
  88. {
  89. KIT_LOG_WARN(g_logger) << "http request field length=" << flen;
  90. /*不作为错误 处理 否则会返回nullptr*/
  91. // parser->setError(INVALID_FIELD);
  92. return;
  93. }
  94. parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen));
  95. }

2. HTTP响应解析类封装

2.1 成员变量

  1. class HttpResponseParser
  2. {
  3. ....
  4. ....
  5. private:
  6. //解析响应报文的结构体
  7. httpclient_parser m_parser;
  8. //响应报文对象
  9. HttpResponse::ptr m_response;
  10. //错误码
  11. int m_error;
  12. };

2.2 接口

2.2.1 构造函数

功能:对解析请求报文的结构体httpclient_parser进行初始化,需要对报文每一部分的解析指定对应的一个回调函数

  1. /**
  2. * @brief HTTP响应解析类构造函数
  3. */
  4. HttpResponseParser();
  5. HttpResponseParser::HttpResponseParser()
  6. :m_error(0)
  7. {
  8. m_response.reset(new HttpResponse);
  9. httpclient_parser_init(&m_parser);
  10. m_parser.reason_phrase = on_response_reason_phrase;
  11. m_parser.status_code = on_response_status_code;
  12. m_parser.chunk_size = on_response_chunk_size;
  13. m_parser.http_version = on_response_http_version;
  14. m_parser.header_done = on_response_header_done;
  15. m_parser.last_chunk = on_response_last_chunk;
  16. m_parser.http_field = on_response_http_field;
  17. m_parser.data = this; //this指针放入
  18. }

2.2.2 execute()(核心)

功能:执行解析动作,进行一次对HTTP响应报文的解析。这个借口比较特殊,使用了开源项目中的httpclient_parser_execute(),该函数是一种状态机的调用形式:解析不同报文部分,会自动调用不同的回调函数去进行自动的解析。

注意:和HTTP请求报文解析不同的点在于,响应报文不一定一次就全部发送完毕。可能存在分块发送的情况,即:chunck形式。但是复用的开源项目里并不支持对报文的分段解析,需要我们特殊处理:如果为chunck发包形式的话,每一次解析就要重置解析结构体的状态,防止记录上一个包的状态。

  1. /**
  2. * @brief 执行解析动作
  3. * @param[in] data 需要解析的具体数据
  4. * @param[in] len 解析数据的长度
  5. * @param[in] chunck 标志位 判断是否是chunck类型
  6. * @return size_t 1:解析成功 -1:解析有问题 >0已经处理的字节数
  7. */
  8. size_t execute(char* data, size_t len, bool chunck);
  9. size_t HttpResponseParser::execute(char* data, size_t len, bool chunck)
  10. {
  11. //如果为chunck包需要重新初始化一下解析包
  12. if(chunck)
  13. httpclient_parser_init(&m_parser);
  14. //每一次都是从头开始解析
  15. size_t ret = httpclient_parser_execute(&m_parser, data, len, 0);
  16. //先将解析过的空间挪走
  17. memmove((void *)data, data + ret, (len - ret));
  18. //返回实际解析过的字节数
  19. return ret;
  20. }

1.2.3 isFinished()

功能:判断当前这次报文解析是否结束。复用开源项目的httpclient_parser_finish()函数。

  1. /**
  2. * @brief 报文解析是否结束
  3. * @return int
  4. */
  5. int isFinished();
  6. int HttpResponseParser::isFinished()
  7. {
  8. return httpclient_parser_finish(&m_parser);
  9. }

2.2.4 hasError()

功能:判断当前这次报文解析是否出错。复用开源项目的httpclient_parser_has_error()函数。

  1. /**
  2. * @brief 解析是否出错
  3. * @return int
  4. */
  5. int hasError();
  6. int HttpResponseParser::hasError()
  7. {
  8. return m_error || httpclient_parser_has_error(&m_parser);
  9. }

1.2.5 getData()

功能:获取HTTP响应报文

  1. /**
  2. * @brief 获取HTTP响应报文
  3. * @return HttpRequest::ptr
  4. */
  5. HttpResponse::ptr getData() const {return m_response;}

1.2.6 setError()

功能:设置解析时的错误码

  1. /**
  2. * @brief 设置解析时的错误码
  3. * @param[in] v 具体错误码
  4. */
  5. void setError(int v) {m_error = v;}

1.2.7 getContentLength()

功能:获取报文主体字段”content-length”中的长度,并将该值由string转换为uint64_t

  1. /**
  2. * @brief 获取报文主体字段"content-length"中的长度 并转换为uint64_t
  3. * @return uint64_t
  4. */
  5. uint64_t getContentLength();
  6. uint64_t HttpResponseParser::getContentLength()
  7. {
  8. uint64_t v = 0;
  9. return m_response->GetHeaderAs<uint64_t>("content-length", v);
  10. }

1.2.8 GetHttpRequestBufferSize()

功能:获取请求报文头部最大缓冲区大小

  1. /**
  2. * @brief 获取响应报文头部最大缓冲区大小
  3. * @return uint64_t
  4. */
  5. static uint64_t GetHttpResponseBufferSize();
  6. uint64_t HttpResponseParser::GetHttpResponseBufferSize()
  7. {
  8. return s_http_response_buffer_size;
  9. }

1.2.9 GetHttpMaxBodySize()

功能:获取请求报文主体最大大小

  1. /**
  2. * @brief 获取响应报文主体最大大小
  3. * @return uint64_t
  4. */
  5. static uint64_t GetHttpMaxBodySize();
  6. uint64_t HttpResponseParser::GetHttpMaxBodySize()
  7. {
  8. return s_http_response_max_body_size;
  9. }

2.3 解析所需回调函数

功能:根据HTTP响应报文不同部分,调用相应的回调函数处理。 仿照开源项目给出的函数签名形式,定义我们自己的解析回调函数。

  1. /**
  2. * @brief 解析状态原因短语回调函数
  3. */
  4. void on_response_reason_phrase(void *data, const char *at, size_t length)
  5. {
  6. HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);
  7. parser->getData()->setReason(std::string(at, length));
  8. }
  9. /**
  10. * @brief 解析状态码回调函数
  11. */
  12. void on_response_status_code(void *data, const char *at, size_t length)
  13. {
  14. HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);
  15. HttpStatus status = (HttpStatus)(atoi(at));
  16. parser->getData()->setStatus(status);
  17. }
  18. void on_response_chunk_size(void *data, const char *at, size_t length)
  19. {
  20. }
  21. /**
  22. * @brief 解析HTTP协议版本回调函数
  23. */
  24. void on_response_http_version(void *data, const char *at, size_t length)
  25. {
  26. HttpResponseParser* parser = static_cast<HttpResponseParser*>(data);
  27. uint8_t v = 0;
  28. if(strncmp(at, "HTTP/1.1", length) == 0)
  29. {
  30. v = 0x11;
  31. }
  32. else if(strncmp(at, "HTTP/1.0", length) == 0)
  33. {
  34. v = 0x10;
  35. }
  36. else
  37. {
  38. KIT_LOG_WARN(g_logger) << "http response version invaild:"
  39. << std::string(at, length);
  40. parser->setError(INVALID_VERSION);
  41. return;
  42. }
  43. parser->getData()->setVersion(v);
  44. }
  45. void on_response_header_done(void *data, const char *at, size_t length)
  46. {
  47. }
  48. void on_response_last_chunk(void *data, const char *at, size_t length)
  49. {
  50. }
  51. /**
  52. * @brief 解析一系列首部字段回调函数
  53. */
  54. void on_response_http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
  55. {
  56. HttpResponseParser *parser = static_cast<HttpResponseParser*>(data);
  57. if(flen == 0)
  58. {
  59. KIT_LOG_WARN(g_logger) << "http response field length=" << flen << "invaild: ";
  60. //parser->setError(INVALID_FIELD);
  61. return;
  62. }
  63. parser->getData()->setHeader(std::string(field, flen), std::string(value, vlen));
  64. }

C\C++知识点补充复习:memmove()函数

功能:将 n 个字节从内存区域 src 复制到内存区域 dest。 内存区域可能会重叠:复制就像首先将 src 中的字节复制到不与 src 或 dest 重叠的临时数组中一样,然后将字节从临时数组复制到 dest。

  • memcpy()功能一模一样,但是其不允许拷贝的源地址和目标地址有内存重叠。当前该函数是允许发生拷贝过程中的内存重叠的。
  1. #include <string.h>
  2. void *memmove(void *dest, const void *src, size_t n);