当前位置: 首页 > news >正文

【sylar-webserver】10 HTTP模块

HTTP 解析

这里使用 nodejs/http-parser 提供的 HTTP 解析器。

HTTP 常量定义

HttpMethod HttpStatus

/* Request Methods */
#define HTTP_METHOD_MAP(XX)         \XX(0,  DELETE,      DELETE)       \XX(1,  GET,         GET)          \XX(2,  HEAD,        HEAD)         \XX(3,  POST,        POST)         \XX(4,  PUT,         PUT)          \
.../* Status Codes */
#define HTTP_STATUS_MAP(XX)                                                 \XX(100, CONTINUE,                        Continue)                        \XX(101, SWITCHING_PROTOCOLS,             Switching Protocols)             \XX(102, PROCESSING,                      Processing)                      \XX(200, OK,                              OK)                              \XX(201, CREATED,                         Created)                         \XX(202, ACCEPTED,                        Accepted)                        \XX(203, NON_AUTHORITATIVE_INFORMATION,   Non-Authoritative Information)   \
...

看来宏定义里 XX 常表示 宏定义函数,开源代码也是这样设计。

/*** @brief HTTP方法枚举*/
enum class HttpMethod {
#define XX(num, name, string) name = num,HTTP_METHOD_MAP(XX)
#undef XXINVALID_METHOD
};/*** @brief HTTP状态枚举*/
enum class HttpStatus {
#define XX(code, name, desc) name = code,HTTP_STATUS_MAP(XX)
#undef XX
};

HTTP 请求和响应结构

HTTP请求和响应的格式可参考HTTP消息 - HTTP | MDN

请求:

  1. 起始行
  2. 标头 Header
  3. 主体 Body

响应:

  1. 状态行
  2. 标头 Header
  3. 主体 Body

HttpRequest 类的部分成员变量
  • m_method: 表示 HTTP 请求的方法,如 GET、POST 等。
  • m_version: 表示 HTTP 协议的版本,例如 0x11 代表 HTTP/1.1。
  • m_close: 表示是否自动关闭连接,true 表示关闭,false 表示保持连接。
  • m_websocket: 表示是否为 WebSocket 请求。
  • m_parserParamFlag: 是一个标志位,用于记录参数解析的状态。每一位代表不同的解析状态,0x1 表示已解析 URL 参数,0x2 表示已解析 HTTP 消息体中的参数,0x4 表示已解析 Cookies。
  • m_url: 存储请求的完整 URL。
  • m_path: 存储请求的路径部分。
  • m_query: 存储请求的查询参数部分。
  • m_fragment: 存储请求的 Fragment 部分。
  • m_body: 存储请求的消息体。
  • m_headers: 是一个 MapType 类型的映射,用于存储请求的头部信息,键为头部字段名,值为头部字段值。
  • m_params: 是一个 MapType 类型的映射,用于存储请求的参数信息,键为参数名,值为参数值。
  • m_cookies: 是一个 MapType 类型的映射,用于存储请求的 Cookies 信息,键为 Cookie 名,值为 Cookie 值。
initQueryParam
void HttpRequest::initQueryParam() {if (m_parserParamFlag & 0x1) {return;}#define PARSE_PARAM(str, m, flag, trim)                                                                    \size_t pos = 0;                                                                                        \do {                                                                                                   \size_t last = pos;                                                                                 \pos         = str.find('=', pos);                                                                  \if (pos == std::string::npos) {                                                                    \break;                                                                                         \}                                                                                                  \size_t key = pos;                                                                                  \pos        = str.find(flag, pos);                                                                  \\if (0) {                                                                                           \std::cout << "<key>:" << str.substr(last, key - last)                                          \<< " <decoded>:" << sylar::StringUtil::UrlDecode(str.substr(last, key - last))       \<< " <value>:" << str.substr(key + 1, pos - key - 1)                                 \<< " <decoded>:" << sylar::StringUtil::UrlDecode(str.substr(key + 1, pos - key - 1)) \<< std::endl;                                                                        \}                                                                                                  \\m.insert(std::make_pair(sylar::StringUtil::UrlDecode(trim(str.substr(last, key - last))),          \sylar::StringUtil::UrlDecode(str.substr(key + 1, pos - key - 1))));        \if (pos == std::string::npos) {                                                                    \break;                                                                                         \}                                                                                                  \++pos;                                                                                             \} while (true);PARSE_PARAM(m_query, m_params, '&', );m_parserParamFlag |= 0x1;
}

从 m_query 解析,查询参数按照 & 分割,每个参数再按 = 分割成键值对。

对键和值进行 URL 编码,并插入到 m_params 中。

initBodyParam
void HttpRequest::initBodyParam() {if (m_parserParamFlag & 0x2) {return;}std::string content_type = getHeader("content-type");if (strcasestr(content_type.c_str(), "application/x-www-form-urlencoded") == nullptr) {m_parserParamFlag |= 0x2;return;}PARSE_PARAM(m_body, m_params, '&', );m_parserParamFlag |= 0x2;
}

从 m_headers 里拿到 Content-Type 字段,application/x-www-form-urlencoded 类型

m_body,消息体参数按 & 分割,每个参数再按 = 分割成键值对。

对键和值进行 URL 编码,并插入到 m_params 中。

initCookies
void HttpRequest::initCookies() {if (m_parserParamFlag & 0x4) {return;}std::string cookie = getHeader("cookie");if (cookie.empty()) {m_parserParamFlag |= 0x4;return;}PARSE_PARAM(cookie, m_cookies, ';', sylar::StringUtil::Trim);m_parserParamFlag |= 0x4;
}

从 m_headers 里拿到 cookie 字段

m_body,消息体参数按 ; 分割,每个参数再按 = 分割成键值对。

对键和值进行 URL 编码,并插入到 m_cookies 中。

上面的 m_path,m_query,m_fragment,m_headers(MapType),m_version,m_method,m_body 参数是 Http_parser 时调用回调添加的,详细见下一部分。

解析案例

GET /search?keyword=apple&category=fruit#product1 HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: en-US,en;q=0.9
Cookie: session_id=123456; user=testuser
Connection: keep-alive

这个案例里 m_body 里没有 content-type

不同的 Content-Type 意味着消息体的格式和解析方式不同。例如:⭐

  • application/x-www-form-urlencoded:消息体是由 & 分隔的键值对,如 key1=value1&key2=value2
  • multipart/form-data:常用于文件上传,消息体的格式更为复杂,包含多个部分,每个部分有自己的头部和数据。
  • application/json:消息体是 JSON 格式的数据。

HttpRequest 类成员变量对应说明
  • m_method: 代表 HTTP 请求的方法,在这个例子中是 GET
  • m_version: 代表 HTTP 协议的版本,这里是 0x11,即 HTTP/1.1。
  • m_close: 根据 Connection 头部字段判断是否自动关闭连接,由于这里是 keep - alive,所以 m_closefalse
  • m_websocket: 此请求不是 WebSocket 请求,所以 m_websocketfalse
  • m_url: 请求的完整 URL,即 /search?keyword=apple&category=fruit#product1
  • m_path: 请求路径,为 /search
  • m_query: 请求参数,是 keyword=apple&category=fruit
  • m_fragment: 请求的 Fragment 部分,是 product1
  • m_body: 由于是 GET 请求,通常没有消息体,所以 m_body 为空字符串。
  • m_headers: 存储请求的头部信息,是一个映射,键值对如下:
    • "Host": "www.example.com"
    • "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
    • "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"
    • "Accept-Language": "en-US,en;q=0.9"
    • "Cookie": "session_id=123456; user=testuser"
    • "Connection": "keep-alive"
  • m_params: 在调用 initQueryParam 方法后,会存储解析后的查询参数,键值对为:
    • "keyword": "apple"
    • "category": "fruit"
  • m_cookies: 在调用 initCookies 方法后,会存储解析后的 Cookie 信息,键值对为:
    • "session_id": "123456"
    • "user": "testuser"

HTTP/1.1 200 OK
Date: Tue, 15 Jun 2023 12:00:00 GMT
Server: Apache/2.4.41 (Ubuntu)
Content-Type: text/html; charset=UTF-8
Content-Length: 1234
Connection: keep-alive<!DOCTYPE html>
<html>
<head><title>Search Results</title>
</head>
<body><h1>Search Results for Apple in Fruit Category</h1><!-- 其他HTML内容 -->
</body>
</html>
HttpResponse 类成员变量对应说明
  • m_status: 代表响应状态,这里是 200,即 HttpStatus::OK
  • m_version: 代表 HTTP 协议的版本,为 0x11,即 HTTP/1.1。
  • m_close: 根据 Connection 头部字段判断是否自动关闭连接,由于是 keep - alive,所以 m_closefalse
  • m_websocket: 此响应不是 WebSocket 响应,所以 m_websocketfalse
  • m_body: 响应消息体,是 HTML 内容:
<!DOCTYPE html>
<html>
<head><title>Search Results</title>
</head>
<body><h1>Search Results for Apple in Fruit Category</h1><!-- 其他HTML内容 -->
</body>
</html>
  • m_reason: 响应原因,是 OK
  • m_headers: 存储响应的头部信息,是一个映射,键值对如下:
    • "Date": "Tue, 15 Jun 2023 12:00:00 GMT"
    • "Server": "Apache/2.4.41 (Ubuntu)"
    • "Content-Type": "text/html; charset=UTF-8"
    • "Content-Length": "1234"
    • "Connection": "keep-alive"
  • m_cookies: 此响应中没有设置 Cookie,所以 m_cookies 为空。

HTTP 解析器

struct http_parser {/** PRIVATE **/unsigned int type : 2;         /* enum http_parser_type */unsigned int flags : 8;        /* F_* values from 'flags' enum; semi-public */unsigned int state : 7;        /* enum state from http_parser.c */unsigned int header_state : 7; /* enum header_state from http_parser.c */unsigned int index : 7;        /* index into current matcher */unsigned int lenient_http_headers : 1;uint32_t nread;          /* # bytes read in various scenarios */uint64_t content_length; /* # bytes in body (0 if no Content-Length header) *//** READ-ONLY **/unsigned short http_major;unsigned short http_minor;unsigned int status_code : 16; /* responses only */unsigned int method : 8;       /* requests only */unsigned int http_errno : 7;/* 1 = Upgrade header was present and the parser has exited because of that.* 0 = No upgrade header present.* Should be checked when http_parser_execute() returns in addition to* error checking.*/unsigned int upgrade : 1;/** PUBLIC **/void *data; /* A pointer to get hook to the "connection" or "socket" object */
};
HttpRequestParser::HttpRequestParser()   --->   http_parser_init(&m_parser, HTTP_REQUEST); HTTP_REQUEST,HTTP_RESPONSE指定解析的m_parser类型。HttpRequestParser::execute(char *data, size_t len)	---> 	http_parser_execute(&m_parser, &s_request_settings, data, len);// 设定解析中调用 回调函数,将参数保存到 m_data 中。
static http_parser_settings s_request_settings ={.on_message_begin    = on_request_message_begin_cb,.on_url              = on_request_url_cb,.on_status           = on_request_status_cb,.on_header_field     = on_request_header_field_cb,.on_header_value     = on_request_header_value_cb,.on_headers_complete = on_request_headers_complete_cb,.on_body             = on_request_body_cb,.on_message_complete = on_request_message_complete_cb,.on_chunk_header     = on_request_chunk_header_cb,.on_chunk_complete   = on_request_chunk_complete_cb
};

HttpSession

继承自 SocketStream,实现了在套接字流上读取HTTP请求与发送HTTP响应的功能,在读取HTTP请求时需要借助HTTP解析器,以便于将套接字流上的内容解析成HTTP请求。

HttpServer

继承自TcpServer,重载handleClient方法,将accept后得到的客户端套接字封装成HttpSession结构,以便于接收和发送HTTP消息。

HttpServlet

HTTP客户端HttpConnection

相关文章:

  • SSL/TLS证书申请与管理技术指南
  • LLM推理加速技术如何迁移到传统 Transformer 模型(ASR)
  • Ubuntu nginx 配置 SSL 证书支持 https 请求
  • 【2025-05-22】centos 离线安装兼容node和npm版本的pm2 和 yarn
  • 《深度掌控Linux:openEuler、CentOS、Debian、Ubuntu的全方位运维指南》
  • 【2025-05-22】XXL-JOB 的 8810 端口添加到 CentOS 6.5 的防火墙白名单
  • Dify-4:API 后端架构
  • 开源无界 智联欧陆——仓颉计划携手OpenHarmony共绘万物互联新图景
  • 将 Docker 镜像推送到 GitLab Container Registry 的完整步骤
  • Java 调用 GitLab API
  • OpenHarmony外设驱动使用 (十二),User_auth
  • ubuntu20.04vscode使用C++20(调整gcc版本vscode设置)
  • day 33简单的神经网络
  • GitLab 备份所有仓库(自动克隆)
  • Spring Boot 内置工具类汇总与讲解
  • 人民日报社主管媒体深度聚焦珈和科技“遥感+AI”农险精准化突破:首创“四维数据贯通”模式 树行业转型新标杆
  • 塔能科技:工厂能耗精准节能全方位解决方案
  • 【C++】位图+布隆过滤器
  • LangFlow可视化Agent编排
  • HarmonyOS优化应用文件上传下载慢问题性能优化二
  • 产品展示类网站/外贸网站推广费用
  • 手机微网站建设案例及报告/郑州网络推广专业公司
  • 怎样做网站分析总结/全网自媒体平台大全
  • 建设网站实训报告书/怎么做百度推广运营
  • 专业定制网站建设/百度网址大全 简单版
  • 巢湖城市建设投资有限公司网站/2345手机浏览器