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

2023-05-16

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

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

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

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

  • 在 Flash AS3 中捕获未处理的 IOErrorEvent

    错误 2044 未处理的 IOErrorEvent text 错误 2036 从不加载 完全的 这就是我每次尝试使用加载器加载不存在的图像时看到的情况 我正在获取 URL 列表 但无法验证它们是否指向任何有用的内容 每当遇到 404 时 它
  • 在 Android WebView 中获取 HTTP 状态代码

    我正在开发一个 Android 应用程序 该应用程序在 WebView 中加载网站 但有时该网站返回 HTTP 代码 500 我的问题是 有没有办法通过侦听器或另一个类从 WebView 获取 HTTP 状态代码 我尝试实现 WebView
  • http://*:80 和 http://+:80 有什么区别

    在学习中关于网络部署 http technet microsoft com en us library dd569093 28WS 10 29 aspx我遇到了一些涉及 http 80 和 http 80 的 netsh exe 命令 这些
  • 在同一 URL 上运行 SOAP 和 RESTful

    假设我们有一个响应主机标头 kebab shop intra net 的网站 此 URL 中是否可以同时包含 SOAP 和 RESTful 也就是说 这两者都是在已部署的代码中处理的 kebab shop intra net takeawa
  • 使用 RCurl 登录 WordPress

    我想使用 R 的 RCurl 包登录 WordPress 网站 以便安装 WordPress 插件 可能在 WordPress 的某些选项页面上使用 postForm 由于该网站受密码保护 我请求您帮助如何验证我的 R 会话 我发现以下三个
  • 在 Java 中使用 JSON 的 HTTP POST

    我想在 Java 中使用 JSON 制作一个简单的 HTTP POST 假设网址是www site com 它接受值 name myname age 20 标记为 details 例如 我将如何创建 POST 语法 我似乎也无法在 JSON
  • 无法远程连接到Python Socket

    我已经使用 python 套接字和 Tkinter 创建了一个聊天应用程序 它在本地运行得很好 但是客户端无法远程连接到服务器 当我输入我的公共 IP 地址作为主机时 我已经完全端口转发了我的网络并且我知道如何很好地进行端口转发 当我运行在
  • IIS 中特定资源的自定义 http 状态代码

    我有一个带有单个 app offline htm 文件的网站 我如何配置 IIS 以使用特定状态代码 例如 209 而不是默认状态代码 200 返回它 使用 ASP Net 的 app offline htm 功能的替代方法是使用IIS U
  • 如何修改 HttpUrlConnection 的标头

    我试图稍微改进一下 Java Html 文档 但我遇到了问题HttpUrlConntion 有一件事是 如果用户代理是 Java VM 某些服务器会阻止请求 另一个问题是HttpUrlConnection不设置Referrer or Loc
  • 如何在 ASP.NET Core 控制器中接收“multipart/mixed”

    旧系统会向我发送以下内容 POST xml HTTP 1 1 Host localhost 9000 User Agent curl 7 64 1 Accept Content Length 321 Content Type multipa
  • 是否可以要求您的用户清除您网站的 HTTP 严格传输安全 (HSTS)?

    如果您为具有较长生命周期的网站打开 HSTS 但后来决定将其关闭 例如由于第三方软件的问题 是否可以警告用户清除其 HSTS 缓存 要关闭服务器的 HSTS 请发送以下标头 Strict Transport Security max age
  • 角度 $q.all() 是否有第二次成功,如 jQuery $.get()

    查看 jQuery 文档 我发现了以下内容 get example php function alert success done function alert second success lt fail function alert e
  • REST DELETE 真的是幂等的吗?

    DELETE 应该是幂等的 如果我删除http example com account 123 http example com account 123它将删除该帐户 如果我再次这样做 我会收到 404 错误吗 因为该帐户已不存在 如果我尝
  • Node.js 管道化 HTTP 客户端代理?

    Node js 中内置的 HTTP 客户端似乎不支持管道请求 https stackoverflow com a 5776649 362536 然而 我突然想到 也许可以创建一个Agent https nodejs org api http
  • 检查 Javascript 中的 URL 是否损坏

    这个问题之前已经发布在 Stack 上 但没有一个具体到我想要理解的内容 检查 URL 是否正确的最简单方法是发送 http Head 请求 但是如何使用它来指定 URL 呢 我在之前的帖子中发现了这一点 function UrlExist
  • ResponseNotReady 对于真正简单的 python http 请求?

    我正在用 python 编写一个简单的脚本重放保存的 HTTP 请求 https stackoverflow com questions 8384848 method program for sending a given http req
  • gradle - 从 url 下载并解压文件

    从 url 下载和解压文件的正确 gradle 方法是什么 http 如果可能的话 我想防止每次运行任务时重新下载 在ant get可以通过以下方式实现skipexisting true 我当前的解决方案是 task foo ant get
  • iPhone Mobile Safari,最大并行 http 连接数是多少?

    我想在 iPhone Mobile Safari OS4 中使用并行 AJAX HTTP 请求 最大并行连接数是多少 如果我没记错的话 Safari 最多使用 4 个到同一服务器的连接 但您可以使用以下命令自行测试这个小测试用例 http
  • 错误:在 Google 应用引擎上部署节点 js 时找不到模块“/workspace/server.js”

    经过一周的搜索 我无法找到适用于我的 Node js 应用程序的应用程序引擎部署问题的解决方案 我已经用这个替换了原来的代码Express 的 hello world 示例 https expressjs com en starter he
  • 将对象数组作为请求中的 url 参数传递

    我需要将一个对象数组 每个对象有 2 个字段 作为 http 请求的 url 中的参数 我该怎么做以及这个链接应该是什么样子 您可以使用您的结构创建一个 xml 即一个对象数组 每个对象都有两个字段 然后将其转换为字符串 如下所示 举个例子

随机推荐

  • java中GBK与UTF-8编码的转换

    文章目录 java源文件中中文字符的编码的问题UTF 8和GBK格式的文件相互转换java实现文件编码的转换 java不同编码的字节数组的转换Java判断文件编码格式对于UTF 8格式文件的判断 xff1a 利用cpdetector开源库确
  • GBK编码表

    全国信息技术标准化技术委员会 汉字内码扩展规范 GBK Chinese Internal Code Specification 1 0 版 xff08 按编码顺序排列 xff09 其编码范围 xff1a 8140 xff0d FEFE xf
  • dll文件下载网址

    https cn dll files com
  • windows中dos命令汇总及获取管理员权限

    文章目录 windows 获取管理员权限的2种方式runas 用法 windows dos 命令行语法项windows dos命令总述 windows dos命令详细介绍 win7及以前 微软官网 windows dos命令详细介绍 win
  • windows比cmd更强大的 WMIC命令使用详解

    文章目录 什么是wmic WMIC能做什么 WMIC命令使用帮助文档WMIC命令使用实例wmic的运行方式可以有两种法1 显示进程的详细信息2 停止 暂停和运行服务功能3 显示出BIOS信息4 停止进程的操作5 连接远程电脑6 BIOS 基
  • 编程意识——宏定义封装多个函数参数

    作者 釜薪君 公众号 嵌入式杂牌军 文章目录 前言一 这种意识的来源二 实现源码分析1 函数调用2 宏定义部分3 函数实现4 宏替换后的函数调用 总结 前言 今天带小伙伴们分析一段不错的代码 xff0c 学习一下关于宏封装的一种意识 xff
  • DSP28335的SCI的FIFO中断使用心得

    自学了一段时间的DSP28335的串口设置 xff0c 写下来帮助更多的新手 xff0c 遇到了很多问题也记录一些解决办法 以下全都是我个人的理解 xff0c 可能说的不对 xff0c 大家讨论 1 关于为什么必须用FIFO 一般的DSP系
  • 51单片机堆栈深入剖析

    用C语言进行MCS51系列单片机程序设计是单片机开发和应用的必然趋势 Keil公司的C51编译器支持经典8051和8051派生产品的版本 xff0c 通称为Cx51 应该说 xff0c Cx51是C语言在MCS51单片机上的扩展 xff0c
  • 基于ros_arduino_bridge的智能小车----上位机篇

    基于ros arduino bridge的智能小车 上位机篇 基于ros arduino bridge的智能小车 硬件篇 基于ros arduino bridge的智能小车 下位机篇 ros arduino bridge文件系统 xff08
  • 基于ros_arduino_bridge的智能小车----下位机篇

    基于ros arduino bridge的智能小车 下位机篇 参考文章 xff1a 基于ros arduino bridge的智能小车 上位机篇 基于ros arduino bridge的智能小车 硬件篇 下位机部分实际上可以视作完全独立的
  • 【命令】Python执行命令超时控制【原创】

    目录 参考 概要 方案 方案一 xff1a os system 方案二 xff1a os popen 方案三 xff1a subprocess check output 方案四 xff1a subprocess Popen 方案五 xff1
  • nRF52 Mesh开发 (2) SDK例程Light_switch server 添加一个element控制开发板其他LED灯

    server文件结构 xff1a 使用SEGGER编译的话直接打开 emProject文件即可 xff1b img文件中包含程序运行过程图 xff1b include文件包含该例程下的头文件 xff1b 2 具体操作 xff1a 在main
  • nRF52 Mesh开发 (3) MESH Sensor Server/Client Models详解与实现

    MESH Sensor Model 实现 MESH Spec规定的 Sensor Model 标准传感器状态传感器描述传感器参数设置传感器cadence传感器数据 传感器可发送和接收的消息Sensor Server Client Model
  • Telink Mesh 开发(1)调试log打印

    Telink Mesh SDK 调试log打印 Telink 官网论坛建议使用GPIO模拟串口打印log xff0c 推荐阅读Telink官网发布的最新SDK使用手册 xff0c 更新了不少东西 一 使用串口打印log1 使能uart lo
  • 蓝牙Mesh基础(3)蓝牙Mesh协议--总览

    蓝牙Mesh协议 总览Bearer Layer xff08 承载层 xff09 Network Layer xff08 网络层 xff09 Low Transport Layer xff08 下层传输层 xff09 Upper Transp
  • 蓝牙Mesh基础(9)设备配网

    设备配网 xff08 启动配置 xff09 设备配网过程配网PDU配网PDU如何传输呢 设备配网过程 首先 xff0c 需要配网的设备先进行未配网广播 xff0c 这个广播不同于普通的ble广播 xff0c 广播数据结构类型 xff08 A
  • 弱网络环境模拟--树莓派搭建ATC

    弱网络环境模拟 树莓派搭建ATC 1 硬件和系统2 搭建过程3 遇到的问题1 Failed to start hostapd service Unit hostapd service is masked2 django python版本问题
  • OpenCV双目相机测距程序

    本文主要分享一个双目测距的实现程序 xff0c 用的bumblebee2相机 使用的OpenCV自带的BM算法 在OpenCV3中 xff0c StereoBM算法发生了比较大的变化 xff0c StereoBM被定义为纯虚类 xff0c
  • stm32 printf 串口输出

    在使用STM32调试时 xff0c 经常使用串口发送信息 xff0c 为了方便调试与串口发送信息 xff0c 用printf xff08 xff09 函数实现通过串口打印信息 1 添加包含printf xff08 xff09 函数的头文件
  • 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

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