
⭐️在这个怀疑的年代,我们依然需要信仰。
个人主页 :YYYing.
⭐️C++大型项目系列专栏:C++大型项目之高性能服务器框架
系列上期内容: 【C++项目之高性能服务器框架 (八) 】Stream流和TcpServer模块
系列下期内容:暂无
目录
[三、Message 消息基类详解](#三、Message 消息基类详解)
[3.1 定义(protocol.h)](#3.1 定义(protocol.h))
[3.2 toByteArray() 实现(protocol.cc)](#3.2 toByteArray() 实现(protocol.cc))
[四、MessageDecoder 编解码器接口详解](#四、MessageDecoder 编解码器接口详解)
[4.1 定义(protocol.h)](#4.1 定义(protocol.h))
[五、Request 请求消息详解](#五、Request 请求消息详解)
[5.1 定义(protocol.h)](#5.1 定义(protocol.h))
[5.2 构造函数(protocol.cc)](#5.2 构造函数(protocol.cc))
[5.3 serializeToByteArray()(protocol.cc)](#5.3 serializeToByteArray()(protocol.cc))
[5.4 parseFromByteArray()(protocol.cc)](#5.4 parseFromByteArray()(protocol.cc))
[六、Response 响应消息详解](#六、Response 响应消息详解)
[6.1 定义(protocol.h)](#6.1 定义(protocol.h))
[6.2 构造函数(protocol.cc)](#6.2 构造函数(protocol.cc))
[6.3 serializeToByteArray()(protocol.cc)](#6.3 serializeToByteArray()(protocol.cc))
[6.4 parseFromByteArray()(protocol.cc)](#6.4 parseFromByteArray()(protocol.cc))
[七、Notify 通知消息详解](#七、Notify 通知消息详解)
[7.1 定义(protocol.h)](#7.1 定义(protocol.h))
[7.2 构造函数(protocol.cc)](#7.2 构造函数(protocol.cc))
[7.3 serializeToByteArray()(protocol.cc)](#7.3 serializeToByteArray()(protocol.cc))
[7.4 parseFromByteArray()(protocol.cc)](#7.4 parseFromByteArray()(protocol.cc))
[三、HTTP 数据结构详解(http.h)](#三、HTTP 数据结构详解(http.h))
[3.1 HTTP 方法枚举(http.h)](#3.1 HTTP 方法枚举(http.h))
[3.2 HTTP 状态码枚举(http.h)](#3.2 HTTP 状态码枚举(http.h))
[3.3 字符串转换函数(http.cc)](#3.3 字符串转换函数(http.cc))
[3.4 CaseInsensitiveLess(http.h/http.cc)](#3.4 CaseInsensitiveLess(http.h/http.cc))
[四、HttpRequest 请求类详解](#四、HttpRequest 请求类详解)
[4.1 定义(http.h)](#4.1 定义(http.h))
[4.2 构造函数(http.cc)](#4.2 构造函数(http.cc))
[4.3 createResponse()(http.cc)](#4.3 createResponse()(http.cc))
[4.4 dump() 序列化(http.cc)](#4.4 dump() 序列化(http.cc))
[4.5 参数懒加载(http.cc)](#4.5 参数懒加载(http.cc))
[五、HttpResponse 响应类详解](#五、HttpResponse 响应类详解)
[5.1 构造函数(http.cc)](#5.1 构造函数(http.cc))
[5.2 setRedirect()(http.cc)](#5.2 setRedirect()(http.cc))
[5.3 setCookie()(http.cc)](#5.3 setCookie()(http.cc))
[5.4 dump() 序列化(http.cc)](#5.4 dump() 序列化(http.cc))
[六、HttpRequestParser 解析器详解](#六、HttpRequestParser 解析器详解)
[6.1 定义(http_parser.h)](#6.1 定义(http_parser.h))
[6.2 execute() 原理](#6.2 execute() 原理)
[七、HttpSession 服务端会话详解](#七、HttpSession 服务端会话详解)
[7.1 定义(http_session.h)](#7.1 定义(http_session.h))
[7.2 recvRequest()(http_session.cc)](#7.2 recvRequest()(http_session.cc))
[7.3 sendResponse()(http_session.cc)](#7.3 sendResponse()(http_session.cc))
[八、Servlet 路由层详解](#八、Servlet 路由层详解)
[8.1 Servlet 基类(servlet.h)](#8.1 Servlet 基类(servlet.h))
[8.2 FunctionServlet(servlet.h)](#8.2 FunctionServlet(servlet.h))
[8.3 ServletDispatch 分发器(servlet.h)](#8.3 ServletDispatch 分发器(servlet.h))
[8.4 getMatchedServlet()(servlet.cc)](#8.4 getMatchedServlet()(servlet.cc))
[8.5 IServletCreator 与对象生命周期(servlet.h)](#8.5 IServletCreator 与对象生命周期(servlet.h))
[九、HttpServer 详解](#九、HttpServer 详解)
[9.1 定义(http_server.h)](#9.1 定义(http_server.h))
[9.2 构造函数(http_server.cc)](#9.2 构造函数(http_server.cc))
[9.3 setName()(http_server.cc)](#9.3 setName()(http_server.cc))
[9.4 handleClient()(http_server.cc)](#9.4 handleClient()(http_server.cc))
[十、HttpConnection 客户端连接详解](#十、HttpConnection 客户端连接详解)
[10.1 定义(http_connection.h)](#10.1 定义(http_connection.h))
[10.2 HttpConnectionPool 连接池(http_connection.h)](#10.2 HttpConnectionPool 连接池(http_connection.h))
[服务端处理一次 HTTP 请求的完整流程](#服务端处理一次 HTTP 请求的完整流程)
[客户端发起一次 HTTP GET 的完整流程](#客户端发起一次 HTTP GET 的完整流程)

前言:
上一篇我们学完了Stream流和TcpServer模块,我们提供了字节流接口,并将套接字封装了流接口,最后我们封装了TcpServer,那么这一篇我们将会开始最后几个模块的学习,话不多说,我们开始。
协议抽象
一、设计与目的
为什么要做协议抽象?
在传统的网络编程中,协议与传输层通常是紧耦合的:
-
HTTP 服务器只能处理 HTTP 协议,无法直接处理私有二进制协议。
-
每种协议都要重复实现「从 Socket 读取 → 解析 → 构造消息对象」的流程。
-
协议升级或替换时,需要改动大量业务代码。
Protocol 抽象层如何解决这些问题?
| 传统问题 | Protocol 抽象层的解决方案 |
|---|---|
| 协议与传输层紧耦合 | MessageDecoder 接口隔离编解码与传输,Stream 只负责字节流传输 |
| 每种协议重复解析逻辑 | 基类统一接口(serializeTo / parseFrom),具体协议只需实现算法 |
| 消息类型混乱 | Request / Response / Notify 语义清晰,通信模式一目了然 |
| 协议切换困难 | 替换 Protocol 具体实现即可,业务代码基于 Message::ptr 操作,无需改动 |
面向接口编程的应用
cpp
// 业务代码只依赖抽象接口,不依赖具体协议
void handleMessage(Stream::ptr stream, MessageDecoder::ptr decoder) {
Message::ptr msg = decoder->parseFrom(stream); // 解耦!
if(msg->getType() == Message::REQUEST) {
// 处理请求...
}
}
-
MessageDecoder是抽象策略 ,RockProtocol/HttpProtocol是具体策略。 -
这是经典的策略模式(Strategy Pattern)。
二、整体架构

设计意图:
-
Protocol是通用协议抽象基类,定义了所有协议的统一编解码接口。 -
Message是通用消息基类,定义了所有消息的公共行为(序列化、反序列化、转字符串)。 -
Request/Response/Notify是消息类型的进一步细分,分别对应请求、响应、通知三种通信模式。 -
分离后,不同协议(Rock、HTTP、Protobuf)可以共用同一套传输层(Stream、TcpServer),只需替换 Protocol 实现即可。
三、Message 消息基类详解
cpp
class Message {
public:
typedef std::shared_ptr<Message> ptr;
enum MessageType {
REQUEST = 1,
RESPONSE = 2,
NOTIFY = 3
};
virtual ~Message() {}
virtual ByteArray::ptr toByteArray();
virtual bool serializeToByteArray(ByteArray::ptr bytearray) = 0;
virtual bool parseFromByteArray(ByteArray::ptr bytearray) = 0;
virtual std::string toString() const = 0;
virtual const std::string& getName() const = 0;
virtual int32_t getType() const = 0;
};
3.1 定义(protocol.h)
成员/方法表:
| 名称 | 类型 | 说明 |
|---|---|---|
ptr |
shared_ptr<Message> |
智能指针类型别名,避免裸指针 |
MessageType |
enum |
消息类型枚举:REQUEST(1)、RESPONSE(2)、NOTIFY(3) |
toByteArray() |
ByteArray::ptr |
便捷方法:先 new ByteArray,再调用 serializeToByteArray |
serializeToByteArray |
bool |
纯虚函数:子类必须实现,将消息对象序列化为二进制字节流 |
parseFromByteArray |
bool |
纯虚函数:子类必须实现,从二进制字节流反序列化为消息对象 |
toString() |
std::string |
纯虚函数:子类实现,用于调试日志输出 |
getName() |
const std::string& |
纯虚函数:返回消息/协议名称 |
getType() |
int32_t |
纯虚函数:返回消息类型(REQUEST/RESPONSE/NOTIFY) |
设计要点:
-
所有方法都是
virtual,保证多态行为。 -
serializeToByteArray/parseFromByteArray是纯虚函数,强制子类实现编解码逻辑。 -
toByteArray()是模板方法:它定义了标准流程(创建 ByteArray → 调用 serialize),子类只需实现 serialize。
3.2 toByteArray() 实现(protocol.cc)
cpp
ByteArray::ptr Message::toByteArray() {
ByteArray::ptr ba(new ByteArray);
if(serializeToByteArray(ba)) {
return ba;
}
return nullptr;
}
逐行拆解:
-
ByteArray::ptr ba(new ByteArray);:在堆上创建一个新的 ByteArray 对象,用于存放序列化后的二进制数据。 -
if(serializeToByteArray(ba)):调用子类实现的纯虚函数,将当前消息对象写入 ByteArray。 -
return ba;:序列化成功,返回包含二进制数据的 ByteArray。 -
return nullptr;:序列化失败(如字段校验不通过),返回空指针。
为什么设计这个方法?
-
业务代码通常只需要「把消息转成二进制」,不关心 ByteArray 的创建细节。
-
统一入口便于后期扩展(如在 toByteArray 中加入压缩、加密等预处理)。
四、MessageDecoder 编解码器接口详解
4.1 定义(protocol.h)
cpp
class MessageDecoder {
public:
typedef std::shared_ptr<MessageDecoder> ptr;
virtual ~MessageDecoder() {}
virtual Message::ptr parseFrom(Stream::ptr stream) = 0;
virtual int32_t serializeTo(Stream::ptr stream, Message::ptr msg) = 0;
};
设计意图:
-
MessageDecoder是更高层的抽象 ,直接面向Stream操作。 -
parseFrom(Stream):从流中读取原始字节,解析为Message对象。 -
serializeTo(Stream, Message):将Message对象序列化后写入流。 -
与
Message::serializeToByteArray的区别:MessageDecoder负责协议级别的控制 (如处理消息头、校验和、分包粘包),而Message只负责消息体内容的序列化。
典型使用场景:
cpp
// 服务端接收消息
MessageDecoder::ptr decoder = std::make_shared<RockProtocol>();
Message::ptr msg = decoder->parseFrom(socket_stream);
// 服务端发送消息
decoder->serializeTo(socket_stream, response_msg);
五、Request 请求消息详解
5.1 定义(protocol.h)
cpp
class Request : public Message {
public:
typedef std::shared_ptr<Request> ptr;
Request();
uint32_t getSn() const { return m_sn;}
uint32_t getCmd() const { return m_cmd;}
void setSn(uint32_t v) { m_sn = v;}
void setCmd(uint32_t v) { m_cmd = v;}
virtual bool serializeToByteArray(ByteArray::ptr bytearray) override;
virtual bool parseFromByteArray(ByteArray::ptr bytearray) override;
protected:
uint32_t m_sn;
uint32_t m_cmd;
};
成员变量表:
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_sn |
uint32_t |
0 |
Sequence Number,请求序列号,用于匹配请求与响应 |
m_cmd |
uint32_t |
0 |
Command ID,命令标识,区分不同的业务接口 |
为什么是 protected?
Request是基类, RockRequest 等具体请求会继承它。保护成员允许子类直接访问,同时阻止外部随意修改内部状态。
5.2 构造函数(protocol.cc)
cpp
Request::Request()
:m_sn(0)
,m_cmd(0) {
}
-
m_sn初始化为 0,表示尚未分配序列号。实际使用时由发送方生成递增的唯一 ID。 -
m_cmd初始化为 0,表示尚未设置命令类型。子类构造函数通常会覆盖这个值。
5.3 serializeToByteArray()(protocol.cc)
cpp
bool Request::serializeToByteArray(ByteArray::ptr bytearray) {
bytearray->writeFuint8(getType());
bytearray->writeUint32(m_sn);
bytearray->writeUint32(m_cmd);
return true;
}
逐行拆解:
-
writeFuint8(getType()):先写入 1 字节的消息类型。getType()由子类实现(如 RockRequest 返回REQUEST = 1)。 -
writeUint32(m_sn):写入 4 字节序列号(网络字节序,由 ByteArray 内部处理)。 -
writeUint32(m_cmd):写入 4 字节命令号。 -
return true;:Request 基类无复杂校验,固定返回成功。
二进制格式:
bash
┌────────┬────────────┬────────────┐
│ Type │ SN │ Cmd │
│ 1 byte │ 4 bytes │ 4 bytes │
│ 0x01 │ 0x000003E9 │ 0x00000001 │
└────────┴────────────┴────────────┘
5.4 parseFromByteArray()(protocol.cc)
cpp
bool Request::parseFromByteArray(ByteArray::ptr bytearray) {
m_sn = bytearray->readUint32();
m_cmd = bytearray->readUint32();
return true;
}
注意:
-
这里没有读取 Type 字段,因为 Type 通常在更高层的 Protocol 解析器中先读取,用于决定创建 Request / Response / Notify 中的哪一种。
-
readUint32()会自动处理字节序转换(ByteArray 内部使用网络字节序)。
六、Response 响应消息详解
6.1 定义(protocol.h)
cpp
class Response : public Message {
public:
typedef std::shared_ptr<Response> ptr;
Response();
uint32_t getSn() const { return m_sn;}
uint32_t getCmd() const { return m_cmd;}
uint32_t getResult() const { return m_result;}
const std::string& getResultStr() const { return m_resultStr;}
void setSn(uint32_t v) { m_sn = v;}
void setCmd(uint32_t v) { m_cmd = v;}
void setResult(uint32_t v) { m_result = v;}
void setResultStr(const std::string& v) { m_resultStr = v;}
virtual bool serializeToByteArray(ByteArray::ptr bytearray) override;
virtual bool parseFromByteArray(ByteArray::ptr bytearray) override;
protected:
uint32_t m_sn;
uint32_t m_cmd;
uint32_t m_result;
std::string m_resultStr;
};
成员变量表:
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_sn |
uint32_t |
0 |
序列号,与对应的 Request 保持一致 |
m_cmd |
uint32_t |
0 |
命令号,与对应的 Request 保持一致 |
m_result |
uint32_t |
404 |
业务结果码,0 通常表示成功 |
m_resultStr |
std::string |
"unhandle" |
结果描述字符串,用于调试 |
为什么是 404 / "unhandle"?
- 默认值暗示「尚未被处理」。如果服务端忘记设置 result,客户端收到 404 能快速发现问题。
6.2 构造函数(protocol.cc)
cpp
Response::Response()
:m_sn(0)
,m_cmd(0)
,m_result(404)
,m_resultStr("unhandle") {
}
6.3 serializeToByteArray()(protocol.cc)
cpp
bool Response::serializeToByteArray(ByteArray::ptr bytearray) {
bytearray->writeFuint8(getType());
bytearray->writeUint32(m_sn);
bytearray->writeUint32(m_cmd);
bytearray->writeUint32(m_result);
bytearray->writeStringVint(m_resultStr);
return true;
}
新增字段解析:
-
writeUint32(m_result):4 字节结果码。 -
writeStringVint(m_resultStr):使用 Vint 变长编码写入字符串。先写字符串长度(Vint 压缩),再写字符串内容。比固定长度更省空间。
6.4 parseFromByteArray()(protocol.cc)
cpp
bool Response::parseFromByteArray(ByteArray::ptr bytearray) {
m_sn = bytearray->readUint32();
m_cmd = bytearray->readUint32();
m_result = bytearray->readUint32();
m_resultStr = bytearray->readStringVint();
return true;
}
读取顺序必须与写入顺序完全一致,这是二进制协议的基本约束。
七、Notify 通知消息详解
7.1 定义(protocol.h)
cpp
class Notify : public Message {
public:
typedef std::shared_ptr<Notify> ptr;
Notify();
uint32_t getNotify() const { return m_notify;}
void setNotify(uint32_t v) { m_notify = v;}
virtual bool serializeToByteArray(ByteArray::ptr bytearray) override;
virtual bool parseFromByteArray(ByteArray::ptr bytearray) override;
protected:
uint32_t m_notify;
};
成员变量表:
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_notify |
uint32_t |
0 |
通知类型标识 |
Notify vs Request/Response:
-
Request/Response是一问一答模式,客户端发 Request,服务端回 Response。 -
Notify是单向推送模式,服务端主动推送给客户端,无需响应。 -
典型场景:聊天消息推送、状态变更通知、广播。
7.2 构造函数(protocol.cc)
cpp
Notify::Notify()
:m_notify(0) {
}
7.3 serializeToByteArray()(protocol.cc)
cpp
bool Notify::serializeToByteArray(ByteArray::ptr bytearray) {
bytearray->writeFuint8(getType());
bytearray->writeUint32(m_notify);
return true;
}
7.4 parseFromByteArray()(protocol.cc)
cpp
bool Notify::parseFromByteArray(ByteArray::ptr bytearray) {
m_notify = bytearray->readUint32();
return true;
}
八、继承关系与类图
bash
Message(抽象基类)
│
┌─────────────────┼─────────────────┐
│ │ │
Request Response Notify
│ │ │
RockRequest RockResponse RockNotify
(具体实现) (具体实现) (具体实现)
设计模式:模板方法模式
-
Message::toByteArray()是模板方法:定义了「创建 ByteArray → 调用 serialize」的标准流程。 -
serializeToByteArray()是扩展点:由子类实现具体的字段写入逻辑。
九、完整调用链梳理
以 Rock 协议发送一个请求为例:
步骤 1:构造请求对象
cpp
RockRequest::ptr req(new RockRequest);
req->setSn(1001);
req->setCmd(1);
req->setBody("hello");
步骤 2:序列化为 ByteArray
cpp
ByteArray::ptr ba = req->toByteArray();
// 内部调用:
// 1. new ByteArray
// 2. req->serializeToByteArray(ba)
// - writeFuint8(REQUEST = 1)
// - writeUint32(1001) // sn
// - writeUint32(1) // cmd
// - writeStringVint("hello") // body(RockRequest 扩展)
步骤 3:通过 Stream 发送
cpp
stream->writeFixSize(ba->getData(), ba->getSize());
// → 经过 Hook 后的 Socket::send → 内核 TCP 发送缓冲区
步骤 4:服务端接收并解析
cpp
// RockProtocol::parseFrom(stream)
// 1. 先读取 1 字节 Type,发现是 REQUEST
// 2. new RockRequest
// 3. req->parseFromByteArray(ba)
// - readUint32() → sn
// - readUint32() → cmd
// - readStringVint() → body
十、线程安全总结
| 对象 | 保护内容 | 说明 |
|---|---|---|
Message 子类对象 |
成员变量 | 无显式锁。单条消息通常只在一个协程中操作,无需加锁 |
ByteArray |
内部缓冲区 | ByteArray 内部有读写位置指针,非线程安全。同一线程/协程串行操作即可 |
Stream |
IO 操作 | SocketStream 的 read/write 被 Hook 后变为协程让出,由 IOManager 调度,天然线程安全 |
关键设计:
-
Protocol 层无锁设计,锁在更下层(IOManager 的任务队列、ByteArray 的跨线程共享场景)。
-
一条 Message 从
parseFrom到业务处理到serializeTo,通常都在同一个协程中完成。
十一、学习验证清单
学完后,你应该能:
- 解释
Message和MessageDecoder的分工:谁负责消息内容,谁负责协议控制? - 说明
Request/Response/Notify三种消息类型的适用场景。 - 解释
toByteArray()为什么是模板方法模式。 - 说出
serializeToByteArray和parseFromByteArray的调用顺序必须一致的原因。 - 解释
writeStringVint相比固定长度字符串的优势。 - 设计一个简化版的
JsonProtocol:实现MessageDecoder接口,用 JSON 做序列化格式。
HTTP协议栈详解
一、设计与目的
提供HTTP服务,主要包含以下几个模块:
- HTTP常量定义,包括HTTP方法
HttpMethod与HTTP状态HttpStatus。 - HTTP请求与响应结构,对应
HttpRequest和HttpResponse。 - HTTP解析器,包含HTTP请求解析器与HTTP响应解析器,对应
HttpRequestParser和HttpResponseParser。 - HTTP会话结构,对应
HttpSession。 - HTTP服务器。
- HTTP Servlet。
- HTTP客户端
HttpConnection,用于发起GET/POST等请求,支持连接池。
HTTP模块依赖nodejs/http-parser提供的HTTP解析器,并且直接复用了nodejs/http-parser中定义的HTTP方法与状态枚举。
HTTP 模块要解决什么问题?
-
协议解析复杂:HTTP 是文本协议,请求行、头部、消息体的格式各不相同,手动解析容易出错。
-
请求路由困难 :不同 URI 需要调用不同的业务函数,传统的
if/else判断难以维护。 -
连接管理繁琐:HTTP/1.1 支持 Keep-Alive,一个 TCP 连接上可能有多次请求,需要会话状态管理。
-
客户端调用麻烦 :每次发 HTTP 请求都要手动
socket() → connect() → send() → recv() → parse()。
sylar HTTP 模块的解决方案
| 问题 | 解决方案 |
|---|---|
| 协议解析复杂 | Ragel 状态机自动生成解析器,零手写解析代码 |
| 请求路由困难 | ServletDispatch 支持精准匹配、Glob 模糊匹配、默认回退 |
| 连接管理繁琐 | HttpSession 封装 Keep-Alive 循环,自动处理连接关闭 |
| 客户端调用麻烦 | HttpConnection 提供 DoGet / DoPost 静态便捷方法 |
二、整体架构

三、HTTP 数据结构详解(http.h)
3.1 HTTP 方法枚举(http.h)
cpp
#define HTTP_METHOD_MAP(XX) \
XX(0, DELETE, DELETE) \
XX(1, GET, GET) \
...
XX(33, SOURCE, SOURCE)
enum class HttpMethod {
#define XX(num, name, string) name = num,
HTTP_METHOD_MAP(XX)
#undef XX
INVALID_METHOD
};
设计要点:
-
使用 X-Macro 技巧:通过一个宏定义表生成枚举值、字符串转换函数、静态数组。
-
添加新方法只需修改
HTTP_METHOD_MAP一处,枚举、转换函数自动同步。 -
INVALID_METHOD放在#undef XX之后,作为默认值,不占用宏生成的序号。
3.2 HTTP 状态码枚举(http.h)
cpp
#define HTTP_STATUS_MAP(XX) \
XX(100, CONTINUE, Continue) \
...
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required)
enum class HttpStatus {
#define XX(code, name, desc) name = code,
HTTP_STATUS_MAP(XX)
#undef XX
};
- 同理使用 X-Macro,覆盖 1xx ~ 5xx 全量状态码。
3.3 字符串转换函数(http.cc)
cpp
HttpMethod StringToHttpMethod(const std::string& m) {
#define XX(num, name, string) \
if(strcmp(#string, m.c_str()) == 0) { \
return HttpMethod::name; \
}
HTTP_METHOD_MAP(XX);
#undef XX
return HttpMethod::INVALID_METHOD;
}
逐行拆解:
-
strcmp(#string, m.c_str()):#string是宏参数展开后的字符串字面量(如"GET"),与输入字符串比较。 -
遍历所有方法,找到匹配即返回对应枚举值。
-
无匹配返回
INVALID_METHOD。
cpp
const char* HttpMethodToString(const HttpMethod& m) {
uint32_t idx = (uint32_t)m;
if(idx >= (sizeof(s_method_string) / sizeof(s_method_string[0]))) {
return "<unknown>";
}
return s_method_string[idx];
}
设计要点:
-
利用枚举的底层整数值作为数组索引,O(1) 时间复杂度。
-
先做越界检查,防止非法枚举值导致数组越界。
3.4 CaseInsensitiveLess(http.h/http.cc)
cpp
struct CaseInsensitiveLess {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return strcasecmp(lhs.c_str(), rhs.c_str()) < 0;
}
};
为什么需要这个仿函数?
-
HTTP 协议规定 Header 的 key 是大小写不敏感 的(如
Content-Type和content-type等价)。 -
std::map默认使用std::less<std::string>,是大小写敏感的。 -
CaseInsensitiveLess使用strcasecmp做忽略大小写比较,保证m_headers["Content-Type"]和m_headers["content-type"]访问到同一项。
四、HttpRequest 请求类详解
关于HTTP请求和响应的格式可参考我之前的博客,以下是一个HTTP请求与响应的示例:
对于HTTP请求,需要关注HTTP方法,请求路径和参数,HTTP版本,HTTP头部的key-value结构,Cookies,以及HTTP Body内容。
4.1 定义(http.h)
cpp
class HttpRequest {
public:
typedef std::shared_ptr<HttpRequest> ptr;
typedef std::map<std::string, std::string, CaseInsensitiveLess> MapType;
HttpRequest(uint8_t version = 0x11, bool close = true);
std::shared_ptr<HttpResponse> createResponse();
// ... getter / setter ...
private:
HttpMethod m_method;
uint8_t m_version;
bool m_close;
bool m_websocket;
uint8_t m_parserParamFlag;
std::string m_path;
std::string m_query;
std::string m_fragment;
std::string m_body;
MapType m_headers;
MapType m_params;
MapType m_cookies;
};
成员变量表:
| 变量名 | 类型 | 默认值 | 含义 |
|---|---|---|---|
m_method |
HttpMethod |
GET |
HTTP 方法(GET/POST/PUT/DELETE...) |
m_version |
uint8_t |
0x11 |
HTTP 版本,0x11 = HTTP/1.1,0x10 = HTTP/1.0 |
m_close |
bool |
true |
连接是否关闭。HTTP/1.0 默认关闭,HTTP/1.1 默认 Keep-Alive |
m_websocket |
bool |
false |
是否为 WebSocket 升级请求 |
m_parserParamFlag |
uint8_t |
0 |
懒加载标记位:bit0=query, bit1=body, bit2=cookie |
m_path |
string |
"/" |
请求路径(如 /api/user) |
m_query |
string |
"" |
URL 查询字符串(如 ?id=1&name=sylar) |
m_fragment |
string |
"" |
URL 片段(如 #section1) |
m_body |
string |
"" |
请求消息体(POST 的表单或 JSON 数据) |
m_headers |
MapType |
空 | HTTP 头部键值对 |
m_params |
MapType |
空 | 解析后的请求参数(query + body 合并) |
m_cookies |
MapType |
空 | 解析后的 Cookie 键值对 |
4.2 构造函数(http.cc)
cpp
HttpRequest::HttpRequest(uint8_t version, bool close)
:m_method(HttpMethod::GET)
,m_version(version)
,m_close(close)
,m_websocket(false)
,m_parserParamFlag(0)
,m_path("/") {
}
-
默认方法为 GET,路径为
/,便于快速构造一个最小合法请求。 -
m_close的默认值由调用方决定。HttpServer 构造 Request 时会传入req->isClose() || !m_isKeepalive。
4.3 createResponse()(http.cc)
cpp
std::shared_ptr<HttpResponse> HttpRequest::createResponse() {
HttpResponse::ptr rsp(new HttpResponse(getVersion(), isClose()));
return rsp;
}
设计意图:
-
便捷工厂方法:根据当前请求的版本和连接状态,自动创建对应的响应对象。
-
保证 Request 和 Response 的
version/close标志一致,避免客户端收到版本不匹配的响应。
4.4 dump() 序列化(http.cc)
cpp
std::ostream& HttpRequest::dump(std::ostream& os) const {
os << HttpMethodToString(m_method) << " "
<< m_path
<< (m_query.empty() ? "" : "?")
<< m_query
<< (m_fragment.empty() ? "" : "#")
<< m_fragment
<< " HTTP/"
<< ((uint32_t)(m_version >> 4))
<< "."
<< ((uint32_t)(m_version & 0x0F))
<< "\r\n";
逐行拆解:
-
HttpMethodToString(m_method):将枚举转为字符串(如"GET")。 -
m_path:输出路径。 -
m_query.empty() ? "" : "?":有查询参数时输出?,否则跳过。 -
m_fragment同理,前缀为#。 -
m_version >> 4和m_version & 0x0F:0x11高 4 位是主版本号 1,低 4 位是次版本号 1,即 HTTP/1.1。
cpp
if(!m_websocket) {
os << "connection: " << (m_close ? "close" : "keep-alive") << "\r\n";
}
for(auto& i : m_headers) {
if(!m_websocket && strcasecmp(i.first.c_str(), "connection") == 0) {
continue;
}
os << i.first << ": " << i.second << "\r\n";
}
-
WebSocket 请求不输出
Connection头,避免与 WebSocket 握手规范冲突。 -
遍历
m_headers输出所有自定义头部,但跳过已自动处理的connection头(防止重复)。
cpp
if(!m_body.empty()) {
os << "content-length: " << m_body.size() << "\r\n\r\n"
<< m_body;
} else {
os << "\r\n";
}
return os;
}
-
有消息体时,自动计算并输出
Content-Length,然后空行\r\n\r\n,最后输出 body。 -
无消息体时,只输出一个空行
\r\n表示头部结束。
4.5 参数懒加载(http.cc)
cpp
void HttpRequest::initQueryParam() {
if(m_parserParamFlag & 0x1) {
return;
}
// ... 解析 m_query ...
m_parserParamFlag |= 0x1;
}
设计要点:
-
m_parserParamFlag是位掩码标记:-
bit 0 (
0x1):query 参数已解析 -
bit 1 (
0x2):body 参数已解析 -
bit 2 (
0x4):cookie 已解析
-
-
懒加载(Lazy Initialization) :只有调用
getParam()/getCookie()时才解析,避免不必要的字符串分割开销。 -
PARSE_PARAM宏:统一处理key=value&key2=value2格式的字符串,支持 query、body、cookie 三种场景。 -
StringUtil::UrlDecode:对参数值做 URL 解码(如%20→ 空格)。
五、HttpResponse 响应类详解
对于HTTP响应,需要关注HTTP版本,响应状态码,响应字符串,响应头部的key-value结构,以及响应的Body内容。
5.1 构造函数(http.cc)
cpp
HttpResponse::HttpResponse(uint8_t version, bool close)
:m_status(HttpStatus::OK)
,m_version(version)
,m_close(close)
,m_websocket(false) {
}
- 默认状态码为
200 OK,表示请求成功。
5.2 setRedirect()(http.cc)
cpp
void HttpResponse::setRedirect(const std::string& uri) {
m_status = HttpStatus::FOUND; // 302
setHeader("Location", uri);
}
- 便捷方法:设置 302 重定向,只需传入目标 URI。
5.3 setCookie()(http.cc)
cpp
void HttpResponse::setCookie(const std::string& key, const std::string& val,
time_t expired, const std::string& path,
const std::string& domain, bool secure) {
std::stringstream ss;
ss << key << "=" << val;
if(expired > 0) {
ss << ";expires=" << sylar::Time2Str(expired, "%a, %d %b %Y %H:%M:%S") << " GMT";
}
if(!domain.empty()) { ss << ";domain=" << domain; }
if(!path.empty()) { ss << ";path=" << path; }
if(secure) { ss << ";secure"; }
m_cookies.push_back(ss.str());
}
逐行拆解:
-
按 RFC 6265 规范构造
Set-Cookie头值。 -
m_cookies是vector<string>,因为一个响应可以设置多个 Cookie。 -
在
dump()中会遍历m_cookies,每个元素输出为Set-Cookie: xxx\r\n。
5.4 dump() 序列化(http.cc)
cpp
std::ostream& HttpResponse::dump(std::ostream& os) const {
os << "HTTP/"
<< ((uint32_t)(m_version >> 4))
<< "."
<< ((uint32_t)(m_version & 0x0F))
<< " "
<< (uint32_t)m_status
<< " "
<< (m_reason.empty() ? HttpStatusToString(m_status) : m_reason)
<< "\r\n";
-
响应行格式:
HTTP/1.1 200 OK\r\n -
m_reason为空时,自动从状态码查找默认描述字符串。
六、HttpRequestParser 解析器详解
输入字节流,解析HTTP消息,包括HttpRequestParser和HttpResponseParser两个结构。
HTTP解析器基于nodejs/http-parser实现,通过套接字读到HTTP消息后将消息内容传递给解析器,解析器通过回调的形式通知调用方HTTP解析的内容。
6.1 定义(http_parser.h)
cpp
/**
* @brief HTTP请求解析类
*/
class HttpRequestParser {
public:
/// HTTP解析类的智能指针
typedef std::shared_ptr<HttpRequestParser> ptr;
/**
* @brief 构造函数
*/
HttpRequestParser();
/**
* @brief 解析协议
* @param[in, out] data 协议文本内存
* @param[in] len 协议文本内存长度
* @return 返回实际解析的长度,并且将已解析的数据移除
*/
size_t execute(char* data, size_t len);
/**
* @brief 是否解析完成
* @return 是否解析完成
*/
int isFinished();
/**
* @brief 是否有错误
* @return 是否有错误
*/
int hasError();
/**
* @brief 返回HttpRequest结构体
*/
HttpRequest::ptr getData() const { return m_data;}
/**
* @brief 设置错误
* @param[in] v 错误值
*/
void setError(int v) { m_error = v;}
/**
* @brief 获取消息体长度
*/
uint64_t getContentLength();
/**
* @brief 获取http_parser结构体
*/
const http_parser& getParser() const { return m_parser;}
public:
/**
* @brief 返回HttpRequest协议解析的缓存大小
*/
static uint64_t GetHttpRequestBufferSize();
/**
* @brief 返回HttpRequest协议的最大消息体大小
*/
static uint64_t GetHttpRequestMaxBodySize();
private:
/// http_parser
http_parser m_parser;
/// HttpRequest结构
HttpRequest::ptr m_data;
/// 错误码
/// 1000: invalid method
/// 1001: invalid version
/// 1002: invalid field
int m_error;
};
关键成员:
| 成员 | 类型 | 说明 |
|---|---|---|
m_parser |
http_parser |
Ragel 状态机生成的解析器结构体,包含状态、回调函数指针 |
m_data |
HttpRequest::ptr |
解析过程中逐步填充的 HttpRequest 对象 |
m_error |
int |
错误码:1000=非法方法, 1001=非法版本, 1002=非法字段 |
6.2 execute() 原理
cpp
size_t HttpRequestParser::execute(char* data, size_t len) {
// 内部调用 Ragel 生成的解析函数
// 返回已解析的字节数
}
工作流程:
-
Ragel 状态机逐字符扫描
data。 -
遇到请求行(
GET /path HTTP/1.1)时,回调设置m_data->setMethod()/setPath()/setVersion()。 -
遇到头部字段(
Host: example.com)时,回调插入m_headers。 -
头部结束(
\r\n\r\n)后,isFinished()返回 true。 -
返回已消耗的字节数,未解析部分留给下次
execute或作为 body 处理。
为什么用 Ragel?
-
HTTP 解析涉及大量状态跳转(请求行 → 头部 → body),手写容易出错。
-
Ragel 是有限状态机编译器,从正则文法生成高效 C 代码,性能和正确性都有保障。
七、HttpSession 服务端会话详解
继承自SocketStream,实现了在套接字流上读取HTTP请求与发送HTTP响应的功能,在读取HTTP请求时需要借助HTTP解析器,以便于将套接字流上的内容解析成HTTP请求。
7.1 定义(http_session.h)
cpp
class HttpSession : public SocketStream {
public:
typedef std::shared_ptr<HttpSession> ptr;
HttpSession(Socket::ptr sock, bool owner = true);
HttpRequest::ptr recvRequest();
int sendResponse(HttpResponse::ptr rsp);
};
-
继承
SocketStream,复用其read/write/readFixSize/writeFixSize方法。 -
owner = true:HttpSession 析构时自动关闭 Socket。
7.2 recvRequest()(http_session.cc)
cpp
HttpRequest::ptr HttpSession::recvRequest() {
HttpRequestParser::ptr parser(new HttpRequestParser);
uint64_t buff_size = HttpRequestParser::GetHttpRequestBufferSize();
std::shared_ptr<char> buffer(new char[buff_size], [](char* ptr){ delete[] ptr; });
char* data = buffer.get();
int offset = 0;
逐行拆解:
-
创建解析器对象。
-
获取默认缓冲区大小(通常为 4KB)。
-
分配缓冲区,使用自定义删除器的
shared_ptr管理,确保delete[]而非delete。 -
offset:记录上次未解析完的数据在缓冲区中的偏移。
cpp
do {
int len = read(data + offset, buff_size - offset);
if(len <= 0) {
close();
return nullptr;
}
len += offset;
size_t nparse = parser->execute(data, len);
-
read()从 Socket 读取数据到data + offset(保留上次剩余数据)。 -
len <= 0:连接关闭或出错,返回 nullptr。 -
parser->execute(data, len):解析缓冲区中的数据,返回已解析字节数。
cpp
if(parser->hasError()) {
close();
return nullptr;
}
offset = len - nparse;
if(offset == (int)buff_size) {
close();
return nullptr;
}
if(parser->isFinished()) {
break;
}
} while(true);
-
offset = len - nparse:计算未解析数据长度。 -
offset == buff_size:数据量超过缓冲区且仍未解析完成(可能是攻击),直接关闭连接。 -
isFinished():请求行和头部已解析完成,退出循环。
cpp
int64_t length = parser->getContentLength();
if(length > 0) {
std::string body;
body.resize(length);
int len = 0;
if(length >= offset) {
memcpy(&body[0], data, offset);
len = offset;
} else {
memcpy(&body[0], data, length);
len = length;
}
length -= offset;
if(length > 0) {
if(readFixSize(&body[len], length) <= 0) {
close();
return nullptr;
}
}
parser->getData()->setBody(body);
}
parser->getData()->init();
return parser->getData();
}
Body 处理逻辑:
-
getContentLength()从Content-Length头获取 body 长度。 -
body.resize(length):预分配空间。 -
先从缓冲区剩余数据中拷贝已读取的 body 部分。
-
如果还不够,调用
readFixSize()继续从 Socket 读取剩余 body 字节。 -
parser->getData()->init():根据Connection头的值初始化m_close标志。
7.3 sendResponse()(http_session.cc)
cpp
int HttpSession::sendResponse(HttpResponse::ptr rsp) {
std::stringstream ss;
ss << *rsp;
std::string data = ss.str();
return writeFixSize(data.c_str(), data.size());
}
-
利用
operator<<调用rsp->dump()将响应对象序列化为字符串。 -
writeFixSize保证所有字节都写入 Socket(被 Hook 后,发送缓冲区满时会协程让出)。
八、Servlet 路由层详解
提供HTTP请求路径到处理类的映射,用于规范化的HTTP消息处理流程。
HTTP Servlet包括两部分,第一部分是Servlet对象,每个Servlet对象表示一种处理HTTP消息的方法,第二部分是ServletDispatch,它包含一个请求路径到Servlet对象的映射,用于指定一个请求路径该用哪个Servlet来处理。
为什么需要这一层?
没有路由层时,你的代码是这样的:
cpp
void handleClient(socket) {
string path = parseRequest(socket);
if(path == "/login") { doLogin(); }
else if(path == "/register") { doRegister(); }
else if(path == "/api/user") { doUser(); }
else if(path == "/api/order") { doOrder(); }
// ... 100 个 else if 后 ...
else { send404(); }
}
问题:
-
每加一个接口就要改 handleClient,违反开闭原则。
-
if/else 成百上千行,难以维护。
-
无法动态注册/卸载路由。
有了 Servlet 路由层后:
cpp
void handleClient(socket) {
auto req = session->recvRequest();
m_dispatch->handle(req, rsp, session); // 一行搞定,永远不用改
}
新接口只需要 dispatch->addServlet("/new/path", handler) 注册即可。业务代码与 HTTP 连接管理完全解耦。
8.1 Servlet 基类(servlet.h)
cpp
/**
* @brief Servlet封装
*/
class Servlet {
public:
/// 智能指针类型定义
typedef std::shared_ptr<Servlet> ptr;
/**
* @brief 构造函数
* @param[in] name 名称
*/
Servlet(const std::string& name)
:m_name(name) {}
/**
* @brief 析构函数
*/
virtual ~Servlet() {}
/**
* @brief 处理请求
* @param[in] request HTTP请求
* @param[in] response HTTP响应
* @param[in] session HTTP连接
* @return 是否处理成功
*/
virtual int32_t handle(sylar::http::HttpRequest::ptr request
, sylar::http::HttpResponse::ptr response
, sylar::http::HttpSession::ptr session) = 0;
/**
* @brief 返回Servlet名称
*/
const std::string& getName() const { return m_name;}
protected:
/// 名称
std::string m_name;
};
-
纯虚基类,
handle()是扩展点,所有业务处理器必须实现它。 -
返回值
int32_t:目前框架未使用,预留用于错误码传递。
8.2 FunctionServlet(servlet.h)
cpp
class FunctionServlet : public Servlet {
public:
typedef std::function<int32_t(HttpRequest::ptr, HttpResponse::ptr, HttpSession::ptr)> callback;
FunctionServlet(callback cb);
virtual int32_t handle(...) override { return m_cb(request, response, session); }
private:
callback m_cb;
};
- 用
std::function包装 Lambda 或函数指针,方便业务代码快速注册路由,无需定义新类。
8.3 ServletDispatch 分发器(servlet.h)
cpp
/**
* @brief Servlet分发器
*/
class ServletDispatch : public Servlet {
public:
/// 智能指针类型定义
typedef std::shared_ptr<ServletDispatch> ptr;
/// 读写锁类型定义
typedef RWMutex RWMutexType;
/**
* @brief 构造函数
*/
ServletDispatch();
virtual int32_t handle(sylar::http::HttpRequest::ptr request
, sylar::http::HttpResponse::ptr response
, sylar::http::HttpSession::ptr session) override;
/**
* @brief 添加servlet
* @param[in] uri uri
* @param[in] slt serlvet
*/
void addServlet(const std::string& uri, Servlet::ptr slt);
/**
* @brief 添加servlet
* @param[in] uri uri
* @param[in] cb FunctionServlet回调函数
*/
void addServlet(const std::string& uri, FunctionServlet::callback cb);
/**
* @brief 添加模糊匹配servlet
* @param[in] uri uri 模糊匹配 /sylar_*
* @param[in] slt servlet
*/
void addGlobServlet(const std::string& uri, Servlet::ptr slt);
/**
* @brief 添加模糊匹配servlet
* @param[in] uri uri 模糊匹配 /sylar_*
* @param[in] cb FunctionServlet回调函数
*/
void addGlobServlet(const std::string& uri, FunctionServlet::callback cb);
void addServletCreator(const std::string& uri, IServletCreator::ptr creator);
void addGlobServletCreator(const std::string& uri, IServletCreator::ptr creator);
template<class T>
void addServletCreator(const std::string& uri) {
addServletCreator(uri, std::make_shared<ServletCreator<T> >());
}
template<class T>
void addGlobServletCreator(const std::string& uri) {
addGlobServletCreator(uri, std::make_shared<ServletCreator<T> >());
}
/**
* @brief 删除servlet
* @param[in] uri uri
*/
void delServlet(const std::string& uri);
/**
* @brief 删除模糊匹配servlet
* @param[in] uri uri
*/
void delGlobServlet(const std::string& uri);
/**
* @brief 返回默认servlet
*/
Servlet::ptr getDefault() const { return m_default;}
/**
* @brief 设置默认servlet
* @param[in] v servlet
*/
void setDefault(Servlet::ptr v) { m_default = v;}
/**
* @brief 通过uri获取servlet
* @param[in] uri uri
* @return 返回对应的servlet
*/
Servlet::ptr getServlet(const std::string& uri);
/**
* @brief 通过uri获取模糊匹配servlet
* @param[in] uri uri
* @return 返回对应的servlet
*/
Servlet::ptr getGlobServlet(const std::string& uri);
/**
* @brief 通过uri获取servlet
* @param[in] uri uri
* @return 优先精准匹配,其次模糊匹配,最后返回默认
*/
Servlet::ptr getMatchedServlet(const std::string& uri);
void listAllServletCreator(std::map<std::string, IServletCreator::ptr>& infos);
void listAllGlobServletCreator(std::map<std::string, IServletCreator::ptr>& infos);
private:
/// 读写互斥量
RWMutexType m_mutex;
/// 精准匹配servlet MAP
/// uri(/sylar/xxx) -> servlet
std::unordered_map<std::string, IServletCreator::ptr> m_datas;
/// 模糊匹配servlet 数组
/// uri(/sylar/*) -> servlet
std::vector<std::pair<std::string, IServletCreator::ptr> > m_globs;
/// 默认servlet,所有路径都没匹配到时使用
Servlet::ptr m_default;
};
成员变量表:
| 变量名 | 类型 | 说明 |
|---|---|---|
m_mutex |
RWMutex |
读写锁:读操作多(每次请求都查路由),写操作少(启动时注册),读写锁比互斥锁更高效 |
m_datas |
unordered_map |
精准匹配:/hello → Servlet |
m_globs |
vector<pair> |
模糊匹配:/api/* → Servlet,按注册顺序遍历 |
m_default |
Servlet::ptr |
默认 Servlet,所有匹配都失败时调用(通常为 NotFoundServlet) |
8.4 getMatchedServlet()(servlet.cc)
cpp
Servlet::ptr ServletDispatch::getMatchedServlet(const std::string& uri) {
RWMutexType::ReadLock lock(m_mutex);
auto mit = m_datas.find(uri);
if(mit != m_datas.end()) {
return mit->second->get();
}
for(auto it = m_globs.begin(); it != m_globs.end(); ++it) {
if(!fnmatch(it->first.c_str(), uri.c_str(), 0)) {
return it->second->get();
}
}
return m_default;
}
逐行拆解:
-
ReadLock:大量并发请求同时查路由时,读锁允许多个读者并行。 -
m_datas.find(uri):O(1) 哈希查找精准匹配。 -
fnmatch(...):Unix 标准 Glob 匹配函数(如/sylar/*匹配/sylar/hello)。 -
优先级:精准 > 模糊 > 默认。一旦匹配成功立即返回,不继续搜索。
8.5 IServletCreator 与对象生命周期(servlet.h)
cpp
class IServletCreator {
public:
virtual Servlet::ptr get() const = 0;
virtual std::string getName() const = 0;
};
class HoldServletCreator : public IServletCreator {
Servlet::ptr m_servlet;
public:
Servlet::ptr get() const override { return m_servlet; }
};
template<class T>
class ServletCreator : public IServletCreator {
public:
Servlet::ptr get() const override { return Servlet::ptr(new T); }
};
设计意图:
-
HoldServletCreator:持有已有实例 (如 FunctionServlet、单例 Servlet),每次get()返回同一对象。 -
ServletCreator<T>:工厂模式 ,每次get()创建新的T实例。适用于有状态的 Servlet(如每个请求需要独立计数器)。 -
通过
IServletCreator接口统一封装,ServletDispatch 不关心具体创建策略。
九、HttpServer 详解
继承自TcpServer,重载handleClient方法,将accept后得到的客户端套接字封装成HttpSession结构,以便于接收和发送HTTP消息。
9.1 定义(http_server.h)
cpp
/**
* @brief HTTP服务器类
*/
class HttpServer : public TcpServer {
public:
/// 智能指针类型
typedef std::shared_ptr<HttpServer> ptr;
/**
* @brief 构造函数
* @param[in] keepalive 是否长连接
* @param[in] worker 工作调度器
* @param[in] accept_worker 接收连接调度器
*/
HttpServer(bool keepalive = false
,sylar::IOManager* worker = sylar::IOManager::GetThis()
,sylar::IOManager* io_worker = sylar::IOManager::GetThis()
,sylar::IOManager* accept_worker = sylar::IOManager::GetThis());
/**
* @brief 获取ServletDispatch
*/
ServletDispatch::ptr getServletDispatch() const { return m_dispatch;}
/**
* @brief 设置ServletDispatch
*/
void setServletDispatch(ServletDispatch::ptr v) { m_dispatch = v;}
virtual void setName(const std::string& v) override;
protected:
virtual void handleClient(Socket::ptr client) override;
private:
/// 是否支持长连接
bool m_isKeepalive;
/// Servlet分发器
ServletDispatch::ptr m_dispatch;
};
-
继承
TcpServer,复用bind()/start()/stop()/startAccept()的完整生命周期。 -
只需重写
handleClient(),注入 HTTP 协议处理逻辑。
9.2 构造函数(http_server.cc)
cpp
HttpServer::HttpServer(bool keepalive, ...)
:TcpServer(worker, io_worker, accept_worker)
,m_isKeepalive(keepalive) {
m_dispatch.reset(new ServletDispatch);
m_type = "http";
m_dispatch->addServlet("/_/status", Servlet::ptr(new StatusServlet));
m_dispatch->addServlet("/_/config", Servlet::ptr(new ConfigServlet));
}
逐行拆解:
-
m_isKeepalive:是否开启 HTTP 长连接。默认false,即 HTTP/1.0 短连接模式。 -
m_dispatch.reset(new ServletDispatch):创建路由分发器。 -
m_type = "http":设置服务器类型标识,用于日志和配置识别。 -
预注册两个内置监控接口:
-
/_/status:返回服务器运行状态(StatusServlet)。 -
/_/config:返回当前配置信息(ConfigServlet)。
-
9.3 setName()(http_server.cc)
cpp
void HttpServer::setName(const std::string& v) {
TcpServer::setName(v);
m_dispatch->setDefault(std::make_shared<NotFoundServlet>(v));
}
- 设置服务器名称时,同步更新默认 404 Servlet 的名称,使 404 页面显示当前服务器标识。
9.4 handleClient()(http_server.cc)
cpp
void HttpServer::handleClient(Socket::ptr client) {
SYLAR_LOG_DEBUG(g_logger) << "handleClient " << *client;
HttpSession::ptr session(new HttpSession(client));
do {
auto req = session->recvRequest();
if(!req) {
SYLAR_LOG_DEBUG(g_logger) << "recv http request fail...";
break;
}
逐行拆解:
-
创建
HttpSession包装客户端 Socket。 -
do { ... } while(true):Keep-Alive 循环,一个连接处理多次请求。 -
recvRequest():阻塞等待并解析一个完整 HTTP 请求(被 Hook 后协程让出)。 -
!req:解析失败或连接关闭,退出循环。
cpp
HttpResponse::ptr rsp(new HttpResponse(req->getVersion()
,req->isClose() || !m_isKeepalive));
rsp->setHeader("Server", getName());
m_dispatch->handle(req, rsp, session);
session->sendResponse(rsp);
-
根据请求的
version和isClose()创建响应。如果请求要求关闭,或服务器不支持 Keepalive,响应会带上Connection: close。 -
setHeader("Server", getName()):添加服务器标识头(如Server: sylar/1.0.0)。 -
m_dispatch->handle():路由分发,找到对应的 Servlet 执行业务逻辑。 -
sendResponse():将响应对象序列化后写入 Socket。
cpp
if(!m_isKeepalive || req->isClose()) {
break;
}
} while(true);
session->close();
}
-
如果服务器不支持 Keepalive,或请求要求关闭连接,退出循环。
-
session->close():关闭底层 Socket,释放资源。
十、HttpConnection 客户端连接详解
用于发起GET/POST等请求并获取响应,支持设置超时,keep-alive,支持连接池。
HTTP服务端的业务模型是接收请求→ 发送响应,而HTTP客户端的业务模型是发送请求→ 接收响应。
关于连接池,是指提前预备好一系列已接建立连接的socket,这样,在发起请求时,可以直接从中选择一个进行通信,而不用重复创建套接字→ 发起connect→ 发起请求 的流程。
连接池与发起请求时的keep-alive参数有关,如果使用连接池来发起GET/POST请求,在未设置keep-alive时,连接池并没有什么卵用。
10.1 定义(http_connection.h)
cpp
/**
* @brief HTTP客户端类
*/
class HttpConnection : public SocketStream {
friend class HttpConnectionPool;
public:
/// HTTP客户端类智能指针
typedef std::shared_ptr<HttpConnection> ptr;
/**
* @brief 发送HTTP的GET请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoGet(const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的GET请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoGet(Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoPost(const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoPost(Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoRequest(HttpMethod method
, const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoRequest(HttpMethod method
, Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] req 请求结构体
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @return 返回HTTP结果结构体
*/
static HttpResult::ptr DoRequest(HttpRequest::ptr req
, Uri::ptr uri
, uint64_t timeout_ms);
/**
* @brief 构造函数
* @param[in] sock Socket类
* @param[in] owner 是否掌握所有权
*/
HttpConnection(Socket::ptr sock, bool owner = true);
/**
* @brief 析构函数
*/
~HttpConnection();
/**
* @brief 接收HTTP响应
*/
HttpResponse::ptr recvResponse();
/**
* @brief 发送HTTP请求
* @param[in] req HTTP请求结构
*/
int sendRequest(HttpRequest::ptr req);
private:
uint64_t m_createTime = 0;
uint64_t m_request = 0;
};
-
DoGet/DoPost/DoRequest:静态便捷方法,一行代码完成 URL 解析、连接、发送、接收、解析的全流程。 -
HttpResult:封装错误码、响应对象、错误描述,比直接抛异常更可控。
10.2 HttpConnectionPool 连接池(http_connection.h)
cpp
class HttpConnectionPool {
public:
typedef std::shared_ptr<HttpConnectionPool> ptr;
typedef Mutex MutexType;
static HttpConnectionPool::ptr Create(const std::string& uri
,const std::string& vhost
,uint32_t max_size
,uint32_t max_alive_time
,uint32_t max_request);
HttpConnectionPool(const std::string& host
,const std::string& vhost
,uint32_t port
,bool is_https
,uint32_t max_size
,uint32_t max_alive_time
,uint32_t max_request);
HttpConnection::ptr getConnection();
/**
* @brief 发送HTTP的GET请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doGet(const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的GET请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doGet(Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] url 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doPost(const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP的POST请求
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doPost(Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri 请求的url
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doRequest(HttpMethod method
, const std::string& url
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] method 请求类型
* @param[in] uri URI结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @param[in] headers HTTP请求头部参数
* @param[in] body 请求消息体
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doRequest(HttpMethod method
, Uri::ptr uri
, uint64_t timeout_ms
, const std::map<std::string, std::string>& headers = {}
, const std::string& body = "");
/**
* @brief 发送HTTP请求
* @param[in] req 请求结构体
* @param[in] timeout_ms 超时时间(毫秒)
* @return 返回HTTP结果结构体
*/
HttpResult::ptr doRequest(HttpRequest::ptr req
, uint64_t timeout_ms);
private:
static void ReleasePtr(HttpConnection* ptr, HttpConnectionPool* pool);
private:
std::string m_host;
std::string m_vhost;
uint32_t m_port;
uint32_t m_maxSize;
uint32_t m_maxAliveTime;
uint32_t m_maxRequest;
bool m_isHttps;
MutexType m_mutex;
std::list<HttpConnection*> m_conns;
std::atomic<int32_t> m_total = {0};
};
设计要点:
-
max_size:连接池最大连接数,防止资源耗尽。 -
max_alive_time:连接最大存活时间,超时后关闭,避免服务器端超时断开导致的脏连接。 -
max_request:单连接最大请求数,防止连接老化。 -
m_conns:list<HttpConnection*>,LIFO 结构,最近使用的连接优先复用(热连接延迟更低)。 -
m_total:atomic计数,记录总连接数,保证线程安全。
十一、完整调用链梳理
服务端处理一次 HTTP 请求的完整流程
bash
TcpServer::startAccept()
└── accept() 得到 client socket
└── m_ioWorker->schedule(HttpServer::handleClient, client)
└── HttpServer::handleClient(Socket::ptr client)
├── HttpSession::ptr session(new HttpSession(client))
├── do {
│ HttpRequest::ptr req = session->recvRequest()
│ │ ├── new HttpRequestParser
│ │ ├── loop: read() → parser->execute()
│ │ ├── parse body (Content-Length)
│ │ └── return HttpRequest
│ │
│ HttpResponse::ptr rsp(new HttpResponse(...))
│ m_dispatch->handle(req, rsp, session)
│ │ └── ServletDispatch::getMatchedServlet(req->getPath())
│ │ ├── 精准匹配 /hello → FunctionServlet->handle()
│ │ ├── 模糊匹配 /api/* → XxxServlet->handle()
│ │ └── 默认 → NotFoundServlet->handle()
│ session->sendResponse(rsp)
│ └── rsp->dump() → writeFixSize()
│ } while(m_isKeepalive && !req->isClose())
└── session->close()
客户端发起一次 HTTP GET 的完整流程
bash
auto result = HttpConnection::DoGet("http://example.com/api", 3000);
// 内部流程:
// 1. URI::Create(url) 解析出 host=example.com, port=80, path=/api
// 2. Socket::CreateTCP(addr) → connect()
// 3. 构造 HttpRequest(GET, /api, HTTP/1.1)
// 4. sendRequest(req) → dump → writeFixSize
// 5. recvResponse() → HttpResponseParser → HttpResponse
// 6. 返回 HttpResult { OK, response, "" }
十二、线程安全总结
| 对象 | 保护内容 | 说明 |
|---|---|---|
ServletDispatch |
m_datas / m_globs |
RWMutex 读写锁保护。读多写少场景性能优秀 |
HttpRequest / HttpResponse |
成员变量 | 单请求通常单协程处理,无锁设计 |
HttpSession / HttpConnection |
Socket IO | IOManager 调度,协程级别串行,天然线程安全 |
HttpConnectionPool |
m_conns / m_total |
Mutex 保护连接队列,atomic 保护计数器 |
十三、学习验证清单
学完后,你应该能:
- 解释
HttpServer::handleClient()中 Keep-Alive 循环的退出条件。 - 说明
HttpSession::recvRequest()如何处理「请求头 + body 分两次到达」的情况。 - 解释 Servlet 路由匹配的优先级:精准 > 模糊 > 默认。
- 说明
CaseInsensitiveLess的作用,以及为什么 HTTP Header 需要忽略大小写。 - 解释
HttpRequestParser中offset和nparse的关系,以及为什么需要保留未解析数据。 - 用
HttpConnection::DoGet()发起一个 HTTP 请求并处理响应。 - 解释
IServletCreator的两种实现(HoldServletCreatorvsServletCreator)分别适用于什么场景。 - 画出从
accept()到Servlet::handle()的完整调用链。
结语
到此我们的框架中的核心10多个模块就结束了,至于剩下的一些模块近期应该是不会再出了,等到需要的时候我会再端出来的。
我是YYYing, 后面还有更精彩的内容,希望各位能多多关注支持一下主包。
无限进步 ,我们下次再见!
---⭐️ 封面自取 ⭐️---
