【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

对于http服务器,http request的解析是比较麻烦的,由于我们的重点并不在这上面,所以这一部分不打算自己编写,而是使用开源的http-parser库,下面我们将使用该库来构建项目中处理http的类。

HTTP Parser简介

http-parser是一个用C编写的HTTP消息解析器,可以解析HTTP请求或者回应消息。

这个解析器常常在高性能的HTTP应用中使用。

在解析的过程中,它不会调用任何系统调用,不会在HEAP上申请内存,不会缓存数据,并且可以在任意时刻打断解析过程,而不会产生任何影响。

对于每个HTTP消息(在WEB服务器中就是每个请求),它只需要40字节的内存占用(解析器本身的基本数据结构)。

特性:

无第三方依赖
可以处理持久消息(keep-alive)
支持解码chunk编码的消息
支持Upgrade协议升级(如无例外就是WebSocket)
可以防御缓冲区溢出攻击

解析器可以处理以下类型的HTTP消息:

头部的字段和值
Content-Length
请求方法
响应的状态码
Transfer-Encoding
HTTP版本
请求的URL
消息主体

Github链接:

https://github.com/nodejs/http-parser

下面我们将根据它提供的接口来编写我们自己的类。

准备工作

首先,我们根据上一节所讲的http知识,定义我们的http request和response结构体:

typedef std::map<std::string, std::string> header_t;
typedef header_t::iterator header_iter_t;

struct HttpRequest
{
    std::string http_method;
    std::string http_url;
    //std::string http_version;

    header_t    http_headers;
    std::string http_header_field; //field is waiting for value while parsing

    std::string http_body;
};

struct HttpResponse
{
    //std::string http_version;
    int         http_code;
    std::string http_phrase;

    header_t    http_headers;

    std::string http_body;

    std::string GetResponse();
    void        ResetResponse();
};

几点说明:

  1. 我们先假定http_version为HTTP/1.1,所以暂时不对该字段进行处理;
  2. http_header_field是在使用http-parser用来暂存header的field的;
  3. GetResponse()函数利用数据成员生成并返回一则response消息字符串(可直接用于发送给客户端);
  4. ResetResponse()函数清空所有数据,准备下一次响应时重用;
std::string HttpResponse::GetResponse()
{
    std::ostringstream ostream;
    ostream << "HTTP/1.1" << " " << http_code << " " << http_phrase << "\r\n"
            << "Connection: keep-alive"                  << "\r\n";

    header_iter_t iter = http_headers.begin();

    while (iter != http_headers.end())
    {
        ostream << iter->first << ": " << iter->second   << "\r\n";
        ++ iter;
    }
    ostream << "Content-Length: " << http_body.size()       << "\r\n\r\n";
    ostream << http_body;

    return ostream.str();
}

void HttpResponse::ResetResponse()
{
    //http_version = "HTTP/1.1";
    http_code = 200;
    http_phrase = "OK";

    http_body.clear();
    http_headers.clear();
}

接下来,我们在Connection类中添加下面几个数据成员:

        HttpRequest        *http_request_parser;    //解析时用
        HttpRequest        *http_request_process;   //处理请求时用
        HttpResponse        http_response;
        HttpParser          http_parser;            

其中http_request_parser将在解析的时候使用(http-parser中);

而http_request_process将在处理请求的时候使用(在connection中)。

HttpParser

我们先看看官方文档的一些说明:

One http_parser object is used per TCP connection. Initialize the struct using http_parser_init() and set the callbacks. That might look something like this for a request parser:

http_parser_settings settings;
settings.on_url = my_url_callback;
settings.on_header_field = my_header_field_callback;
/* ... */

http_parser *parser = malloc(sizeof(http_parser));
http_parser_init(parser, HTTP_REQUEST);
parser->data = my_socket;

上面给的例子是进行配置,初始化的过程,在settings中,我们需要设置一系列的回调函数:

During the http_parser_execute() call, the callbacks set in http_parser_settings will be executed. The parser maintains state and never looks behind, so buffering the data is not necessary. If you need to save certain data for later usage, you can do that from the callbacks.

There are two types of callbacks:

  • notification typedef int (http_cb) (http_parser);

    Callbacks:on_message_begin,on_headers_complete,on_message_complete.

  • data typedef int (http_data_cb) (http_parser, const char *at, size_t length);

    Callbacks:(requests only)on_url,

    (common)n_header_field,on_header_value,on_body;

Callbacks must return 0 on success. Returning a non-zero value indicates error to the parser, making it exit immediately.

根据上面的信息,我们便可以知道我们的HttpParser类需要哪些东西了:

一个parser成员;

一个settings成员;

一个初始化函数,用于初始化parser和settings;

一个解析函数,用于调用http_parser_execute() ;

七个回调函数;

具体代码如下:

class HttpParser
{
    public:
        void InitParser(Connection *con);
        int  HttpParseRequest(const std::string &inbuf);

        static int OnMessageBeginCallback(http_parser *parser);
        static int OnUrlCallback(http_parser *parser, const char *at, size_t length);
        static int OnHeaderFieldCallback(http_parser *parser, const char *at, size_t length);
        static int OnHeaderValueCallback(http_parser *parser, const char *at, size_t length);
        static int OnHeadersCompleteCallback(http_parser *parser);
        static int OnBodyCallback(http_parser *parser, const char *at, size_t length);
        static int OnMessageCompleteCallback(http_parser *parser);

    private:
        http_parser          parser;
        http_parser_settings settings;
};

之后便是具体实现了:

/*
 * 调用http_parser_execute
 * 在该函数执行期间,将调用一系列回调函数
 */
int HttpParser::HttpParseRequest(const std::string &inbuf)
{
    int nparsed = http_parser_execute(&parser, &settings, inbuf.c_str(), inbuf.size());

    if (parser.http_errno != HPE_OK)
    {
        return -1;
    }

    return nparsed;
}

/* 初始化http_request_parser */
int HttpParser::OnMessageBeginCallback(http_parser *parser)
{
    Connection *con = (Connection*)parser->data;

    con->http_request_parser = new HttpRequest();

    return 0;
}

/* 将解析好的url赋值给http_url */
int HttpParser::OnUrlCallback(http_parser *parser, const char *at, size_t length)
{
    Connection *con = (Connection*)parser->data;

    con->http_request_parser->http_url.assign(at, length);

    return 0;
}

/* 将解析到的header_field暂存在http_header_field中 */
int HttpParser::OnHeaderFieldCallback(http_parser *parser, const char *at, size_t length)
{
    Connection *con = (Connection*)parser->data;

    con->http_request_parser->http_header_field.assign(at, length);

    return 0;
}

/* 将解析到的header_value跟header_field一一对应 */
int HttpParser::OnHeaderValueCallback(http_parser *parser, const char *at, size_t length)
{
    Connection      *con  = (Connection*)parser->data;
    HttpRequest *request = con->http_request_parser;

    request->http_headers[request->http_header_field] = std::string(at, length);

    return 0;
}

/* 参照官方文档 */
int HttpParser::OnHeadersCompleteCallback(http_parser *parser)
{
    Connection *con  = (Connection*)parser->data;
    HttpRequest *request = con->http_request_parser;
    request->http_method    = http_method_str((http_method)parser->method);
    return 0;
}

/* 本函数可能被调用不止一次,因此使用append */
int HttpParser::OnBodyCallback(http_parser *parser, const char *at, size_t length)
{
    Connection *con = (Connection*)parser->data;

    con->http_request_parser->http_body.append(at, length); 

    return 0;
}

/* 将解析完毕的消息放到消息队列中 */
int HttpParser::OnMessageCompleteCallback(http_parser *parser)
{
    Connection *con  = (Connection*)parser->data;
    HttpRequest *request = con->http_request_parser;

    con->req_queue.push(request);
    con->http_request_parser = NULL;
    return 0;
}

对于OnHeadersCompleteCallback函数,参照官方文档:

Scalar valued message information such as status_code, method, and the HTTP version are stored in the parser structure. This data is only temporally stored in http_parser and gets reset on each new message. If this information is needed later, copy it out of the structure during the headers_complete callback.

(一些可扩展的信息字段,例如status_code、method和HTTP版本号,它们都存储在解析器的数据结构中。 这些数据被临时的存储在http_parser中,并且会在每个连接到来后被重置(当多个连接的HTTP数据使用同一个解析器时); 如果需要保留这些数据,必须要在on_headers_complete返回之前保存它们。)

事实上,我们每一个http连接都有一个解析器,所以不存在以上问题,但是我们还是按照步骤在on_headers_complete中保存它们。

至此,我们通过http-parser提供的接口定义了自己的HttpParser类,接下来便可以使用了~

在下一节中,我们将开始接触本项目的核心部分——状态机!

时间: 2024-08-03 09:42:46

【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser的相关文章

【slighttpd】基于lighttpd架构的Server项目实战(8)—状态机机制回顾

有限状态机FSM(Finite State Machine) 关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成.状态机通过响应一系列事件而"运行".每个事件都在属于"当前" 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集.函数返回"下一个"(也许是同一个)节点.这些节点中至少有一个必须是终态.当到达终态, 状态机停止. 传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行.很少有事

【slighttpd】基于lighttpd架构的Server项目实战(6)—预备知识之Http

接下来,我们开始http部分的开发.在此之前,有必要先学习一下HTTP协议- http1.1 的rfc文档:http://www.ietf.org/rfc/rfc2616.txt 简介 超文本传输协议(Hypertext Transfer Protocol,简称HTTP)是应用层协议,是一种请求/响应式的协议,即一个客户端与服务器建立连接后,向服务器发送一个请求,服务器接到请求后,给予相应的响应信息. HTTP 请求报文 HTTP 请求报文由请求行.请求头部.空行 和 请求体 4 个部分组成:

【slighttpd】基于lighttpd架构的Server项目实战(10)—插件&amp;动态库

上一节我们介绍了状态机,本节我们将添加插件模块,之后就可以根据公共接口来开发插件,而我们的server则只需要通过状态机调用相应阶段的公共函数,无需关心插件的实现细节.我们的插件将以动态库so的形式来加载. 插件 我们的插件类将作为一个基类,成员函数作为虚函数,之后由插件开发者继承.实现. 本项目的插件接口对应于状态机的阶段,每个阶段提供一个函数: /*************************************************************************

【slighttpd】基于lighttpd架构的Server项目实战(11)—C++的Name Mangling

上一节中,我们介绍了插件作为动态库的加载,其中我们注意到 函数: void* dlsym(void* handle,const char* symbol) 返回的是[symbol对应的地址]. 因此,在我们开发的插件中,SetupPlugin和RemovePlugin函数需要添加extern "C" : extern "C" Plugin* SetupPlugin() { return new MyPlugin(); } extern "C" P

在基于AngularJs架构的ABP项目中使用UEditor

[前提须知] 读过此篇博客 了解angular-ueditor 了解ABP如何使用 会使用VS2017 [1.下载ABP模板] https://aspnetboilerplate.com/Templates 选择ASP.NET MVC 5.x页签下基于AngularJs前端框架和基于Entity Framework对像映射关系的模板,如下图所示: [2.下载UEditor插件] http://ueditor.baidu.com/website/download.html#ueditor 这里我使

企业级电商项目P2P金融项目实战,企业架构师培训视频课程

15套java架构师.集群.高可用.高可扩 展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布 式项目实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Redis.ActiveMQ.Nginx.Mycat. Spring.MongoDB.ZeroMQ.Git.Nosql.Jvm.Mecached.Netty.Nio.Mina.性能调优.高并发.

15套java互联网架构师、高并发、集群、负载均衡、高可用、数据库设计、缓存、性能优化、大型分布式 项目实战视频教程

* { font-family: "Microsoft YaHei" !important } h1 { color: #FF0 } 15套java架构师.集群.高可用.高可扩 展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布 式项目实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Redis.ActiveMQ.Nginx.Mycat

java架构师课程、性能调优、高并发、tomcat负载均衡、大型电商项目实战、高可用、高可扩展、数据库架构设计、Solr集群与应用、分布式实战、主从复制、高可用集群、大数据

15套Java架构师详情 * { font-family: "Microsoft YaHei" !important } h1 { background-color: #006; color: #FF0 } 15套java架构师.集群.高可用.高可扩展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布式项目实战视频教程 视频课程包含: 高级Java架构师包含:Spring boot.Spring  clo

高并发,分布式,高性能,系统架构项目实战

15套java架构师.集群.高可用.高可扩展.高性能.高并发.性能优化.Spring boot.Redis.ActiveMQ.Nginx.Mycat.Netty.Jvm大型分布式项目实战视频教程 视频课程内容包含: 高级Java架构师包含:Spring boot.Spring  cloud.Dubbo.Redis.ActiveMQ.Nginx.Mycat.Spring.MongoDB.ZeroMQ.Git.Nosql.Jvm.Mecached.Netty.Nio.Mina.性能调优.高并发.to