【C++大型项目之高性能服务器框架 (九) 】协议抽象与Http服务器模块

⭐️在这个怀疑的年代,我们依然需要信仰。

个人主页 :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 数据结构详解(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,那么这一篇我们将会开始最后几个模块的学习,话不多说,我们开始。

协议抽象

一、设计与目的

为什么要做协议抽象?

在传统的网络编程中,协议与传输层通常是紧耦合的:

  1. HTTP 服务器只能处理 HTTP 协议,无法直接处理私有二进制协议。

  2. 每种协议都要重复实现「从 Socket 读取 → 解析 → 构造消息对象」的流程。

  3. 协议升级或替换时,需要改动大量业务代码。

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;
}

逐行拆解:

  1. ByteArray::ptr ba(new ByteArray);:在堆上创建一个新的 ByteArray 对象,用于存放序列化后的二进制数据。

  2. if(serializeToByteArray(ba)):调用子类实现的纯虚函数,将当前消息对象写入 ByteArray。

  3. return ba;:序列化成功,返回包含二进制数据的 ByteArray。

  4. 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;
}

逐行拆解:

  1. writeFuint8(getType()):先写入 1 字节的消息类型。getType() 由子类实现(如 RockRequest 返回 REQUEST = 1)。

  2. writeUint32(m_sn):写入 4 字节序列号(网络字节序,由 ByteArray 内部处理)。

  3. writeUint32(m_cmd):写入 4 字节命令号。

  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,通常都在同一个协程中完成。


十一、学习验证清单

学完后,你应该能:

  • 解释 MessageMessageDecoder 的分工:谁负责消息内容,谁负责协议控制?
  • 说明 Request / Response / Notify 三种消息类型的适用场景。
  • 解释 toByteArray() 为什么是模板方法模式。
  • 说出 serializeToByteArrayparseFromByteArray 的调用顺序必须一致的原因。
  • 解释 writeStringVint 相比固定长度字符串的优势。
  • 设计一个简化版的 JsonProtocol:实现 MessageDecoder 接口,用 JSON 做序列化格式。

HTTP协议栈详解

一、设计与目的

提供HTTP服务,主要包含以下几个模块:

  1. HTTP常量定义,包括HTTP方法HttpMethod与HTTP状态HttpStatus
  2. HTTP请求与响应结构,对应HttpRequestHttpResponse
  3. HTTP解析器,包含HTTP请求解析器与HTTP响应解析器,对应HttpRequestParserHttpResponseParser
  4. HTTP会话结构,对应HttpSession
  5. HTTP服务器。
  6. HTTP Servlet。
  7. HTTP客户端HttpConnection,用于发起GET/POST等请求,支持连接池。

HTTP模块依赖nodejs/http-parser提供的HTTP解析器,并且直接复用了nodejs/http-parser中定义的HTTP方法与状态枚举。

HTTP 模块要解决什么问题?

  1. 协议解析复杂:HTTP 是文本协议,请求行、头部、消息体的格式各不相同,手动解析容易出错。

  2. 请求路由困难 :不同 URI 需要调用不同的业务函数,传统的 if/else 判断难以维护。

  3. 连接管理繁琐:HTTP/1.1 支持 Keep-Alive,一个 TCP 连接上可能有多次请求,需要会话状态管理。

  4. 客户端调用麻烦 :每次发 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-Typecontent-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";

逐行拆解:

  1. HttpMethodToString(m_method):将枚举转为字符串(如 "GET")。

  2. m_path:输出路径。

  3. m_query.empty() ? "" : "?":有查询参数时输出 ?,否则跳过。

  4. m_fragment 同理,前缀为 #

  5. m_version >> 4m_version & 0x0F0x11 高 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_cookiesvector<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 生成的解析函数
    // 返回已解析的字节数
}

工作流程:

  1. Ragel 状态机逐字符扫描 data

  2. 遇到请求行(GET /path HTTP/1.1)时,回调设置 m_data->setMethod() / setPath() / setVersion()

  3. 遇到头部字段(Host: example.com)时,回调插入 m_headers

  4. 头部结束(\r\n\r\n)后,isFinished() 返回 true。

  5. 返回已消耗的字节数,未解析部分留给下次 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;

逐行拆解:

  1. 创建解析器对象。

  2. 获取默认缓冲区大小(通常为 4KB)。

  3. 分配缓冲区,使用自定义删除器的 shared_ptr 管理,确保 delete[] 而非 delete

  4. 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 处理逻辑:

  1. getContentLength()Content-Length 头获取 body 长度。

  2. body.resize(length):预分配空间。

  3. 先从缓冲区剩余数据中拷贝已读取的 body 部分。

  4. 如果还不够,调用 readFixSize() 继续从 Socket 读取剩余 body 字节。

  5. 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;
}

逐行拆解:

  1. ReadLock:大量并发请求同时查路由时,读锁允许多个读者并行。

  2. m_datas.find(uri):O(1) 哈希查找精准匹配。

  3. fnmatch(...):Unix 标准 Glob 匹配函数(如 /sylar/* 匹配 /sylar/hello)。

  4. 优先级:精准 > 模糊 > 默认。一旦匹配成功立即返回,不继续搜索。


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));
}

逐行拆解:

  1. m_isKeepalive:是否开启 HTTP 长连接。默认 false,即 HTTP/1.0 短连接模式。

  2. m_dispatch.reset(new ServletDispatch):创建路由分发器。

  3. m_type = "http":设置服务器类型标识,用于日志和配置识别。

  4. 预注册两个内置监控接口:

    • /_/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;
        }

逐行拆解:

  1. 创建 HttpSession 包装客户端 Socket。

  2. do { ... } while(true):Keep-Alive 循环,一个连接处理多次请求。

  3. recvRequest():阻塞等待并解析一个完整 HTTP 请求(被 Hook 后协程让出)。

  4. !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);
  • 根据请求的 versionisClose() 创建响应。如果请求要求关闭,或服务器不支持 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_connslist<HttpConnection*>,LIFO 结构,最近使用的连接优先复用(热连接延迟更低)。

  • m_totalatomic 计数,记录总连接数,保证线程安全。


十一、完整调用链梳理

服务端处理一次 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 需要忽略大小写。
  • 解释 HttpRequestParseroffsetnparse 的关系,以及为什么需要保留未解析数据。
  • HttpConnection::DoGet() 发起一个 HTTP 请求并处理响应。
  • 解释 IServletCreator 的两种实现(HoldServletCreator vs ServletCreator)分别适用于什么场景。
  • 画出从 accept()Servlet::handle() 的完整调用链。

结语

到此我们的框架中的核心10多个模块就结束了,至于剩下的一些模块近期应该是不会再出了,等到需要的时候我会再端出来的。

我是YYYing, 后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步 ,我们下次再见!


---⭐️ 封面自取 ⭐️---

相关推荐
Huangjin007_19 小时前
【Linux 系统篇(四)】权限详解(一)
linux·运维·服务器
ch0sen1pm19 小时前
不太懂 atomic?我花了一下午从 freelist 写到 CAS 无锁栈
c++
_waylau19 小时前
Spring Framework HTTP服务客户端详解
java·后端·网络协议·spring·http·spring cloud
老大白菜19 小时前
Omnigent(omni)使用说明
服务器·数据库·microsoft
Android-Flutter19 小时前
Android的http和https知识点
android·http·https
程序员-李俞19 小时前
向量引擎接入自研 API 中转网关:鉴权、限流、熔断和审计日志复盘
服务器·人工智能·大模型·api·ai编程·ai api
红豆诗人20 小时前
C++ string 和 vector 基础:从常用接口到模拟实现
c++·stl
良木生香20 小时前
【C++初阶】STL—— Stack & Queue 从入门到精通:容器适配器、迭代器与经典面试题
java·开发语言·c++·算法·zookeeper
爱刷碗的苏泓舒20 小时前
网络通信入门之 NTRIP、HTTP、TCP 与 IP 的关系:协议分层、连接过程以及实时数据流
网络协议·tcp/ip·http·网络通信·rtcm·监控运维·ntrip