【项目篇】从零手写高并发服务器(九):HTTP协议支持——从TCP到应用层

文章目录

从零手写高并发服务器(九):HTTP协议支持------从TCP到应用层

💬 开篇:上一篇我们的TcpServer已经能跑了,但它只是一个TCP服务器,收到的是原始字节流。本篇我们在TcpServer之上实现HTTP协议解析,让服务器能处理真正的HTTP请求,返回网页内容。

👍 点赞、收藏与分享:这是从传输层到应用层的跨越,写完这篇你就有一个完整的HTTP服务器了。

🚀 循序渐进:HTTP协议回顾 → 正则表达式 → HttpRequest → HttpResponse → HttpContext → HttpServer → 测试。


一、HTTP协议快速回顾

1.1 HTTP请求格式

bash 复制代码
GET /index.html HTTP/1.1\r\n          ← 请求行
Host: www.example.com\r\n             ← 请求头部
Connection: keep-alive\r\n
Content-Type: text/html\r\n
\r\n                                   ← 空行(头部结束标志)
请求正文(GET请求通常没有正文)           ← 正文

请求行三要素:

  • 请求方法:GET / POST / PUT / DELETE ...
  • 资源路径:/index.html?key=val
  • 协议版本:HTTP/1.1

1.2 HTTP响应格式

bash 复制代码
HTTP/1.1 200 OK\r\n                    ← 响应行
Content-Type: text/html\r\n            ← 响应头部
Content-Length: 13\r\n
\r\n                                    ← 空行
Hello, World!                           ← 响应正文

1.3 解析思路

HTTP请求的解析是逐步进行的,因为TCP是流式传输,数据可能分多次到达:

bash 复制代码
解析状态机:

  RECV_HTTP_LINE    →  解析请求行
       ↓ 成功
  RECV_HTTP_HEAD    →  解析头部(一行一行解析)
       ↓ 头部解析完毕
  RECV_HTTP_BODY    →  接收正文(根据Content-Length)
       ↓ 正文接收完毕
  RECV_HTTP_OVER    →  解析完成,可以处理请求了

二、工具函数Util

在实现HTTP模块之前,先准备一些工具函数。将以下代码添加到 server.hpp 的最后(在 #endif 之前):

cpp 复制代码
// ==================== HTTP工具函数 ====================
class Util {
public:
    // 字符串分割
    static size_t Split(const std::string &src, const std::string &sep, std::vector<std::string> *arry) {
        size_t offset = 0;
        while (offset < src.size()) {
            size_t pos = src.find(sep, offset);
            if (pos == std::string::npos) {
                if (offset == src.size()) break;
                arry->push_back(src.substr(offset));
                return arry->size();
            }
            if (pos == offset) {
                offset = pos + sep.size();
                continue;
            }
            arry->push_back(src.substr(offset, pos - offset));
            offset = pos + sep.size();
        }
        return arry->size();
    }
    
    // 读取文件内容
    static bool ReadFile(const std::string &filename, std::string *buf) {
        std::ifstream ifs(filename, std::ios::binary);
        if (ifs.is_open() == false) {
            ERR_LOG("OPEN %s FILE FAILED!", filename.c_str());
            return false;
        }
        size_t fsize = 0;
        ifs.seekg(0, ifs.end);
        fsize = ifs.tellg();
        ifs.seekg(0, ifs.beg);
        buf->resize(fsize);
        ifs.read(&(*buf)[0], fsize);
        if (ifs.good() == false) {
            ERR_LOG("READ %s FILE FAILED!", filename.c_str());
            ifs.close();
            return false;
        }
        ifs.close();
        return true;
    }
    
    // 写入文件
    static bool WriteFile(const std::string &filename, const std::string &buf) {
        std::ofstream ofs(filename, std::ios::binary | std::ios::trunc);
        if (ofs.is_open() == false) {
            ERR_LOG("OPEN %s FILE FAILED!", filename.c_str());
            return false;
        }
        ofs.write(buf.c_str(), buf.size());
        if (ofs.good() == false) {
            ERR_LOG("WRITE %s FILE FAILED!", filename.c_str());
            ofs.close();
            return false;
        }
        ofs.close();
        return true;
    }
    
    // URL编码:将特殊字符转为%HH格式
    static std::string UrlEncode(const std::string &url, bool convert_space_to_plus) {
        std::string res;
        for (auto &c : url) {
            if (c == '.' || c == '-' || c == '_' || c == '~' || isalnum(c)) {
                res += c;
                continue;
            }
            if (c == ' ' && convert_space_to_plus == true) {
                res += '+';
                continue;
            }
            char tmp[4] = {0};
            snprintf(tmp, 4, "%%%02X", c);
            res += tmp;
        }
        return res;
    }
    
    // URL解码
    static std::string UrlDecode(const std::string &url, bool convert_plus_to_space) {
        std::string res;
        for (int i = 0; i < (int)url.size(); i++) {
            if (url[i] == '+' && convert_plus_to_space == true) {
                res += ' ';
                continue;
            }
            if (url[i] == '%' && (i + 2) < (int)url.size()) {
                char v1 = HEXTOI(url[i + 1]);
                char v2 = HEXTOI(url[i + 2]);
                char v = v1 * 16 + v2;
                res += v;
                i += 2;
                continue;
            }
            res += url[i];
        }
        return res;
    }
    
    // 获取HTTP状态码描述
    static std::string StatuDesc(int statu) {
        auto it = _statu_msg.find(statu);
        if (it != _statu_msg.end()) {
            return it->second;
        }
        return "Unknow";
    }
    
    // 根据文件后缀获取mime类型
    static std::string ExtMime(const std::string &filename) {
        size_t pos = filename.find_last_of('.');
        if (pos == std::string::npos) {
            return "application/octet-stream";
        }
        std::string ext = filename.substr(pos);
        auto it = _mime_msg.find(ext);
        if (it == _mime_msg.end()) {
            return "application/octet-stream";
        }
        return it->second;
    }
    
    // 判断一个文件是否是目录
    static bool IsDirectory(const std::string &filename) {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if (ret < 0) {
            return false;
        }
        return S_ISDIR(st.st_mode);
    }
    
    // 判断一个文件是否是普通文件
    static bool IsRegular(const std::string &filename) {
        struct stat st;
        int ret = stat(filename.c_str(), &st);
        if (ret < 0) {
            return false;
        }
        return S_ISREG(st.st_mode);
    }
    
    // 判断请求的资源路径是否有效(不能包含..等路径穿越)
    static bool ValidPath(const std::string &path) {
        std::vector<std::string> subdir;
        Split(path, "/", &subdir);
        int level = 0;
        for (auto &dir : subdir) {
            if (dir == "..") {
                level--;
                if (level < 0) return false;
            } else {
                level++;
            }
        }
        return true;
    }
    
private:
    static char HEXTOI(char c) {
        if (c >= '0' && c <= '9') return c - '0';
        if (c >= 'a' && c <= 'z') return c - 'a' + 10;
        if (c >= 'A' && c <= 'Z') return c - 'A' + 10;
        return 0;
    }
    
    static std::unordered_map<int, std::string> _statu_msg;
    static std::unordered_map<std::string, std::string> _mime_msg;
};

// 静态成员初始化
std::unordered_map<int, std::string> Util::_statu_msg = {
    {100, "Continue"},
    {101, "Switching Protocol"},
    {200, "OK"},
    {201, "Created"},
    {204, "No Content"},
    {206, "Partial Content"},
    {301, "Moved Permanently"},
    {302, "Found"},
    {303, "See Other"},
    {304, "Not Modified"},
    {307, "Temporary Redirect"},
    {400, "Bad Request"},
    {401, "Unauthorized"},
    {403, "Forbidden"},
    {404, "Not Found"},
    {405, "Method Not Allowed"},
    {406, "Not Acceptable"},
    {408, "Request Timeout"},
    {413, "Payload Too Large"},
    {414, "URI Too Long"},
    {415, "Unsupported Media Type"},
    {500, "Internal Server Error"},
    {502, "Bad Gateway"},
    {503, "Service Unavailable"},
    {504, "Gateway Timeout"},
    {505, "HTTP Version Not Supported"}
};

std::unordered_map<std::string, std::string> Util::_mime_msg = {
    {".html", "text/html"},
    {".htm", "text/html"},
    {".css", "text/css"},
    {".js", "application/javascript"},
    {".json", "application/json"},
    {".xml", "application/xml"},
    {".png", "image/png"},
    {".jpg", "image/jpeg"},
    {".jpeg", "image/jpeg"},
    {".gif", "image/gif"},
    {".ico", "image/x-icon"},
    {".svg", "image/svg+xml"},
    {".mp3", "audio/mpeg"},
    {".mp4", "video/mp4"},
    {".avi", "video/x-msvideo"},
    {".txt", "text/plain"},
    {".pdf", "application/pdf"},
    {".doc", "application/msword"},
    {".gz", "application/gzip"},
    {".zip", "application/zip"},
    {".tar", "application/x-tar"},
    {".unknown", "application/octet-stream"}
};

三、HttpRequest与HttpResponse类

3.1 HTTP请求解析状态

cpp 复制代码
// ==================== HTTP请求解析状态 ====================
typedef enum {
    RECV_HTTP_ERROR,
    RECV_HTTP_LINE,
    RECV_HTTP_HEAD,
    RECV_HTTP_BODY,
    RECV_HTTP_OVER
} HttpRecvStatu;

3.2 HttpRequest类

cpp 复制代码
// ==================== HttpRequest请求类 ====================
class HttpRequest {
public:
    std::string _method;
    std::string _path;
    std::string _version;
    std::string _body;
    std::smatch _matches;
    std::unordered_map<std::string, std::string> _headers;
    std::unordered_map<std::string, std::string> _params;
    
public:
    HttpRequest() : _version("HTTP/1.1") {}
    
    void Reset() {
        _method.clear();
        _path.clear();
        _version = "HTTP/1.1";
        _body.clear();
        std::smatch match;
        _matches.swap(match);
        _headers.clear();
        _params.clear();
    }
    
    void SetHeader(const std::string &key, const std::string &val) {
        _headers[key] = val;
    }
    
    bool HasHeader(const std::string &key) const {
        return _headers.find(key) != _headers.end();
    }
    
    std::string GetHeader(const std::string &key) const {
        auto it = _headers.find(key);
        if (it == _headers.end()) return "";
        return it->second;
    }
    
    void SetParam(const std::string &key, const std::string &val) {
        _params[key] = val;
    }
    
    bool HasParam(const std::string &key) const {
        return _params.find(key) != _params.end();
    }
    
    std::string GetParam(const std::string &key) const {
        auto it = _params.find(key);
        if (it == _params.end()) return "";
        return it->second;
    }
    
    size_t ContentLength() const {
        if (HasHeader("Content-Length") == false) return 0;
        return std::stol(GetHeader("Content-Length"));
    }
    
    // 判断是否是短链接
    bool Close() const {
        if (HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive") {
            return false;
        }
        return true;
    }
};

3.3、HttpResponse类

cpp 复制代码
// ==================== HttpResponse响应类 ====================
class HttpResponse {
public:
    int _statu;
    bool _redirect_flag;
    std::string _redirect_url;
    std::string _body;
    std::unordered_map<std::string, std::string> _headers;
    
public:
    HttpResponse() : _redirect_flag(false), _statu(200) {}
    HttpResponse(int statu) : _redirect_flag(false), _statu(statu) {}
    
    void Reset() {
        _statu = 200;
        _redirect_flag = false;
        _redirect_url.clear();
        _body.clear();
        _headers.clear();
    }
    
    void SetHeader(const std::string &key, const std::string &val) {
        _headers[key] = val;
    }
    
    bool HasHeader(const std::string &key) {
        return _headers.find(key) != _headers.end();
    }
    
    std::string GetHeader(const std::string &key) {
        auto it = _headers.find(key);
        if (it == _headers.end()) return "";
        return it->second;
    }
    
    void SetContent(const std::string &body, const std::string &type = "text/html") {
        _body = body;
        SetHeader("Content-Type", type);
    }
    
    void SetRedirect(const std::string &url, int statu = 302) {
        _statu = statu;
        _redirect_flag = true;
        _redirect_url = url;
    }
    
    // 判断是否是短链接
    bool Close() {
        if (HasHeader("Connection") == true && GetHeader("Connection") == "keep-alive") {
            return false;
        }
        return true;
    }
};

四、HttpContext上下文类------HTTP解析状态机

4.1 HttpContext的作用

HttpContext是HTTP请求的解析上下文,保存当前的解析状态和解析进度。每个Connection都有一个HttpContext,因为TCP是流式的,一次可能收不完一个完整的HTTP请求。

4.2 HttpContext类实现

cpp 复制代码
// ==================== HttpContext上下文类 ====================
class HttpContext {
private:
    int _resp_statu;            // 解析出错时记录错误状态码
    HttpRecvStatu _recv_statu;  // 当前解析阶段
    HttpRequest _request;       // 已解析的请求信息
    
private:
    // 解析请求行
    bool ParseHttpLine(const std::string &line) {
        std::smatch matches;
        std::regex e("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?", std::regex::icase);
        bool ret = std::regex_match(line, matches, e);
        if (ret == false) {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 400;
            return false;
        }
        // matches[0]-整体 [1]-方法 [2]-路径 [3]-查询字符串 [4]-协议版本
        _request._method = matches[1];
        std::transform(_request._method.begin(), _request._method.end(),
                       _request._method.begin(), ::toupper);
        _request._path = Util::UrlDecode(matches[2], false);
        _request._version = matches[4];
        
        // 解析查询字符串 key=val&key=val
        std::string query_string = matches[3];
        if (!query_string.empty()) {
            std::vector<std::string> query_string_arry;
            Util::Split(query_string, "&", &query_string_arry);
            for (auto &str : query_string_arry) {
                size_t pos = str.find("=");
                if (pos == std::string::npos) {
                    _recv_statu = RECV_HTTP_ERROR;
                    _resp_statu = 400;
                    return false;
                }
                std::string key = Util::UrlDecode(str.substr(0, pos), true);
                std::string val = Util::UrlDecode(str.substr(pos + 1), true);
                _request.SetParam(key, val);
            }
        }
        return true;
    }
    
    // 接收并解析请求行
    bool RecvHttpLine(Buffer *buf) {
        if (_recv_statu != RECV_HTTP_LINE) return false;
        std::string line = buf->GetLineAndPop();
        if (line.size() == 0) {
            if (buf->ReadAbleSize() > 8192) {
                _recv_statu = RECV_HTTP_ERROR;
                _resp_statu = 414;
                return false;
            }
            return true; // 等待更多数据
        }
        if (line.size() > 8192) {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 414;
            return false;
        }
        bool ret = ParseHttpLine(line);
        if (ret == false) return false;
        _recv_statu = RECV_HTTP_HEAD;
        return true;
    }
    
    // 解析一行头部字段
    bool ParseHttpHead(std::string &line) {
        // 去掉末尾\r\n
        if (line.back() == '\n') line.pop_back();
        if (line.back() == '\r') line.pop_back();
        size_t pos = line.find(": ");
        if (pos == std::string::npos) {
            _recv_statu = RECV_HTTP_ERROR;
            _resp_statu = 400;
            return false;
        }
        std::string key = line.substr(0, pos);
        std::string val = line.substr(pos + 2);
        _request.SetHeader(key, val);
        return true;
    }
    
    // 接收并解析头部
    bool RecvHttpHead(Buffer *buf) {
        if (_recv_statu != RECV_HTTP_HEAD) return false;
        while (true) {
            std::string line = buf->GetLineAndPop();
            if (line.size() == 0) {
                if (buf->ReadAbleSize() > 8192) {
                    _recv_statu = RECV_HTTP_ERROR;
                    _resp_statu = 413;
                    return false;
                }
                return true; // 等待更多数据
            }
            // 空行表示头部结束
            if (line == "\n" || line == "\r\n") {
                break;
            }
            bool ret = ParseHttpHead(line);
            if (ret == false) return false;
        }
        _recv_statu = RECV_HTTP_BODY;
        return true;
    }
    
    // 接收正文
    bool RecvHttpBody(Buffer *buf) {
        if (_recv_statu != RECV_HTTP_BODY) return false;
        size_t content_length = _request.ContentLength();
        if (content_length == 0) {
            _recv_statu = RECV_HTTP_OVER;
            return true;
        }
        // 还需要接收多少字节
        size_t real_len = content_length - _request._body.size();
        if (buf->ReadAbleSize() >= real_len) {
            _request._body.append(buf->ReadPosition(), real_len);
            buf->MoveReadOffset(real_len);
            _recv_statu = RECV_HTTP_OVER;
            return true;
        }
        // 数据不够,先收下现有的
        _request._body.append(buf->ReadPosition(), buf->ReadAbleSize());
        buf->MoveReadOffset(buf->ReadAbleSize());
        return true;
    }
    
public:
    HttpContext() : _resp_statu(200), _recv_statu(RECV_HTTP_LINE) {}
    
    void Reset() {
        _resp_statu = 200;
        _recv_statu = RECV_HTTP_LINE;
        _request.Reset();
    }
    
    int RespStatu() { return _resp_statu; }
    HttpRecvStatu RecvStatu() { return _recv_statu; }
    HttpRequest &Request() { return _request; }
    
    // 接收并解析HTTP请求
    // switch没有break是故意的:一次调用尽量多地完成解析
    void RecvHttpRequest(Buffer *buf) {
        switch (_recv_statu) {
            case RECV_HTTP_LINE: RecvHttpLine(buf);
            case RECV_HTTP_HEAD: RecvHttpHead(buf);
            case RECV_HTTP_BODY: RecvHttpBody(buf);
            default: break;
        }
    }
};

五、HttpServer类------完整HTTP服务器

5.1 HttpServer的作用

HttpServer在TcpServer之上封装HTTP协议处理逻辑:

bash 复制代码
HttpServer的结构:

  ┌──────────────────────────────────────────┐
  │              HttpServer                   │
  │                                           │
  │  TcpServer _server     → 底层TCP服务器     │
  │  路由表:                                  │
  │    GET  /index  → handler1                │
  │    POST /login  → handler2                │
  │    GET  /.*     → 静态资源处理              │
  │                                           │
  │  流程:                                    │
  │  1. 收到数据 → HttpContext解析              │
  │  2. 解析完成 → 路由匹配                    │
  │  3. 调用handler → 生成HttpResponse          │
  │  4. 组装响应 → Send发送                    │
  └──────────────────────────────────────────┘

5.2 HttpServer类实现

cpp 复制代码
// ==================== HttpServer ====================
#define DEFAULT_TIMEOUT 10

class HttpServer {
private:
    using Handler = std::function<void(const HttpRequest &, HttpResponse *)>;
    using Handlers = std::vector<std::pair<std::regex, Handler>>;
    Handlers _get_route;
    Handlers _post_route;
    Handlers _put_route;
    Handlers _delete_route;
    std::string _basedir;  // 静态资源根目录
    TcpServer _server;
    
private:
    void ErrorHandler(const HttpRequest &req, HttpResponse *rsp) {
        std::string body;
        body += "<html>";
        body += "<head>";
        body += "<meta http-equiv='Content-Type' content='text/html;charset=utf-8'>";
        body += "</head>";
        body += "<body>";
        body += "<h1>";
        body += std::to_string(rsp->_statu);
        body += " ";
        body += Util::StatuDesc(rsp->_statu);
        body += "</h1>";
        body += "</body>";
        body += "</html>";
        rsp->SetContent(body, "text/html");
    }
    
    // 将HttpResponse按照HTTP协议格式组装并发送
    void WriteResponse(const PtrConnection &conn, const HttpRequest &req, HttpResponse &rsp) {
        // 1. 补全头部字段
        if (req.Close() == true) {
            rsp.SetHeader("Connection", "close");
        } else {
            rsp.SetHeader("Connection", "keep-alive");
        }
        if (rsp._body.empty() == false && rsp.HasHeader("Content-Length") == false) {
            rsp.SetHeader("Content-Length", std::to_string(rsp._body.size()));
        }
        if (rsp._body.empty() == false && rsp.HasHeader("Content-Type") == false) {
            rsp.SetHeader("Content-Type", "application/octet-stream");
        }
        if (rsp._redirect_flag == true) {
            rsp.SetHeader("Location", rsp._redirect_url);
        }
        // 2. 组装响应字符串
        std::stringstream rsp_str;
        rsp_str << req._version << " " << std::to_string(rsp._statu) << " " << Util::StatuDesc(rsp._statu) << "\r\n";
        for (auto &head : rsp._headers) {
            rsp_str << head.first << ": " << head.second << "\r\n";
        }
        rsp_str << "\r\n";
        rsp_str << rsp._body;
        // 3. 发送(只调用一次str()避免性能问题)
        std::string rsp_buf = rsp_str.str();
        conn->Send(rsp_buf.c_str(), rsp_buf.size());
    }
    
    // 判断是否是静态资源请求
    bool IsFileRequest(const HttpRequest &req) {
        if (_basedir.empty()) return false;
        if (req._method != "GET" && req._method != "HEAD") return false;
        if (Util::ValidPath(req._path) == false) return false;
        std::string req_path = _basedir + req._path;
        if (req_path.back() == '/') req_path += "index.html";
        if (Util::IsRegular(req_path) == false) return false;
        return true;
    }
    
    // 静态资源请求的处理
    void FileHandler(const HttpRequest &req, HttpResponse *rsp) {
        std::string req_path = _basedir + req._path;
        if (req_path.back() == '/') req_path += "index.html";
        bool ret = Util::ReadFile(req_path, &rsp->_body);
        if (ret == false) return;
        rsp->SetHeader("Content-Type", Util::ExtMime(req_path));
    }
    
    // 功能性请求的路由分发
    void Dispatcher(HttpRequest &req, HttpResponse *rsp, Handlers &handlers) {
        for (auto &handler : handlers) {
            const std::regex &re = handler.first;
            const Handler &functor = handler.second;
            bool ret = std::regex_match(req._path, req._matches, re);
            if (ret == false) continue;
            return functor(req, rsp);
        }
        rsp->_statu = 404;
    }
    
    // 路由查找+处理
    void Route(HttpRequest &req, HttpResponse *rsp) {
        if (IsFileRequest(req)) {
            return FileHandler(req, rsp);
        }
        if (req._method == "GET" || req._method == "HEAD") {
            return Dispatcher(req, rsp, _get_route);
        } else if (req._method == "POST") {
            return Dispatcher(req, rsp, _post_route);
        } else if (req._method == "PUT") {
            return Dispatcher(req, rsp, _put_route);
        } else if (req._method == "DELETE") {
            return Dispatcher(req, rsp, _delete_route);
        }
        rsp->_statu = 405;
    }
    
    // 连接建立成功后的回调
    void OnConnected(const PtrConnection &conn) {
        conn->SetContext(HttpContext());
        DBG_LOG("NEW CONNECTION %p", conn.get());
    }
    
    // 收到新数据后的回调
    void OnMessage(const PtrConnection &conn, Buffer *buffer) {
        while (buffer->ReadAbleSize() > 0) {
            // 1. 获取上下文
            HttpContext *context = conn->GetContext()->get<HttpContext>();
            // 2. 解析数据
            context->RecvHttpRequest(buffer);
            HttpRequest &req = context->Request();
            HttpResponse rsp(context->RespStatu());
            // 3. 解析出错,返回错误响应并关闭连接
            if (context->RespStatu() >= 400) {
                ErrorHandler(req, &rsp);
                WriteResponse(conn, req, rsp);
                context->Reset();
                conn->Shutdown();
                return;
            }
            // 4. 还没解析完,等待更多数据
            if (context->RecvStatu() != RECV_HTTP_OVER) {
                return;
            }
            // 5. 解析完毕,路由处理
            Route(req, &rsp);
            // 6. 发送响应
            WriteResponse(conn, req, rsp);
            // 7. 重置上下文(keep-alive连接复用)
            context->Reset();
            // 8. 短链接直接关闭
            if (rsp.Close() == true) {
                conn->Shutdown();
            }
        }
    }
    
public:
    HttpServer(int port, int timeout = DEFAULT_TIMEOUT) : _server(port) {
        _server.EnableInactiveRelease(timeout);
        _server.SetConnectedCallback(std::bind(&HttpServer::OnConnected, this, std::placeholders::_1));
        _server.SetMessageCallback(std::bind(&HttpServer::OnMessage, this, std::placeholders::_1, std::placeholders::_2));
    }
    
    void SetThreadCount(int count) { _server.SetThreadCount(count); }
    
    void SetBaseDir(const std::string &path) {
        assert(Util::IsDirectory(path) == true);
        _basedir = path;
    }
    
    void Get(const std::string &pattern, const Handler &handler) {
        _get_route.push_back(std::make_pair(std::regex(pattern), handler));
    }
    
    void Post(const std::string &pattern, const Handler &handler) {
        _post_route.push_back(std::make_pair(std::regex(pattern), handler));
    }
    
    void Put(const std::string &pattern, const Handler &handler) {
        _put_route.push_back(std::make_pair(std::regex(pattern), handler));
    }
    
    void Delete(const std::string &pattern, const Handler &handler) {
        _delete_route.push_back(std::make_pair(std::regex(pattern), handler));
    }
    
    void SetTimerTask(const Functor &task, int delay) {
        _server.RunAfter(task, delay);
    }
    
    void Listen() {
        _server.Start();
    }
};

六、完整的HTTP服务器测试

6.1 创建静态资源

bash 复制代码
cd ~/TcpServer
mkdir -p wwwroot
vim wwwroot/index.html
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Http Server</title>
</head>
<body>
    <h1>Hello! 这是我的HTTP服务器!</h1>
    <p>如果你看到这个页面,说明服务器运行成功了。</p>
</body>
</html>

6.2 编写HTTP服务器

bash 复制代码
cd ~/TcpServer/test
vim http_server.cpp
cpp 复制代码
#include "../source/server.hpp"

int main() {
    HttpServer server(8500);
    server.SetThreadCount(3);
    server.SetBaseDir("../wwwroot");  // 静态资源根目录(相对于test目录)
    
    // 注册GET路由:/hello
    server.Get("/hello", [](const HttpRequest &req, HttpResponse *rsp) {
        rsp->SetContent("<html><body><h1>Hello World!</h1></body></html>", "text/html");
    });
    
    // 注册GET路由:/say/xxx,使用正则捕获
    server.Get("/say/(.*)", [](const HttpRequest &req, HttpResponse *rsp) {
        std::string msg = req._matches[1];
        std::string body = "<html><body><h1>You said: " + msg + "</h1></body></html>";
        rsp->SetContent(body, "text/html");
    });
    
    // 注册POST路由:/login
    server.Post("/login", [](const HttpRequest &req, HttpResponse *rsp) {
        std::string body = "<html><body><h1>Login OK!</h1>";
        body += "<p>Body: " + req._body + "</p>";
        body += "</body></html>";
        rsp->SetContent(body, "text/html");
    });
    
    server.Listen();
    return 0;
}

6.3 编译运行

bash 复制代码
cd ~/TcpServer/test
g++ -std=c++17 http_server.cpp -o http_server -lpthread
./http_server

6.4 测试

bash 复制代码
# 测试静态资源
curl http://127.0.0.1:8500/

# 测试/hello路由
curl http://127.0.0.1:8500/hello

# 测试正则路由
curl http://127.0.0.1:8500/say/nihao

# 测试POST
curl -X POST -d "username=test&password=123" http://127.0.0.1:8500/login

预期输出:

bash 复制代码
wsh@VM-16-2-ubuntu:~/TcpServer/test$ curl http://127.0.0.1:8500/
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>My Http Server</title>
</head>
<body>
    <h1>Hello! 这是我的HTTP服务器!</h1>
    <p>如果你看到这个页面,说明服务器运行成功了。</p>
</body>
</html>

wsh@VM-16-2-ubuntu:~/TcpServer/test$ curl http://127.0.0.1:8500/hello
<html><body><h1>Hello World!</h1></body></html>

wsh@VM-16-2-ubuntu:~/TcpServer/test$ curl http://127.0.0.1:8500/say/nihao
<html><body><h1>You said: nihao</h1></body></html>

wsh@VM-16-2-ubuntu:~/TcpServer/test$ curl -X POST -d "username=test&password=123" http://127.0.0.1:8500/login
<html><body><h1>Login OK!</h1><p>Body: username=test&password=123</p></body></html>

全部通过!🎉


七、提交代码

bash 复制代码
cd ~/TcpServer
git add .
git commit -m "实现HTTP协议支持:Util、HttpContext、HttpServer"
git push

八、本篇总结

模块 功能
Util 工具函数:字符串分割、文件读写、URL编解码、MIME类型
HttpContext HTTP请求解析状态机,处理粘包分包
HttpServer 在TcpServer之上封装HTTP协议,支持路由和静态资源

核心是 HttpContext 的状态机设计

bash 复制代码
RECV_HTTP_LINE → 解析请求行(方法、路径、协议版本、查询字符串)
    ↓
RECV_HTTP_HEAD → 解析头部(一行一行读)
    ↓
RECV_HTTP_BODY → 根据Content-Length接收正文
    ↓
RECV_HTTP_OVER → 解析完成,业务处理

关键特性:

  • ✅ 完整的HTTP请求解析(支持查询字符串、POST正文)
  • ✅ 路由分发(GET、POST、PUT、DELETE)
  • ✅ 静态资源服务(自动MIME类型识别)
  • ✅ Keep-Alive连接复用
  • ✅ 错误处理与状态码返回
  • ✅ URL编解码支持

至此,一个完整的HTTP服务器已经实现!


九、完整server.hpp类顺序总览

到目前为止,server.hpp 中所有类的完整顺序:

cpp 复制代码
#ifndef __SERVER_HPP__
#define __SERVER_HPP__

// ===== 头文件 =====
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <memory>
#include <cassert>
#include <cstring>
#include <ctime>
#include <typeinfo>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <regex>
#include <sstream>
#include <fstream>
#include <algorithm>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/eventfd.h>
#include <sys/timerfd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ===== 1. 日志宏 =====
// ===== 2. Buffer类(含FindCRLF / GetLineAndPop)=====
// ===== 3. Any类 =====
// ===== 4. NetWork类(SIGPIPE处理)=====
// ===== 5. Socket类 =====
// ===== 6. 前向声明 class Poller; class EventLoop; =====
// ===== 7. Channel类(Update/Remove只声明)=====
// ===== 8. Poller类 =====
// ===== 9. TimerTask类 =====
// ===== 10. TimerWheel类(对外接口只声明)=====
// ===== 11. EventLoop类(含AssertInLoop / HasTimer)=====
// ===== 12. Channel::Update() / Remove() 实现 =====
// ===== 13. TimerWheel::TimerAdd/Refresh/Cancel 实现 =====
// ===== 14. LoopThread类 =====
// ===== 15. LoopThreadPool类 =====
// ===== 16. ConnStatu枚举 =====
// ===== 17. Connection类 =====
// ===== 18. Acceptor类 =====
// ===== 19. TcpServer类 =====
// ===== 20. Util工具类 + 静态成员初始化 =====
// ===== 21. HttpRecvStatu枚举 =====
// ===== 22. HttpRequest类 =====
// ===== 23. HttpResponse类 =====
// ===== 24. HttpContext类 =====
// ===== 25. HttpServer类 =====

#endif

十、本篇总结

模块 功能
Util 工具函数:字符串分割、文件读写、URL编解码、MIME类型
HttpRequest 保存解析后的HTTP请求信息
HttpResponse 组织HTTP响应数据
HttpContext HTTP请求解析状态机,处理粘包
HttpServer 在TcpServer之上封装HTTP协议,支持路由和静态资源

至此,整个项目的核心代码全部完成!我们实现了:

bash 复制代码
底层 → 上层:
  Buffer → Socket → Channel → Poller → EventLoop
  → TimerWheel → LoopThread → LoopThreadPool
  → Connection → Acceptor → TcpServer
  → HttpRequest/Response/Context → HttpServer

💬 下一篇预告:性能测试与优化!用压测工具对服务器进行并发测试,分析瓶颈,进行优化。然后是项目总结篇,回顾整个架构设计。

相关推荐
云飞云共享云桌面19 小时前
非标自动化研发成本高?云飞云共享云桌面:1台主机=10台工作站,年省数十万。
大数据·运维·服务器·人工智能·自动化·云计算·电脑
ShineWinsu20 小时前
爬虫对抗:ZLibrary反爬机制实战分析技术文章大纲
c++
Linux运维技术栈20 小时前
生产环境Linux应用目录迁移至LVM独立分区 标准化实战方案
linux·运维·服务器·lvm·逻辑卷
tang7778920 小时前
小红书平台用什么代理 IP 比较好?2026年3月实测数据 + 选型推荐
网络·爬虫·python·网络协议·tcp/ip·数据挖掘·ip
feasibility.21 小时前
SSH Agent Forwarding 与 tmux 排障笔记
linux·运维·服务器·经验分享·笔记·ssh
charlie11451419121 小时前
通用GUI编程技术——Win32 原生编程实战(十六)——Visual Studio 资源编辑器使用指南
开发语言·c++·ide·学习·gui·visual studio·win32
ShawnLiaoking21 小时前
Linux 会话窗口常开
linux·运维·服务器
230万光年的思念21 小时前
向日葵远程控制ubuntu24一直显示连接中
linux
DpHard21 小时前
现代 C++ 中 push 接口为何提供 const T& 与 T&& 两个重载
c++
U-52184F691 天前
深度解析:从 Qt 的 Q_D 宏说起,C++ 工业级 SDK 是如何保证 ABI 稳定性的
数据库·c++·qt