slighttpd基于lighttpd架构的Server项目实战—http-parser

Posted jiange_zh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了slighttpd基于lighttpd架构的Server项目实战—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类,接下来便可以使用了~

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

以上是关于slighttpd基于lighttpd架构的Server项目实战—http-parser的主要内容,如果未能解决你的问题,请参考以下文章

slighttpd基于lighttpd架构的Server项目实战—http-parser

slighttpd基于lighttpd架构的Server项目实战—状态机机制回顾

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

slighttpd基于lighttpd架构的Server项目实战—预备知识之Http

基于 Serverless 架构的编程学习小工具

基于 Serverless 架构的编程学习小工具