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

2023-05-16

转载地址:https://blog.csdn.net/jiange_zh/article/details/50639178

对于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 settins;
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;

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类,接下来便可以使用了~

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • Express.js在控制器中获取http方法

    我正在构建一个注册表单 本地护照作为身份验证 表单作为表单助手 因为注册只知道 GET 和 POST 我想在一个函数中完成整个处理 换句话说 我正在寻找类似的东西 exports register function req res if r
  • gRPC(HTTP/2) 比使用 HTTP/2 的 REST 更快吗?

    目标是引入一种性能更好的传输和应用层协议latency and 网络吞吐量 目前 该应用程序使用REST with HTTP 1 1并且我们遇到了很高的延迟 我需要解决这个延迟问题并且我愿意使用gRPC HTTP 2 or 休息 HTTP2
  • 反向代理受 NTLM 保护的网站

    如何将请求代理到受 NTLM 保护的网站 例如团队基金会 and 共享点 我不断得到401 身份验证错误 根据这篇 Microsoft TechNet 文章 https www microsoft com technet prodtechn
  • 保存到服务器后,隐藏字符“\u0”添加到文件中

    我正在使用 Apache 服务器为 Web 开发网站提供服务 这样我就可以不断保存和编辑文件 我使用 Gulp for Sass 来连接和丑化 css 和 js 文件 一个月前 我的 js 和 css 文件遇到问题 似乎在文件下面添加了随机
  • PHP 是否有与 Java 的 RequestDispatcher.forward 等效的功能?

    在 Java 中我可以编写一个非常基本的 JSPindex jsp像这样 这样做的效果是用户请求index jsp 或者只是包含目录 假设index jsp是目录的默认文档 将会看到home action没有浏览器重定向 即 forward
  • 为什么使用 HTTP 动词?

    因为动词的目标是像 server domain getallrecords 或 server domain delete1record 或类似的 URL 而getallrecords delete1record都是专门为特定目的而设计的 为
  • 减少1000张图片的HTTP请求?

    我知道这个问题可能听起来有点疯狂 但我想也许有人会想出一个聪明的主意 假设您在一个 HTML 页面上有 1000 个缩略图 图像大小约为5 10 kb 有没有办法在单个请求中加载所有图像 以某种方式将所有图像压缩到一个文件中 或者您对该主题
  • 通过 HTTPS 加载页面但请求不安全的 XMLHttpRequest 端点

    我有一个页面 上面有一些 D3 javascript 该页面位于 HTTPS 网站内 但证书是自签名的 当我加载页面时 我的 D3 可视化效果不显示 并且出现错误 混合内容 页面位于 https integration jsite com
  • 按照约定应返回哪些 REST PUT/POST/DELETE 调用?

    根据 REST 意识形态 PUT POST DELETE 请求的响应正文中应该包含什么 返回码呢 是HTTP OK enough 如果有的话 这种约定的原因是什么 我发现了一篇描述 POST PUT 差异的好文章 发布与放置 http ww
  • MPMoviePlayerController 播放 YouTube 视频

    如何在 iPhone 上的 MPMoviePlayerController 中播放 YouTube 视频 同时避免进入全屏模式 这个问题已经在这里提出 MPMoviePlayerController 正在播放 YouTube 视频吗 htt
  • 我应该使用多个 HttpClient 来进行批量异步 GET 请求吗?

    我有一个场景 我需要在尽可能短的时间内发出大量 GET 请求 想想大约 1000 个 我知道通常最好保留一个客户端并尽可能重用它 Create Single HTTP Client HttpClient client new HttpCli
  • 在 iOS 中,http 204 响应返回空白页面,有办法阻止这种情况吗?

    以前可能有人问过这个问题 但我似乎找不到解决方案 所以如果是这种情况 我深表歉意 我正在开发一个使用express的简单节点应用程序 其中一个帖子路由返回 http 204 并发送它 下面是我的代码 router post id funct
  • 如何使用批处理脚本调用的curl 获取http post 请求的响应代码?

    我正在努力为从我们的工具之一发送 http post 请求提供支持 该工具基本上通过 http 请求执行作业 实现此目的的方法是该工具使用多个参数调用 RunScript bat 该脚本解析这些参数并在验证后发出curl post 请求 P
  • Chrome 开发工具无法显示响应,即使返回的内容具有标题 Content-Type:text/html;字符集=UTF-8

    为什么我的 Chrome 开发者工具显示 无法显示响应数据 当返回的内容是text html类型时响应 在开发者工具中查看返回的响应的替代方法是什么 我认为只有当您选中 保留日志 并且您在导航离开后尝试查看先前请求的响应数据时 才会发生这种
  • ASP.NET Core URL 重写

    我正在尝试将我的网站从 www 重定向到非 www 规则以及 http 到 https https example com https example com 在中间件中 我曾经在 web config 中进行这些重定向更改 例如
  • HttpWebRequest vs Webclient(特殊场景)

    我知道这个问题之前已经回答过thread https stackoverflow com questions 1694388 webclient vs httpwebrequest httpwebresponse 但我似乎找不到详细信息 在
  • 404 标头 - HTTP 1.0 还是 1.1?

    为什么我能找到的几乎每个例子 包括这个问题 https stackoverflow com questions 437256 sending a 404 error in php大约一年前 说 404 标头应该是HTTP 1 0 404 N
  • 使用 Unity 在 C# 中发送 http 请求

    如何使用 Unity 在 C 中发送 HTTP GET 和 POST 请求 我想要的是 在post请求中发送json数据 我使用Unity序列化器 所以不需要 新的 我只想在发布数据中传递一个字符串并且能够 将 ContentType 设置
  • Node.js 未处理的“错误”事件

    我编写了一个简单的代码并将其保存在文件 try js 中 var http require http var makeRequest function message var options host localhost port 8080
  • Flutter http请求上传mp3文件

    我使用这个 api 上传 mp3 文件 使用这种方法 Future

随机推荐