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);
}
全部评论

相关推荐

还想问问golang有哪些推荐的简历项目,在哪可以找
Terryhimself:计网 数据库 redis都常问,然后项目的话有一个7days golang系列可以看看,一般一个web项目+一个轮子项目日常实习就够了,然后就是看下八股 刷刷算法题
点赞 评论 收藏
分享
点赞 3 评论
分享
牛客网
牛客企业服务