HTTP服务器建立请求解析与响应构建:从基础架构到动态交互

目录

一、Http.hpp

模块1:头文件和命名空间

模块2:全局常量定义

模块3:HttpRequest类(HTTP请求解析)

模块4:HttpResponse类(HTTP响应构建)

模块5:函数指针定义和Http类

代码总结

1. 架构设计

2. 功能特点

3. 主要流程

4. 待改进点

1、总体结构

2、详细解析

1. 常量和头文件

2. HttpRequest类 - 请求解析

主要功能:

关键方法:

路径处理逻辑:

3. HttpResponse类 - 响应构建

主要功能:

关键方法:

文件类型识别:

4. Http类 - 主服务器

核心架构:

工作流程:

路由注册机制:

5. 请求处理流程

3、特色功能

1. 静态文件服务

2. 动态请求支持

3. 特殊处理

4、代码优缺点

优点:

局限性:

5、使用示例

二、Util.hpp

1、代码总结与注意事项

1. 类设计特点

2. 方法详细说明

ReadFileContent方法

ReadOneLine方法

FileSize方法

3. 设计模式

4. 使用示例

5. 改进建议

三、引入之前实现过的相关头文件

Common.hpp

InetAddr.hpp

Log.hpp

Mutex.hpp

Socket.hpp

TcpServer.hpp

四、相关的HTML文件

404.html

Login.html

Register.html

index.html

test.html

五、Main.cc

1、代码架构分析

1. 处理函数设计模式

2. 路由注册机制

3. 请求处理流程

2、重要技术细节

1. 查询参数处理

2. HTTP响应构建

3. 日志记录

3、运行服务器

编译和运行:

测试请求:

总结


一、Http.hpp

模块1:头文件和命名空间

// 防止头文件重复包含
#pragma once
// 包含自定义网络库的头文件
#include "Socket.hpp"      // Socket相关类
#include "TcpServer.hpp"   // TCP服务器类
#include "Util.hpp"        // 工具函数
#include "Log.hpp"         // 日志模块
// 包含标准库头文件
#include         // 输入输出流
#include           // 字符串类
#include           // 智能指针
#include          // 字符串流
#include       // 函数对象
#include    // 哈希表
// 使用命名空间
using namespace SocketModule;  // Socket模块命名空间
using namespace LogModule;     // 日志模块命名空间

模块2:全局常量定义

// HTTP协议相关的常量定义
const std::string gspace = " ";       // 空格
const std::string glinespace = "\r\n"; // 行分隔符(回车换行)
const std::string glinesep = ": ";    // 头部键值分隔符
// 服务器文件路径相关常量
const std::string webroot = "./wwwroot";  // 网站根目录
const std::string homepage = "index.html"; // 默认首页文件名
const std::string page_404 = "/404.html";  // 404错误页面路径

模块3:HttpRequest类(HTTP请求解析)

// HTTP请求类,用于解析客户端发来的HTTP请求
class HttpRequest
{
public:
    // 构造函数,初始化_is_interact为false(默认不是交互请求)
    HttpRequest() : _is_interact(false)
    {
    }
    // 序列化方法(当前未实现,返回空字符串)
    std::string Serialize()
    {
        return std::string();
    }
    // 解析请求行:例如 "GET / HTTP/1.1"
    void ParseReqLine(std::string &reqline)
    {
        // GET / HTTP/1.1
        std::stringstream ss(reqline);  // 创建字符串流
        ss >> _method >> _uri >> _version;  // 按空格分割,分别存入_method、_uri、_version
    }
    // 反序列化:将HTTP请求字符串解析为HttpRequest对象
    // reqstr是一个完整的http request string
    bool Deserialize(std::string &reqstr)
    {
        // 1. 提取请求行
        std::string reqline;  // 用于存储请求行
        // 使用Util工具读取一行(以glinespace为分隔符)
        bool res = Util::ReadOneLine(reqstr, &reqline, glinespace);
        // 记录调试日志
        LOG(LogLevel::DEBUG) << reqline;
        // 2. 对请求行进行反序列化
        ParseReqLine(reqline);  // 调用上面方法解析请求行
        // 处理URI:如果请求根目录,添加默认首页
        if (_uri == "/")
            _uri = webroot + _uri + homepage; // 转换为 ./wwwroot/index.html
        else
            _uri = webroot + _uri; // 转换为 ./wwwroot/a/b/c.html
        // 记录解析结果到日志
        LOG(LogLevel::DEBUG) << "_method: " << _method;
        LOG(LogLevel::DEBUG) << "_uri: " << _uri;
        LOG(LogLevel::DEBUG) << "_version: " << _version;
        // 检查URI中是否包含查询参数(?后面的部分)
        const std::string temp = "?";  // 查询参数分隔符
        auto pos = _uri.find(temp);    // 查找?的位置
        if (pos == std::string::npos)  // 如果没有找到?
        {
            return true;  // 直接返回,没有查询参数
        }
        // 如果有查询参数,进行分割处理
        // _uri示例: ./wwwroot/login?username=zhangsan&password=123456
        _args = _uri.substr(pos + temp.size());  // 提取查询参数部分
        _uri = _uri.substr(0, pos);              // 提取URI部分(去掉查询参数)
        _is_interact = true;                     // 标记为交互请求
        // 最终_uri格式: ./wwwroot/XXX.YYY
        return true;
    }
    // 获取URI的getter方法
    std::string Uri()
    {
        return _uri;
    }
    // 判断是否为交互请求的getter方法
    bool isInteract()
    {
        return _is_interact;
    }
    // 获取查询参数的getter方法
    std::string Args()
    {
        return _args;
    }
    // 析构函数
    ~HttpRequest()
    {
    }
private:
    // HTTP请求行部分
    std::string _method;   // 请求方法(GET/POST等)
    std::string _uri;      // 请求的资源路径
    std::string _version;  // HTTP协议版本
    // HTTP头部字段(未完全实现)
    std::unordered_map _headers;
    std::string _blankline;  // 空行
    std::string _text;       // 请求体(如果有)
    // 自定义字段
    std::string _args;       // 查询参数
    bool _is_interact;       // 是否为交互请求的标志
};

基本用法:

#include 
#include 
// 假设有相关的依赖文件
#include "httpreq.h"
// 全局变量定义(实际应该在合适的地方定义)
std::string webroot = "./wwwroot/";
std::string homepage = "index.html";
int main() {
    // 创建一个HttpRequest对象
    HttpRequest req;
    // 准备一个HTTP请求字符串
    std::string http_request =
        "GET / HTTP/1.1\r\n"
        "Host: localhost:8080\r\n"
        "User-Agent: Mozilla/5.0\r\n"
        "\r\n";
    // 解析HTTP请求
    bool success = req.Deserialize(http_request);
    if (success) {
        // 获取解析后的URI
        std::cout << "请求URI: " << req.Uri() << std::endl;
        // 检查是否是交互请求
        if (req.isInteract()) {
            std::cout << "这是交互请求" << std::endl;
            std::cout << "查询参数: " << req.Args() << std::endl;
        } else {
            std::cout << "这是静态文件请求" << std::endl;
        }
    }
    return 0;
}

模块4:HttpResponse类(HTTP响应构建)

// HTTP响应类,用于构建发送给客户端的HTTP响应
class HttpResponse
{
public:
    // 构造函数,初始化空行和HTTP版本
    HttpResponse() : _blankline(glinespace), _version("HTTP/1.0")
    {
    }
    // 序列化:将HttpResponse对象转换为HTTP响应字符串
    std::string Serialize()
    {
        // 构建状态行:HTTP/1.0 200 OK\r\n
        std::string status_line = _version + gspace + std::to_string(_code) + gspace + _desc + glinespace;
        // 构建响应头
        std::string resp_header;
        for (auto &header : _headers)
        {
            // 每个头部格式:key: value\r\n
            std::string line = header.first + glinesep + header.second + glinespace;
            resp_header += line;
        }
        // 组合:状态行 + 响应头 + 空行 + 响应体
        return status_line + resp_header + _blankline + _text;
    }
    // 设置目标文件路径
    void SetTargetFile(const std::string &target)
    {
        _targetfile = target;
    }
    // 设置HTTP状态码和对应的描述
    void SetCode(int code)
    {
        _code = code;
        switch (_code)
        {
        case 200:
            _desc = "OK";
            break;
        case 404:
            _desc = "Not Found";
            break;
        case 301:
            _desc = "Moved Permanently";
            break;
        case 302:
            _desc = "See Other";
            break;
        default:
            break;
        }
    }
    // 设置HTTP响应头(如果已存在则不设置)
    void SetHeader(const std::string &key, const std::string &value)
    {
        auto iter = _headers.find(key);
        if (iter != _headers.end())  // 如果key已存在
            return;                   // 直接返回,不覆盖
        _headers.insert(std::make_pair(key, value));  // 插入新的键值对
    }
    // 根据文件后缀名确定Content-Type
    std::string Uri2Suffix(const std::string &targetfile)
    {
        // ./wwwroot/a/b/c.html
        auto pos = targetfile.rfind(".");  // 从后往前查找最后一个.
        if (pos == std::string::npos)      // 如果没有找到后缀名
        {
            return "text/html";            // 默认返回text/html
        }
        std::string suffix = targetfile.substr(pos);  // 提取后缀名
        if (suffix == ".html" || suffix == ".htm")
            return "text/html";
        else if (suffix == ".jpg")
            return "image/jpeg";
        else if (suffix == "png")
            return "image/png";
        else
            return "";
    }
    // 构建HTTP响应
    bool MakeResponse()
    {
        // 特殊处理:忽略favicon.ico请求
        if (_targetfile == "./wwwroot/favicon.ico")
        {
            LOG(LogLevel::DEBUG) << "用户请求: " << _targetfile << "忽略它";
            return false;  // 返回false表示不发送响应
        }
        // 重定向测试:如果请求redir_test,返回301重定向
        if (_targetfile == "./wwwroot/redir_test")
        {
            SetCode(301);                            // 设置状态码301
            SetHeader("Location", "https://www.qq.com/");  // 设置重定向地址
            return true;
        }
        // 读取文件内容
        int filesize = 0;
        bool res = Util::ReadFileContent(_targetfile, &_text);  // 读取目标文件
        if (!res)  // 如果文件读取失败(文件不存在)
        {
            _text = "";
            LOG(LogLevel::WARNING) << "client want get : " << _targetfile << " but not found";
            // 设置404状态
            SetCode(404);
            // 加载404错误页面
            _targetfile = webroot + page_404;              // 设置404页面路径
            filesize = Util::FileSize(_targetfile);        // 获取404页面大小
            Util::ReadFileContent(_targetfile, &_text);    // 读取404页面内容
            // 设置响应头
            std::string suffix = Uri2Suffix(_targetfile);  // 根据后缀确定Content-Type
            SetHeader("Content-Type", suffix);
            SetHeader("Content-Length", std::to_string(filesize));
            // 注释掉的302重定向示例
            // SetCode(302);
            // SetHeader("Location", "http://8.137.19.140:8080/404.html");
            // return true;
        }
        else  // 文件读取成功
        {
            LOG(LogLevel::DEBUG) << "读取文件: " << _targetfile;
            // 设置200状态
            SetCode(200);
            // 设置响应头
            filesize = Util::FileSize(_targetfile);
            std::string suffix = Uri2Suffix(_targetfile);
            SetHeader("Conent-Type", suffix);
            SetHeader("Content-Length", std::to_string(filesize));
            SetHeader("Set-Cookie", "username=zhangsan;");  // 设置Cookie
            // SetHeader("Set-Cookie", "passwd=123456;");
        }
        return true;
    }
    // 设置响应体的文本内容
    void SetText(const std::string &t)
    {
        _text = t;
    }
    // 反序列化方法(当前未实现,直接返回true)
    bool Deserialize(std::string &reqstr)
    {
        return true;
    }
    // 析构函数
    ~HttpResponse() {}
    // 响应字段(为了方便调试,设为public,实际应该为private)
public:
    std::string _version;  // HTTP版本
    int _code;             // 状态码,如404
    std::string _desc;     // 状态描述,如"Not Found"
    std::unordered_map _headers;  // 响应头
    std::vector cookie;                       // Cookie列表(未使用)
    std::string _blankline;                                // 空行
    std::string _text;                                     // 响应体
    // 其他属性
    std::string _targetfile;  // 目标文件路径
};

基本使用(返回静态文件):

#include 
#include 
// 假设有相关的依赖文件
#include "httpres.h"
// 全局变量定义
std::string webroot = "./wwwroot/";
std::string homepage = "index.html";
std::string page_404 = "404.html";
int main() {
    // 创建一个HttpResponse对象
    HttpResponse response;
    // 设置要返回的目标文件
    response.SetTargetFile("./wwwroot/about.html");
    // 构建HTTP响应(会自动设置状态码、Content-Type等)
    bool shouldSend = response.MakeResponse();
    if (shouldSend) {
        // 序列化为HTTP响应字符串
        std::string http_response = response.Serialize();
        std::cout << "生成的HTTP响应:" << std::endl;
        std::cout << http_response << std::endl;
        // 在实际服务器中,可以通过socket发送这个字符串
        // send(client_socket, http_response.c_str(), http_response.size(), 0);
    } else {
        std::cout << "不需要发送响应(如favicon.ico)" << std::endl;
    }
    return 0;
}

模块5:函数指针定义和Http类

// 定义HTTP处理函数的类型:接受HttpRequest和HttpResponse的引用
using http_func_t = std::function;
// Http服务器主类
// 功能:
// 1. 返回静态资源
// 2. 提供动态交互的能力
class Http
{
public:
    // 构造函数,创建TCP服务器
    Http(uint16_t port) : tsvrp(std::make_unique(port))
    {
    }
    // 处理HTTP请求的核心方法
    void HandlerHttpRquest(std::shared_ptr &sock, InetAddr &client)
    {
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求(实际可能有bug)
        // tcp是面向字节流的,这里假设一次recv就读取到完整请求
        int n = sock->Recv(&httpreqstr);  // 从socket接收HTTP请求字符串
        if (n > 0)  // 如果接收到了数据
        {
            // 打印原始请求(调试用)
            std::cout << "##########################" << std::endl;
            std::cout << httpreqstr;
            std::cout << "##########################" << std::endl;
            // 对报文完整性进行审核 -- 缺(TODO:需要实现)
            // 创建请求和响应对象
            HttpRequest req;
            HttpResponse resp;
            // 解析HTTP请求
            req.Deserialize(httpreqstr);
            if (req.isInteract())  // 如果是交互请求(带查询参数)
            {
                // _uri: ./wwwroot/login
                if (_route.find(req.Uri()) == _route.end())  // 路由不存在
                {
                    // 处理路由不存在的情况(TODO:实现302重定向等)
                    // SetCode(302)
                }
                else  // 路由存在
                {
                    // 调用注册的处理函数
                    _route[req.Uri()](req, resp);
                    // 序列化响应并发送
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }
            else  // 静态资源请求
            {
                // 设置目标文件路径
                resp.SetTargetFile(req.Uri());
                // 构建响应
                if (resp.MakeResponse())
                {
                    // 序列化并发送响应
                    std::string response_str = resp.Serialize();
                    sock->Send(response_str);
                }
            }
        }
// 调试模式代码块
// #ifndef DEBUG
// #define DEBUG
#ifdef DEBUG
        // 收到请求
        std::string httpreqstr;
        // 假设:概率大,读到了完整的请求
        sock->Recv(&httpreqstr);  // 接收请求
        std::cout << httpreqstr;  // 打印请求
        // 直接构建http应答. 内存级别+固定
        HttpResponse resp;
        resp._version = "HTTP/1.1";
        resp._code = 200;  // success
        resp._desc = "OK";
        std::string filename = webroot + homepage;  // "./wwwroot/index.html";
        // 读取首页文件内容
        bool res = Util::ReadFileContent(filename, &(resp._text));
        (void)res;  // 避免未使用警告
        // 序列化并发送响应
        std::string response_str = resp.Serialize();
        sock->Send(response_str);
#endif
        // 对请求字符串,进行反序列化(已在上面的if块中处理)
    }
    // 启动HTTP服务器
    void Start()
    {
        // 启动TCP服务器,并绑定请求处理函数
        tsvrp->Start([this](std::shared_ptr &sock, InetAddr &client)
                     { this->HandlerHttpRquest(sock, client); });
    }
    // 注册服务(动态请求处理)
    void RegisterService(const std::string name, http_func_t h)
    {
        // 构建完整路径键:./wwwroot/login
        std::string key = webroot + name;
        // 检查路由是否已存在
        auto iter = _route.find(key);
        if (iter == _route.end())  // 如果不存在
        {
            _route.insert(std::make_pair(key, h));  // 插入路由
        }
    }
    // 析构函数
    ~Http()
    {
    }
private:
    std::unique_ptr tsvrp;  // TCP服务器实例(智能指针管理)
    std::unordered_map _route;  // 路由表
};
  • 完整读取请求报头后,识别空行作为分隔符

  • 对报头进行解析,提取关键属性:Content-Length(表示有效载荷的数据长度)

  • 根据获取的Content-Length值,从后续内容中准确截取指定长度的有效载荷数据

基本用法(启动HTTP服务器)

#include 
#include "http.h"  // 包含Http类头文件
int main() {
    // 1. 创建HTTP服务器,监听8080端口
    Http server(8080);
    // 2. 启动服务器
    server.Start();
    // 服务器开始运行,监听连接并处理请求
    return 0;
}

注册动态路由(处理表单提交)

#include 
#include "http.h"
// 登录处理函数
void HandleLogin(HttpRequest &req, HttpResponse &resp) {
    std::cout << "处理登录请求" << std::endl;
    // 获取查询参数(如:username=zhangsan&password=123456)
    std::string args = req.Args();
    // 这里可以解析参数,验证用户名密码...
    // 简单示例:直接返回欢迎信息
    resp.SetCode(200);
    resp.SetHeader("Content-Type", "text/html");
    std::string response_body = R"(
        
            登录成功
            
                

欢迎登录!

参数: )" + args + R"(

)"; resp.SetText(response_body); } // 搜索处理函数 void HandleSearch(HttpRequest &req, HttpResponse &resp) { resp.SetCode(200); resp.SetHeader("Content-Type", "text/html"); resp.SetText("

搜索结果页面

查询参数: " + req.Args() + "

"); } int main() { // 创建HTTP服务器 Http server(8080); // 注册动态路由(注意:路径不要带webroot前缀) server.RegisterService("/login", HandleLogin); // 对应 ./wwwroot/login server.RegisterService("/search", HandleSearch); // 对应 ./wwwroot/search // 启动服务器 server.Start(); return 0; }

代码总结

这是一个简单的HTTP服务器实现,主要特点包括:

1. 架构设计

  • 使用面向对象设计,分离请求解析(HttpRequest)和响应构建(HttpResponse)

  • 支持静态文件服务和动态路由处理

  • 基于TcpServer实现底层网络通信

2. 功能特点

  • 静态文件服务:自动处理HTML、图片等静态资源

  • 动态路由:支持注册自定义处理函数

  • 错误处理:自动处理404错误,返回错误页面

  • 重定向支持:支持301/302重定向

  • Cookie支持:可设置Cookie

3. 主要流程

  1. 接收HTTP请求字符串

  2. 解析请求行和URI

  3. 判断是否为交互请求(带查询参数)

  4. 交互请求:查找路由并调用对应处理函数

  5. 静态请求:读取文件并构建响应

  6. 发送HTTP响应

4. 待改进点

  • 请求完整性验证缺失

  • 请求头解析不完整

  • 并发处理能力有限

  • 安全性考虑不足

  • POST请求体处理未实现

        这个实现是一个教学级别的HTTP服务器,适合理解HTTP协议基本原理,但在生产环境中需要更完善的实现。这份代码是一个简单的HTTP服务器实现,支持静态资源服务和基本的动态请求处理。

1、总体结构

这个HTTP服务器由以下几个核心类组成:

  1. HttpRequest - 解析HTTP请求

  2. HttpResponse - 构建HTTP响应

  3. Http - 主服务器类,协调请求和响应

2、详细解析

1. 常量和头文件

const std::string webroot = "./wwwroot";      // 网站根目录
const std::string homepage = "index.html";    // 默认首页
const std::string page_404 = "/404.html";     // 404错误页面
  • webroot:所有静态文件的存储目录

  • 使用#pragma once防止头文件重复包含

2. HttpRequest类 - 请求解析

主要功能:
  • 解析HTTP请求行:从GET / HTTP/1.1中提取方法、URI、版本

  • 处理查询参数:支持URL中的?参数

  • 路径补全:将相对路径转换为绝对路径

关键方法:
// 示例:解析请求行
// 输入:"GET /login?user=admin HTTP/1.1"
// 输出:_method="GET", _uri="./wwwroot/login", _args="user=admin"
bool Deserialize(std::string &reqstr) {
    // 1. 读取请求行
    // 2. 解析方法、URI、版本
    // 3. 处理默认页面(/ -> /index.html)
    // 4. 分离查询参数
}
路径处理逻辑:
  • 请求/ → ./wwwroot/index.html

  • 请求/about.html → ./wwwroot/about.html

  • 请求/login?user=admin → ./wwwroot/login + args="user=admin"

3. HttpResponse类 - 响应构建

主要功能:
  • 状态码管理:自动设置状态描述

  • 头部管理:添加HTTP头部

  • 文件服务:读取并返回静态文件

  • 重定向支持:301/302重定向

关键方法:
std::string Serialize() {
    // 构建完整的HTTP响应:
    // 状态行: HTTP/1.0 200 OK\r\n
    // 头部: Content-Type: text/html\r\n
    //        Content-Length: 1234\r\n
    // 空行: \r\n
    // 正文: ...
}
bool MakeResponse() {
    // 特殊文件处理(如favicon.ico)
    // 重定向测试页面
    // 文件存在性检查
    // 自动设置Content-Type和Content-Length
}
文件类型识别:
// 根据文件后缀设置Content-Type
.html/.htm → "text/html"
.jpg → "image/jpeg"
.png → "image/png"

4. Http类 - 主服务器

核心架构:
class Http {
private:
    std::unique_ptr tsvrp;  // TCP服务器
    std::unordered_map _route;  // 路由表
};
工作流程:
  1. 接收请求:通过TcpServer接收HTTP原始字符串

  2. 解析请求:使用HttpRequest解析

  3. 路由分发

    • 静态资源 → 直接返回文件

    • 动态请求 → 调用注册的处理函数

  4. 构建响应:使用HttpResponse构建HTTP响应

  5. 发送响应:通过socket发送给客户端

路由注册机制:
// 注册动态处理函数
void RegisterService(const std::string name, http_func_t h) {
    // 示例:RegisterService("/login", LoginHandler)
    // 内部存储:"./wwwroot/login" -> LoginHandler
}

5. 请求处理流程

void HandlerHttpRquest(std::shared_ptr &sock, InetAddr &client) {
    // 1. 接收HTTP请求字符串
    // 2. 创建HttpRequest和HttpResponse对象
    // 3. 解析请求
    if (req.isInteract()) {  // 是否有查询参数(动态请求)
        // 查找路由表,调用对应的处理函数
        _route[req.Uri()](req, resp);
    } else {  // 静态资源请求
        // 设置目标文件,生成响应
        resp.SetTargetFile(req.Uri());
        resp.MakeResponse();
    }
    // 4. 序列化并发送响应
    sock->Send(resp.Serialize());
}

3、特色功能

1. 静态文件服务

  • 自动服务./wwwroot目录下的所有文件

  • 自动识别文件类型

  • 404错误处理

2. 动态请求支持

// 可以注册这样的处理函数:
void LoginHandler(HttpRequest &req, HttpResponse &resp) {
    std::string args = req.Args();  // "username=admin&password=123"
    // 处理登录逻辑...
    resp.SetText("Login Success!");
    resp.SetCode(200);
}
// 注册路由
server.RegisterService("/login", LoginHandler);

3. 特殊处理

  • 忽略favicon.ico:避免浏览器重复请求

  • 重定向测试/redir_test重定向到QQ官网

  • Cookie设置:示例中设置了Set-Cookie头部

4、代码优缺点

优点:

  1. 模块化设计:请求、响应、服务器分离

  2. 易于扩展:通过路由表支持动态处理

  3. 错误处理:基本的404页面支持

  4. 调试友好:详细的日志输出

局限性:

  1. HTTP协议不完整:只实现了基本功能

  2. 请求解析简单:未完整解析HTTP头部

  3. 内存问题:可能处理大文件时内存占用高

  4. 性能问题:单线程,未使用epoll等高效I/O

5、使用示例

int main() {
    Http server(8080);  // 监听8080端口
    // 注册动态路由
    server.RegisterService("/api/test", [](HttpRequest &req, HttpResponse &resp){
        resp.SetText("Dynamic Response");
        resp.SetCode(200);
    });
    server.Start();  // 启动服务器
    return 0;
}

        这是一个教学级别的HTTP服务器实现,适合学习HTTP协议和网络编程的基本原理。在生产环境中,建议使用更成熟的开源方案(如Nginx、Apache)。


二、Util.hpp

Util工具类代码详细讲解,这个工具类提供了文件读取和处理的常用功能,我将逐行详细讲解:

// 防止头文件重复包含
#pragma once
// 包含标准库头文件
#include     // 输入输出流
#include      // 文件流操作
#include       // 字符串类
// 工具类 - 提供文件操作相关的静态方法
class Util
{
public:
    // 读取文件内容到字符串中
    // 参数:
    //   filename - 要读取的文件路径
    //   out      - 输出参数,用于存储读取的文件内容
    // 返回值:读取成功返回true,失败返回false
    static bool ReadFileContent(const std::string &filename /*std::vector*/, std::string *out)
    {
        // version1: 原始版本,以文本方式读取文件(注释掉的代码)
        // 问题:对于二进制文件(如图片)处理不正确
        // std::ifstream in(filename);  // 创建输入文件流
        // if (!in.is_open())           // 检查文件是否成功打开
        // {
        //     return false;            // 打开失败返回false
        // }
        // std::string line;            // 临时存储每行内容
        // while(std::getline(in, line)) // 逐行读取文件
        // {
        //     *out += line;            // 将每行内容添加到输出字符串
        // }
        // in.close();                  // 关闭文件流
        // version2 : 以二进制方式进行读取(当前使用的版本)
        // 优点:可以正确读取文本文件和二进制文件
        // 首先获取文件大小
        int filesize = FileSize(filename);  // 调用FileSize方法获取文件大小
        // 如果文件大小大于0,说明文件存在且有内容
        if(filesize > 0)
        {
            std::ifstream in(filename);     // 创建输入文件流(默认文本模式)
            if(!in.is_open())               // 检查文件是否成功打开
                return false;               // 打开失败返回false
            // 调整输出字符串的大小为文件大小
            // 注意:这里有一个潜在问题,c_str()返回的是const char*,不应该被修改
            // 实际上应该使用:out->resize(filesize); 然后使用&(*out)[0]获取可写指针
            out->resize(filesize);          // 调整字符串大小
            // 读取整个文件内容到字符串中
            // 这里使用了c_str()获取字符串的内部缓冲区指针,然后强制转换为char*
            // 更安全的写法:in.read(&(*out)[0], filesize);
            in.read((char*)(out->c_str()), filesize);  // 读取filesize字节到字符串中
            in.close();                     // 关闭文件流
        }
        else  // 文件大小为0或负数(文件不存在或为空)
        {
            return false;                   // 返回false表示读取失败
        }
        return true;  // 读取成功返回true
    }
    // 从大字符串中读取一行(以指定分隔符为界)
    // 参数:
    //   bigstr - 输入的大字符串,读取后会被修改(移除已读取的部分)
    //   out    - 输出参数,存储读取的一行内容
    //   sep    - 行分隔符,通常是"\r\n"
    // 返回值:成功读取一行返回true,否则返回false
    static bool ReadOneLine(std::string &bigstr, std::string *out, const std::string &sep/*\r\n*/)
    {
        // 在bigstr中查找分隔符的位置
        auto pos = bigstr.find(sep);
        // 如果没有找到分隔符,说明没有完整的一行
        if(pos == std::string::npos)
            return false;  // 返回false
        // 从开始到分隔符位置就是一行内容
        *out = bigstr.substr(0, pos);
        // 从bigstr中删除已经读取的部分(包括分隔符)
        // 这样可以继续读取下一行
        bigstr.erase(0, pos + sep.size());
        return true;  // 成功读取一行返回true
    }
    // 获取文件大小(以字节为单位)
    // 参数:
    //   filename - 要检查的文件路径
    // 返回值:文件大小(字节数),文件不存在或无法打开返回-1
    static int FileSize(const std::string &filename)
    {
        // 以二进制模式打开文件
        // std::ios::binary 确保以二进制方式读取,避免文本模式下的转换
        std::ifstream in(filename, std::ios::binary);
        // 检查文件是否成功打开
        if(!in.is_open())
            return -1;  // 打开失败返回-1
        // 将文件指针移动到文件末尾
        // 参数说明:
        //   0 - 偏移量
        //   in.end - 相对于文件末尾
        in.seekg(0, in.end);
        // 获取当前位置(即文件大小)
        // tellg()返回当前读取位置
        int filesize = in.tellg();
        // 将文件指针移回文件开头
        // 这样不会影响后续的读取操作(如果有的话)
        in.seekg(0, in.beg);
        in.close();  // 关闭文件流
        return filesize;  // 返回文件大小
    }
};

1、代码总结与注意事项

1. 类设计特点

  • 静态工具类:所有方法都是静态的,不需要创建Util对象

  • 功能聚焦:专注于文件操作相关的工具函数

  • 错误处理:每个方法都有明确的成功/失败返回值

2. 方法详细说明

ReadFileContent方法
  • 功能:读取整个文件内容到字符串中

  • 改进:从文本模式改为二进制模式,支持图片等二进制文件

  • 潜在问题

    // 不安全的写法:
    in.read((char*)(out->c_str()), filesize);
    // 更安全的写法:
    out->resize(filesize);
    in.read(&(*out)[0], filesize);  // 或者 in.read(out->data(), filesize);

    因为c_str()返回的是const char*,不应该被写入。虽然在某些实现中可能工作,但不是标准做法。

ReadOneLine方法
  • 功能:从大字符串中提取一行(基于自定义分隔符)

  • 应用场景:HTTP协议解析中读取请求行和头部

  • 副作用:会修改输入字符串bigstr,移除已读取的部分

FileSize方法
  • 功能:获取文件大小

  • 实现原理:使用seekgtellg组合

  • 二进制模式:使用std::ios::binary确保正确计算大小

3. 设计模式

  • 工具类模式:提供一组相关的静态方法

  • 无状态:不维护任何成员变量,所有方法都是纯函数

  • 参数设计

    • 使用std::string*作为输出参数(C++风格)

    • 使用引用参数std::string&允许修改原字符串

4. 使用示例

// 读取文件内容
std::string content;
if (Util::ReadFileContent("test.txt", &content)) {
    std::cout << "文件内容:" << content << std::endl;
}
// 获取文件大小
int size = Util::FileSize("test.txt");
if (size > 0) {
    std::cout << "文件大小:" << size << "字节" << std::endl;
}
// 逐行读取
std::string text = "第一行\r\n第二行\r\n第三行";
std::string line;
while (Util::ReadOneLine(text, &line, "\r\n")) {
    std::cout << "行内容:" << line << std::endl;
}

5. 改进建议

  1. 异常处理:考虑添加异常处理机制

  2. 性能优化:大文件读取可以考虑分块读取

  3. 跨平台:路径分隔符处理(Windows使用\,Unix使用/

  4. 编码处理:考虑不同编码格式的文件读取

  5. 内存安全:修正ReadFileContent中的潜在问题

这个工具类是HTTP服务器的重要组成部分,为文件操作提供了基础支持。


三、引入之前实现过的相关头文件

Common.hpp

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR,
    OPEN_ERR
};
class NoCopy
{
public:
    NoCopy(){}
    ~NoCopy(){}
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator = (const NoCopy&) = delete;
};
#define CONV(addr) ((struct sockaddr*)&addr)

InetAddr.hpp

#pragma once
#include "Common.hpp"
// 网络地址和主机地址之间进行转换的类
class InetAddr
{
public:
    InetAddr() {}
    InetAddr(struct sockaddr_in &addr)
    {
        SetAddr(addr);
    }
    InetAddr(const std::string &ip, uint16_t port) : _ip(ip), _port(port)
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
        // local.sin_addr.s_addr = inet_addr(_ip.c_str()); // TODO
    }
    InetAddr(uint16_t port) : _port(port), _ip()
    {
        // 主机转网络
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_addr.s_addr = INADDR_ANY;
        _addr.sin_port = htons(_port);
    }
    void SetAddr(struct sockaddr_in &addr)
    {
        _addr = addr;
        // 网络转主机
        _port = ntohs(_addr.sin_port); // 从网络中拿到的!网络序列
        // _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
        _ip = ipbuffer;
    }
    uint16_t Port() { return _port; }
    std::string Ip() { return _ip; }
    const struct sockaddr_in &NetAddr() { return _addr; }
    const struct sockaddr *NetAddrPtr()
    {
        return CONV(_addr);
    }
    socklen_t NetAddrLen()
    {
        return sizeof(_addr);
    }
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr()
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InetAddr()
    {
    }
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

Log.hpp

#ifndef __LOG_HPP__
#define __LOG_HPP__
#include 
#include 
#include 
#include  //C++17
#include 
#include 
#include 
#include 
#include 
#include "Mutex.hpp"
namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";
    // 策略模式,C++多态特性
    // 2. 刷新策略 a: 显示器打印 b:向指定的文件写入
    //  刷新策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };
    // 显示器打印日志的策略 : 子类
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::cout << message << gsep;
        }
        ~ConsoleLogStrategy()
        {
        }
    private:
        Mutex _mutex;
    };
    // 文件打印日志的策略 : 子类
    const std::string defaultpath = "/var/log/";
    const std::string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path),
              _file(file)
        {
            LockGuard lockguard(_mutex);
            if (std::filesystem::exists(_path))
            {
                return;
            }
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << '\n';
            }
        }
        void SyncLog(const std::string &message) override
        {
            LockGuard lockguard(_mutex);
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file; // "./log/" + "my.log"
            std::ofstream out(filename, std::ios::app);                              // 追加写入的 方式打开
            if (!out.is_open())
            {
                return;
            }
            out << message << gsep;
            out.close();
        }
        ~FileLogStrategy()
        {
        }
    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件本身
        Mutex _mutex;
    };
    // 形成一条完整的日志&&根据上面的策略,选择不同的刷新方式
    // 1. 形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string Level2Str(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer),"%4d-%02d-%02d %02d:%02d:%02d",
            curr_tm.tm_year+1900,
            curr_tm.tm_mon+1,
            curr_tm.tm_mday,
            curr_tm.tm_hour,
            curr_tm.tm_min,
            curr_tm.tm_sec
        );
        return timebuffer;
    }
    // 1. 形成日志 && 2. 根据不同的策略,完成刷新
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique();
        }
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique();
        }
        // 表示的是未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()),
                  _level(level),
                  _pid(getpid()),
                  _src_name(src_name),
                  _line_number(line_number),
                  _logger(logger)
            {
                // 日志的左边部分,合并起来
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << Level2Str(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            // LogMessage() << "hell world" << "XXXX" << 3.14 << 1234
            template 
            LogMessage &operator<<(const T &info)
            {
                // a = b = c =d;
                // 日志的右半部分,可变的
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }
        private:
            std::string _curr_time;
            LogLevel _level;
            pid_t _pid;
            std::string _src_name;
            int _line_number;
            std::string _loginfo; // 合并之后,一条完整的信息
            Logger &_logger;
        };
        // 这里故意写成返回临时对象
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }
        ~Logger()
        {
        }
    private:
        std::unique_ptr _fflush_strategy;
    };
    // 全局日志对象
    Logger logger;
    // 使用宏,简化用户操作,获取文件名和行号
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()
}
#endif

Mutex.hpp

#pragma once
#include 
#include 
namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void Unlock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };
    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex):_mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.Unlock();
        }
    private:
        Mutex &_mutex;
    };
}

Socket.hpp

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
namespace SocketModule
{
    using namespace LogModule;
    const static int gbacklog = 16;
    // 模版方法模式
    // 基类socket, 大部分方法,都是纯虚方法
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void SocketOrDie() = 0;
        virtual void BindOrDie(uint16_t port) = 0;
        virtual void ListenOrDie(int backlog) = 0;
        virtual std::shared_ptr Accept(InetAddr *client) = 0;
        virtual void Close() = 0;
        virtual int Recv(std::string *out) = 0;
        virtual int Send(const std::string &message) = 0;
        virtual int Connect(const std::string &server_ip, uint16_t port) = 0;
    public:
        void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog)
        {
            SocketOrDie();
            BindOrDie(port);
            ListenOrDie(backlog);
        }
        void BuildTcpClientSocketMethod()
        {
            SocketOrDie();
        }
        // void BuildUdpSocketMethod()
        // {
        //     SocketOrDie();
        //     BindOrDie();
        // }
    };
    const static int defaultfd = -1;
    class TcpSocket : public Socket
    {
    public:
        TcpSocket() : _sockfd(defaultfd)
        {
        }
        TcpSocket(int fd) : _sockfd(fd)
        {
        }
        ~TcpSocket() {}
        void SocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(LogLevel::FATAL) << "socket error";
                exit(SOCKET_ERR);
            }
            LOG(LogLevel::INFO) << "socket success";
        }
        void BindOrDie(uint16_t port) override
        {
            InetAddr localaddr(port);
            int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen());
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "bind error";
                exit(BIND_ERR);
            }
            LOG(LogLevel::INFO) << "bind success";
        }
        void ListenOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
            {
                LOG(LogLevel::FATAL) << "listen error";
                exit(LISTEN_ERR);
            }
            LOG(LogLevel::INFO) << "listen success";
        }
        std::shared_ptr Accept(InetAddr *client) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int fd = ::accept(_sockfd, CONV(peer), &len);
            if (fd < 0)
            {
                LOG(LogLevel::WARNING) << "accept warning ...";
                return nullptr; // TODO
            }
            client->SetAddr(peer);
            return std::make_shared(fd);
        }
        // n == read的返回值
        int Recv(std::string *out) override
        {
            // 流式读取,不关心读到的是什么
            char buffer[4096*2];
            ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                *out += buffer; // 故意
            }
            return n;
        }
        int Send(const std::string &message) override
        {
            return send(_sockfd, message.c_str(), message.size(), 0);
        }
        void Close() override //??
        {
            if (_sockfd >= 0)
                ::close(_sockfd);
        }
        int Connect(const std::string &server_ip, uint16_t port) override
        {
            InetAddr server(server_ip, port);
            return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen());
        }
    private:
        int _sockfd; // _sockfd , listensockfd, sockfd;
    };
    // class UdpSocket : public Socket
    // {
    // };
}

TcpServer.hpp

#include "Socket.hpp"
#include 
#include 
#include 
#include 
using namespace SocketModule;
using namespace LogModule;
using ioservice_t = std::function &sock, InetAddr &client)>;
class TcpServer
{
public:
    TcpServer(uint16_t port) : _port(port),
                               _listensockptr(std::make_unique()),
                               _isrunning(false)
    {
        _listensockptr->BuildTcpSocketMethod(_port);
    }
    void Start(ioservice_t callback)
    {
        _isrunning = true;
        while (_isrunning)
        {
            InetAddr client;
            auto sock = _listensockptr->Accept(&client); // 1. 和client通信sockfd 2. client 网络地址
            if (sock == nullptr)
            {
                continue;
            }
            LOG(LogLevel::DEBUG) << "accept success ..." << client.StringAddr();
            // sock && client
            pid_t id = fork();
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error ...";
                // excepter(sock); //
                exit(FORK_ERR);
            }
            else if (id == 0)
            {
                // 子进程 -> listensock
                _listensockptr->Close();
                if (fork() > 0)
                    exit(OK);
                // 孙子进程在执行任务,已经是孤儿了
                callback(sock, client);
                sock->Close();
                exit(OK);
            }
            else
            {
                // 父进程 -> sock
                sock->Close();
                pid_t rid = ::waitpid(id, nullptr, 0);
                (void)rid;
            }
        }
        _isrunning = false;
    }
    ~TcpServer() {}
private:
    uint16_t _port;
    std::unique_ptr _listensockptr;
    bool _isrunning;
    //func_t excepter; // 服务器异常的回调
};

四、相关的HTML文件

404.html




    
    
    404 - 页面未找到
    


    

404

页面未找到

抱歉,您访问的页面不存在或已被移除。

您可以返回首页或检查URL是否正确。

Login.html




    
    
    Login Page
    


    

Register.html




    
    
    Register Page
    


    

Register

Login Register

index.html




    
    
    Default Home Page
    


    

Welcome to Our Website

Welcome to Our Default Home Page

This is a simple default home page template.

Introduction

This is a basic HTML template for a default home page. It includes a header, navigation bar, a welcome section, and a content area. You can customize this template to suit your needs.

查看板书

© 2025 Your Company Name. All rights reserved.

test.html




    
    
    Document


    点我


五、Main.cc

HTTP服务器主程序代码详细讲解,这是HTTP服务器的主程序文件,包含入口函数和示例处理函数:

// 包含HTTP服务器主类的头文件
#include "Http.hpp"
// Login函数:处理用户登录请求
// 参数:
//   req - HTTP请求对象,包含客户端请求信息
//   resp - HTTP响应对象,用于构建返回给客户端的响应
void Login(HttpRequest &req, HttpResponse &resp)
{
    // 从请求中获取查询参数(args()返回查询字符串,如"username=zhangsan&passwd=123456")
    // 注意:这里只是打印日志,实际上应该解析参数
    LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
    // 构建简单的响应文本
    // 这里直接拼接查询参数,实际应用中应该解析参数并进行验证
    std::string text = "hello: " + req.Args(); // 例如: "hello: username=zhangsan&password=123456"
    // TODO: 实际登录认证逻辑应该在这里实现
    // 1. 解析查询参数,获取用户名和密码
    // 2. 验证用户名密码是否正确
    // 3. 根据验证结果返回不同响应
    // 设置HTTP响应状态码为200(成功)
    resp.SetCode(200);
    // 设置响应头:Content-Type指定响应体类型为纯文本
    resp.SetHeader("Content-Type","text/plain");
    // 设置响应头:Content-Length指定响应体大小
    resp.SetHeader("Content-Length", std::to_string(text.size()));
    // 设置响应体内容
    resp.SetText(text);
}
// 以下是被注释掉的示例处理函数,展示了如何添加更多业务逻辑
// void Register(HttpRequest &req, HttpResponse &resp)
// {
//     // 注册功能处理函数
//     LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
//     std::string text = "hello: " + req.Args();
//
//     resp.SetCode(200);
//     resp.SetHeader("Content-Type","text/plain");
//     resp.SetHeader("Content-Length", std::to_string(text.size()));
//     resp.SetText(text);
// }
// void VipCheck(HttpRequest &req, HttpResponse &resp)
// {
//     // VIP检查功能处理函数
//     LOG(LogLevel::DEBUG) << req.Args() << ", 我们成功进入到了处理数据的逻辑";
//     std::string text = "hello: " + req.Args();
//     resp.SetCode(200);
//     resp.SetHeader("Content-Type","text/plain");
//     resp.SetHeader("Content-Length", std::to_string(text.size()));
//     resp.SetText(text);
// }
// void Search(HttpRequest &req, HttpResponse &resp)
// {
//     // 搜索功能处理函数
//     // 这里没有实现具体逻辑
// }
// HTTP服务器主入口函数
// 参数:
//   argc - 命令行参数个数
//   argv - 命令行参数数组
int main(int argc, char *argv[])
{
    // 检查命令行参数数量
    // 程序期望接收一个参数:端口号
    // 正确用法:./server 8080
    if(argc != 2)  // 如果不是2个参数(程序名+端口号)
    {
        // 打印使用说明
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        // 退出程序,使用预定义的错误码USAGE_ERR(可能在其他头文件中定义)
        exit(USAGE_ERR);
    }
    // 将命令行参数转换为端口号
    // argv[0] 是程序名,argv[1] 是端口号字符串
    uint16_t port = std::stoi(argv[1]);  // stoi将字符串转换为整数
    // 创建HTTP服务器实例
    // 使用智能指针管理,避免内存泄漏
    // std::make_unique是C++14特性,创建unique_ptr智能指针
    std::unique_ptr httpsvr = std::make_unique(port);
    // 注册服务(即注册动态请求处理函数)
    // 将URI路径与处理函数绑定
    // 当用户访问"/login"时,会调用Login函数处理
    httpsvr->RegisterService("/login", Login);
    // 以下是其他示例服务的注册(被注释掉了)
    // httpsvr->RegisterService("/register", Register);
    // httpsvr->RegisterService("/vip_check", VipCheck);
    // httpsvr->RegisterService("/s", Search);
    // httpsvr->RegisterService("/", Login);  // 根路径也可以绑定处理函数
    // 启动HTTP服务器
    // Start()方法会启动服务器并进入事件循环,等待客户端连接
    httpsvr->Start();
    // 程序结束,返回0表示正常退出
    // 注意:由于Start()方法可能会阻塞,这里的return只有在服务器停止后才会执行
    return 0;
}

1、代码架构分析

1. 处理函数设计模式

// 处理函数的标准签名
void HandlerName(HttpRequest &req, HttpResponse &resp)
{
    // 1. 从req中提取参数
    // 2. 执行业务逻辑
    // 3. 设置resp的各个部分
}

2. 路由注册机制

// 路由表结构
std::unordered_map _route;
// 注册路由
httpsvr->RegisterService("/login", Login);
// 内部实现:将"/login"映射到Login函数

3. 请求处理流程

客户端请求 → 服务器接收 → 解析请求 → 路由匹配 → 调用处理函数 → 构建响应 → 发送响应

2、重要技术细节

1. 查询参数处理

// 请求示例:GET /login?username=zhangsan&password=123456
// req.Args() 返回:"username=zhangsan&password=123456"
// 实际应用中需要解析这个字符串:
// std::string args = req.Args();
// 然后按'&'分割键值对,再按'='分割键和值

2. HTTP响应构建

// 一个完整的HTTP响应包含:
resp.SetCode(200);                    // 状态码
resp.SetHeader("Content-Type", ...);  // 内容类型
resp.SetHeader("Content-Length", ...);// 内容长度
resp.SetText(...);                    // 响应体

3. 日志记录

// 使用日志系统记录调试信息
LOG(LogLevel::DEBUG) << "日志信息";
// 有助于调试和监控服务器运行状态

3、运行服务器

使用tree命令查看当前项目的全局结构:

        可以看出,在浏览器端发起请求时,首页作为网站的入口点,整个站点结构呈现为多叉树形态。当用户点击链接时,浏览器会生成新的访问地址并触发二次请求。所有请求的资源都通过HTTP请求的URI进行标识。

编译和运行:

# 编译(假设使用g++)
g++ -std=c++14 -o httpserver main.cpp Http.cpp Util.cpp Socket.cpp TcpServer.cpp -lpthread
# 运行(在8080端口启动服务器)
./httpserver 8080

这里我们使用makefile文件来编译:

myhttp:Main.cc
	g++ -o $@ $^ -std=c++17
.PHONY:clean
clean:
	rm -f myhttp

编译完成后,我们运行服务端:

测试请求:

# 访问静态文件
curl http://113.45.79.2:8080/index.html

我们也可以直接通过浏览器来访问,这样更直观并符合HTML的演示:

# 访问动态接口
curl http://113.45.79.2:8080/login?username=zhangsan&password=123456

总结

这个HTTP服务器示例展示了:

  1. 基于TCP的HTTP服务器基本架构

  2. 静态文件服务和动态API的统一处理

  3. 简单的路由注册机制

  4. 请求/响应的完整处理流程

可以作为学习HTTP协议和服务器开发的入门示例,理解请求处理、响应构建的基本原理。

posted on 2026-01-04 19:17  ljbguanli  阅读(3)  评论(0)    收藏  举报