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

从零开始实现 C++ TinyWebServer 处理请求 HttpRequest类详解

文章目录

  • HTTP 请求报文
  • HttpRequest 类
  • 实现 Init() 函数
  • 实现 ParseRequestLine() 函数
  • 实现 ParseHeader() 函数
  • 实现 ParsePath() 函数
  • 实现 ParseBody() 函数
  • 实现 ParsePost() 函数
  • 实现 ParseFromUrlEncoded() 函数
  • 实现 UserVerify() 函数
  • 实现 Parse() 函数
  • HttpRequest 代码
  • HttpRequest 测试

从零开始实现 C++ TinyWebServer 项目总览
项目源码

HTTP 请求报文

 2025-02-27 200519.png

 2025-02-27 200654.png

一个典型的 HTTP 请求包含请求行、请求头和请求体(可选)。

  • 请求行:包含请求方法(如 GETPOST 等)、请求路径(如 /index.html)和 HTTP 版本(如 HTTP/1.1)。
  • 请求头:包含多个键值对,用于传递额外的信息,如 HostUser-Agent 等。
  • 请求体:通常在 POST 请求中使用,用于传递数据。

HttpRequest 类

在 Web 服务器中,HttpRequest类起着至关重要的作用,主要负责处理客户端发送的 HTTP 请求。具体来说,它承担了以下几个关键任务:

  1. 请求解析:接收客户端发送的 HTTP 请求报文,并对其进行解析,将请求报文中的各个部分(如请求行、请求头、请求体等)提取出来,以便后续处理。
  2. 状态管理:在解析过程中,通过状态机来管理解析的状态,确保解析过程的正确性和顺序性。
  3. 请求处理:根据解析结果,进行相应的处理,例如处理 GET 请求、POST 请求等,并根据请求的内容和服务器的配置,决定如何响应客户端。
class HttpRequest {
public:
    enum PARSE_STATE {
        REQUEST_LINE,
        HEADERS,
        BODY,
        FINISH
    };

    HttpRequest() { Init(); }
    ~HttpRequest() = default;

    void Init();
    bool Parse(Buffer& buff);

    std::string Method() const;
    std::string Path() const;
    std::string& Path();
    std::string Version() const;
    std::string GetPost(const std::string& key) const;
    std::string GetPost(const char* key) const;

    bool IsKeepAlive() const;

private:
    static int ConverHex(char ch);	// 16进制转10进制
    static bool UserVerify(const std::string& name, const std::string& pwd, bool is_login);	// 验证用户

    bool ParseRequestLine(const std::string& line);	// 解析请求行
    void ParseHeader(const std::string& line);		// 解析请求头
    void ParseBody(const std::string& line);		// 解析请求体

    void ParsePath();	// 解析请求路径
    void ParsePost();	// 解析Post事件
    void ParseFromUrlEncoded();	// 解析url

    static const std::unordered_set<std::string> DEFAULT_HTML;
    static const std::unordered_map<std::string, int> DEFAULT_HTML_TAG;

    PARSE_STATE state_;
    std::string method_;
    std::string path_;
    std::string version_;
    std::string body_;
    std::unordered_map<std::string, std::string> header_; // HTTP 请求的头部信息
    std::unordered_map<std::string, std::string> post_;   // POST 请求的数据
};

实现 Init() 函数

void HttpRequest::Init() {
    state_ = REQUEST_LINE;
    method_ = path_ = version_ = body_ = "";
    header_.clear();
    post_.clear();
}

实现 ParseRequestLine() 函数

  • 使用正则表达式^([^ ]*) ([^ ]*) HTTP/([^ ]*)$以及std::regex_match来匹配请求行。
  • 若匹配成功,将匹配结果分别存储到method_path_version_中,并将解析状态设置为HEADERS,表示接下来要解析请求头,返回true
bool HttpRequest::ParseRequestLine(const std::string& line) {
    // GET /index.html HTTP/1.1
    std::regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
    std::smatch match;
    // 匹配指定字符串整体是否符合
    if (std::regex_match(line, match, patten)) { 
        method_ = match[1];
        path_ = match[2];
        version_ = match[3];
        state_ = HEADERS;
        return true;
    } 
    LOG_ERROR("RequestLine Error"); 
    return false;
}

实现 ParseHeader() 函数

  • 如果路径为/,则将其修改为/index.html
  • 否则,检查路径是否在DEFAULT_HTML中,如果存在则在路径后面添加.html后缀。
void HttpRequest::ParseHeader(const std::string& line) {
    std::regex patten("^([^:]*): ?(.*)$");
    std::smatch match;
    if (std::regex_match(line, match, patten)) {
        header_[match[1]] = match[2];
    } else {
        state_ = BODY;
    }
}

实现 ParsePath() 函数

  • 如果路径为/,则将其修改为/index.html
  • 否则,检查路径是否在DEFAULT_HTML中,如果存在则在路径后面添加.html后缀
void HttpRequest::ParsePath() {
    if (path_ == "/") {
        path_ = "/index.html";
    } else {
        if (DEFAULT_HTML.find(path_) != DEFAULT_HTML.end())
            path_ += ".html";
    }
}

实现 ParseBody() 函数

  • 将请求体内容存储到body_中。
  • 调用ParsePost函数处理 POST 请求数据。
  • 将解析状态设置为FINISH,表示解析完成。
void HttpRequest::ParseBody(const std::string& line) {
    body_ = line;
    ParsePost();    
    state_ = FINISH;
    LOG_DEBUG("Body: %s, len: %d", line.c_str(), line.size());
}

实现 ParsePost() 函数

  • 检查请求方法是否为POST,并且请求头中的Content-Type是否为application/x-www-form-urlencoded
  • 如果满足条件,调用ParseFromUrlencoded函数解析 URL 编码的请求体。
  • 检查路径是否为登录或注册相关路径,如果是则调用UserVerify函数,进行用户验证,根据验证结果设置相应的跳转路径。
void HttpRequest::ParsePost() {
    if (method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {
        ParseFromUrlEncoded();
        if (DEFAULT_HTML_TAG.count(path_)) { // 登录/注册
            int tag = DEFAULT_HTML_TAG.find(path_)->second;
            LOG_DEBUG("Tag: %d", tag);
            if (tag == 0 || tag == 1) {
                bool is_login = (tag == 1);
                if (UserVerify(post_["username"], post_["password"], is_login))
                    path_ = "/welcome.html";
                else
                    path_ = "/error.html";
            }
        }
    }
}

实现 ParseFromUrlEncoded() 函数

  • 首先检查请求体是否为空,如果为空则直接返回。
  • 遍历请求体字符串,根据不同的字符进行不同的处理:
    • 遇到=,将之前的字符作为键存储到key中,并更新起始位置j
    • 遇到&,将之前的字符作为值存储到value中,并将键值对存储到post_容器中,同时更新起始位置j
    • 遇到+,将其替换为空格。
    • 遇到%,将其后的两个十六进制字符转换为十进制整数,并更新请求体内容。
  • 最后处理剩余的键值对。
void HttpRequest::ParseFromUrlEncoded() {
    if (body_.size() == 0)
        return;
    
    std::string key, value;
    int num = 0;
    int n = body_.size();
    int i = 0, j = 0;

    while (i < n) {
        char ch = body_[i];
        // username=john%20doe&password=123456
        switch (ch) {
        case '=': // 获取键值对
            key = body_.substr(j, i - j); 
            j = i + 1;
            break;
        case '&': // 获取键值对
            value = body_.substr(j, i - j);
            j = i + 1;
            post_[key] = value;
            LOG_DEBUG("%s = %s", key.c_str(), value.c_str());
            break;
        case '+': // 替换为空格
            body_[i] = ' ';
            break;
        case '%':
            num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);
            body_[i + 2] = num % 10 + '0';
            body_[i + 1] = num / 10 + '0';
            i += 2;
            break;
        default:
            break;
        }
        i++;
    }
    assert(j <= i);
    if (post_.count(key) == 0 && j < i) {
        value = body_.substr(j, i - j);
        post_[key] = value;
    }
}

实现 UserVerify() 函数

  • 首先检查用户名和密码是否为空,如果为空则返回false
  • 使用SqlConnRAII管理数据库连接,构造查询语句,查询数据库中是否存在该用户名。
  • 如果是登录行为,比较输入的密码和数据库中的密码是否一致,一致则验证成功。
  • 如果是注册行为,检查用户名是否已被使用,若未被使用则将用户信息插入数据库。
// 验证用户的用户名和密码
bool HttpRequest::UserVerify(const std::string& name, const std::string& pwd, bool is_login) {
    if (name == "" || pwd == "")
        return false;

    LOG_INFO("Verify name: %s pwd: %s", name.c_str(), pwd.c_str());
    MYSQL* sql;
    SqlConnectRAII(&sql, SqlConnectPool::GetInstance());
    assert(sql);

    bool flag = false;
    char order[256] = {0};
    MYSQL_RES* res = nullptr;      // 查询结果集

    if (!is_login) // 注册
        flag = true;
    snprintf(order, 256, "SELECT username, password FROM User WHERE username='%s' LIMIT 1", name.c_str());
    LOG_DEBUG("%s", order);

    // 查询用户信息
    if (mysql_query(sql, order)) {
        if (res)
            mysql_free_result(res); // 查询失败,释放结果集
        return false;
    }
    res = mysql_store_result(sql);   // 存储查询结果到res中

    // 处理查询结果
    while (MYSQL_ROW row = mysql_fetch_row(res)) {
        LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
        std::string password = row[1];
        if (is_login) {
            if (pwd == password) {
                flag = true;
            } else {
                flag = false;
                LOG_INFO("pwd error!");
            }
        } else {
            flag = false;
            LOG_INFO("user used!");
        }
    }
    mysql_free_result(res);

    // 注册(用户名未被使用)
    if (!is_login && flag) {
        LOG_DEBUG("register");
        bzero(order, 256);
        snprintf(order, 256, "INSERT INTO User(username, password) VALUES('%s', '%s')", name.c_str(), pwd.c_str());
        LOG_DEBUG("%s", order);
        if (mysql_query(sql, order)) {
            LOG_ERROR("MySQL insert fail: Error: %s", mysql_error(sql));
            flag = false;
        } else {
            LOG_DEBUG("UserVerify success!");
            flag = true;
        }
    }
    return flag;
}

实现 Parse() 函数

  • 首先检查Buffer中是否有可读数据,如果没有则返回false

  • 循环读取Buffer中的数据,直到没有可读数据或者解析状态为FINISH

  • 利用search函数查找\r\n来确定每行数据的结束位置,将每行数据提取出来存为line

  • 根据当前解析状态state_的不同,调用不同的解析函数进行处理:

    • 若为REQUEST_LINE状态,调用ParseRequestLine_解析请求行,若解析失败则返回false,成功则继续解析路径。
    • 若为HEADERS状态,调用ParseHeader_解析请求头,若Buffer中剩余可读字节小于等于 2,则认为是 GET 请求,将状态设置为FINISH
    • 若为BODY状态,调用ParseBody_解析请求体。
  • 如果lineend到达Buffer的写指针位置,说明数据读完,调用buff.RetrieveAll()清空Buffer,并跳出循环;否则跳过\r\n继续解析。

  • 最后记录解析出的请求方法、路径和版本信息,并返回true表示解析成功。

bool HttpRequest::Parse(Buffer& buff) {
    const char* END = "\r\n";
    if (buff.ReadableBytes() == 0)
        return false;
    
    while (buff.ReadableBytes() && state_ != FINISH) {
        // 找到buff中,首次出现"\r\n"的位置
        const char* line_end = std::search(buff.ReadBegin(), buff.WriteBeginConst(), END, END + 2);
        string line(buff.ReadBegin(), line_end);
        switch (state_) {
        case REQUEST_LINE:
            if (ParseRequestLine(line) == false)
                return false;
            ParsePath();
            break;
        case HEADERS:
            ParseHeader(line);
            if (buff.ReadableBytes() <= 2) // get请求,提前结束
                state_ = FINISH;
            break;
        case BODY:
            ParseBody(line);
            break;
        default:
            break;
        }
        if (line_end == buff.WriteBegin()) { // 读完了
            buff.RetrieveAll();
            break;
        }
        buff.RetrieveUntil(line_end + 2); // 跳过回车换行
    }
    LOG_DEBUG("[%s] [%s] [%s]", method_ .c_str(), path_.c_str(), version_.c_str());
    return true;
}

HttpRequest 代码

http_request.h

#ifndef HTTP_REQUEST_H
#define HTTP_REQUEST_H

#include <cstring>
#include <string>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
#include <regex> // 正则表达式
#include <mysql/mysql.h>

#include "../buffer/buffer.h"
#include "../log/log.h"
#include "../pool/sql_connect_pool.h"

class HttpRequest {
public:
    enum PARSE_STATE {
        REQUEST_LINE,
        HEADERS,
        BODY,
        FINISH
    };

    HttpRequest() { Init(); }
    ~HttpRequest() = default;

    void Init();
    bool Parse(Buffer& buff);

    std::string Method() const;
    std::string Path() const;
    std::string& Path();
    std::string Version() const;
    std::string GetPost(const std::string& key) const;
    std::string GetPost(const char* key) const;

    bool IsKeepAlive() const;

private:
    static int ConverHex(char ch);
    static bool UserVerify(const std::string& name, const std::string& pwd, bool is_login);

    bool ParseRequestLine(const std::string& line);
    void ParseHeader(const std::string& line);
    void ParseBody(const std::string& line);

    void ParsePath();
    void ParsePost();
    void ParseFromUrlEncoded();

    static const std::unordered_set<std::string> DEFAULT_HTML;
    static const std::unordered_map<std::string, int> DEFAULT_HTML_TAG;

    PARSE_STATE state_;
    std::string method_;
    std::string path_;
    std::string version_;
    std::string body_;
    std::unordered_map<std::string, std::string> header_; // HTTP 请求的头部信息
    std::unordered_map<std::string, std::string> post_;   // POST 请求的数据
};

#endif // HTTP_REQUEST_H

http_request.cc

#include "http_request.h"

const std::unordered_set<std::string> HttpRequest::DEFAULT_HTML {
    "/index", "/register", "/login", "/welcome", "/video", "/picture"
};

// 登录/注册
const std::unordered_map<std::string, int> HttpRequest::DEFAULT_HTML_TAG {
    {"/login.html", 1}, {"/register.html", 0}
};

void HttpRequest::Init() {
    state_ = REQUEST_LINE;
    method_ = path_ = version_ = body_ = "";
    header_.clear();
    post_.clear();
}

bool HttpRequest::ParseRequestLine(const std::string& line) {
    // GET /index.html HTTP/1.1
    std::regex patten("^([^ ]*) ([^ ]*) HTTP/([^ ]*)$");
    std::smatch match;
    if (std::regex_match(line, match, patten)) {
        method_ = match[1];
        path_ = match[2];
        version_ = match[3];
        state_ = HEADERS;
        return true;
    } 
    LOG_ERROR("RequestLine Error"); 
    return false;
}

void HttpRequest::ParseHeader(const std::string& line) {
    std::regex patten("^([^:]*): ?(.*)$");
    std::smatch match;
    if (std::regex_match(line, match, patten)) {
        header_[match[1]] = match[2];
    } else {
        state_ = BODY;
    }
}

void HttpRequest::ParseBody(const std::string& line) {
    body_ = line;
    ParsePost();    
    state_ = FINISH;
    LOG_DEBUG("Body: %s, len: %d", line.c_str(), line.size());
}

void HttpRequest::ParsePost() {
    if (method_ == "POST" && header_["Content-Type"] == "application/x-www-form-urlencoded") {
        ParseFromUrlEncoded();
        if (DEFAULT_HTML_TAG.count(path_)) { // 登录/注册
            int tag = DEFAULT_HTML_TAG.find(path_)->second;
            LOG_DEBUG("Tag: %d", tag);
            if (tag == 0 || tag == 1) {
                bool is_login = (tag == 1);
                if (UserVerify(post_["username"], post_["password"], is_login))
                    path_ = "/welcome.html";
                else
                    path_ = "/error.html";
            }
        }
    }
}

// 16进制转10进制
int HttpRequest::ConverHex(char ch) {
    if (ch >= 'A' && ch <= 'F')
        return ch - 'A' + 10;
    if (ch >= 'a' && ch <= 'f')
        return ch - 'a' + 10;
    return ch;
}

void HttpRequest::ParseFromUrlEncoded() {
    if (body_.size() == 0)
        return;
    
    std::string key, value;
    int num = 0;
    int n = body_.size();
    int i = 0, j = 0;

    while (i < n) {
        char ch = body_[i];
        // username=john%20doe&password=123456
        switch (ch) {
        case '=': // 获取键值对
            key = body_.substr(j, i - j); 
            j = i + 1;
            break;
        case '&': // 获取键值对
            value = body_.substr(j, i - j);
            j = i + 1;
            post_[key] = value;
            LOG_DEBUG("%s = %s", key.c_str(), value.c_str());
            break;
        case '+': // 替换为空格
            body_[i] = ' ';
            break;
        case '%':
            num = ConverHex(body_[i + 1]) * 16 + ConverHex(body_[i + 2]);
            body_[i + 2] = num % 10 + '0';
            body_[i + 1] = num / 10 + '0';
            i += 2;
            break;
        default:
            break;
        }
        i++;
    }
    assert(j <= i);
    if (post_.count(key) == 0 && j < i) {
        value = body_.substr(j, i - j);
        post_[key] = value;
    }
}

void HttpRequest::ParsePath() {
    if (path_ == "/") {
        path_ = "/index.html";
    } else {
        if (DEFAULT_HTML.find(path_) != DEFAULT_HTML.end())
            path_ += ".html";
    }
}

// 验证用户的用户名和密码
bool HttpRequest::UserVerify(const std::string& name, const std::string& pwd, bool is_login) {
    if (name == "" || pwd == "")
        return false;

    LOG_INFO("Verify name: %s pwd: %s", name.c_str(), pwd.c_str());
    MYSQL* sql;
    SqlConnectRAII(&sql, SqlConnectPool::GetInstance());
    assert(sql);

    bool flag = false;
    char order[256] = {0};
    MYSQL_RES* res = nullptr;      // 查询结果集

    if (!is_login) // 注册
        flag = true;
    snprintf(order, 256, "SELECT username, password FROM User WHERE username='%s' LIMIT 1", name.c_str());
    LOG_DEBUG("%s", order);

    // 查询用户信息
    if (mysql_query(sql, order)) {
        if (res)
            mysql_free_result(res); // 查询失败,释放结果集
        return false;
    }
    res = mysql_store_result(sql);   // 存储查询结果到res中

    // 处理查询结果
    while (MYSQL_ROW row = mysql_fetch_row(res)) {
        LOG_DEBUG("MYSQL ROW: %s %s", row[0], row[1]);
        std::string password = row[1];
        if (is_login) {
            if (pwd == password) {
                flag = true;
            } else {
                flag = false;
                LOG_INFO("pwd error!");
            }
        } else {
            flag = false;
            LOG_INFO("user used!");
        }
    }
    mysql_free_result(res);

    // 注册(用户名未被使用)
    if (!is_login && flag) {
        LOG_DEBUG("register");
        bzero(order, 256);
        snprintf(order, 256, "INSERT INTO User(username, password) VALUES('%s', '%s')", name.c_str(), pwd.c_str());
        LOG_DEBUG("%s", order);
        if (mysql_query(sql, order)) {
            LOG_ERROR("MySQL insert fail: Error: %s", mysql_error(sql));
            flag = false;
        } else {
            LOG_DEBUG("UserVerify success!");
            flag = true;
        }
    }
    return flag;
}

bool HttpRequest::Parse(Buffer& buff) {
    const char* END = "\r\n";
    if (buff.ReadableBytes() == 0)
        return false;
    
    while (buff.ReadableBytes() && state_ != FINISH) {
        // 找到buff中,首次出现"\r\n"的位置
        const char* line_end = std::search(buff.ReadBegin(), buff.WriteBeginConst(), END, END + 2);
        string line(buff.ReadBegin(), line_end);
        switch (state_) {
        case REQUEST_LINE:
            if (ParseRequestLine(line) == false)
                return false;
            ParsePath();
            break;
        case HEADERS:
            ParseHeader(line);
            if (buff.ReadableBytes() <= 2) // get请求,提前结束
                state_ = FINISH;
            break;
        case BODY:
            ParseBody(line);
            break;
        default:
            break;
        }
        if (line_end == buff.WriteBegin()) { // 读完了
            buff.RetrieveAll();
            break;
        }
        buff.RetrieveUntil(line_end + 2); // 跳过回车换行
    }
    LOG_DEBUG("[%s] [%s] [%s]", method_ .c_str(), path_.c_str(), version_.c_str());
    return true;
}

std::string HttpRequest::Method() const {
    return method_;
}

std::string HttpRequest::Path() const {
    return path_;
}

std::string& HttpRequest::Path() {
    return path_;
}

std::string HttpRequest::Version() const {
    return version_;
}

std::string HttpRequest::GetPost(const std::string& key) const {
    if (post_.count(key) == 1)
        // return post_[key]; post_有const属性,所以不能用[]
        return post_.find(key)->second;
    return "";
}

std::string HttpRequest::GetPost(const char* key) const {
    assert(key != nullptr);
    if (post_.count(key) == 1)
        return post_.find(key)->second;
    return "";
}

bool HttpRequest::IsKeepAlive() const {
    if (header_.count("Connect") == 1) 
        return header_.find("Connect")->second == "keep-alive" && version_ == "1.1";
    return false;
}

HttpRequest 测试

测试HttpRequest的,解析,注册,登录功能

#include "../code/http/http_request.h"
#include <iostream>

void TestHttpRequest() {
    // 初始化日志系统
    Log* logger = Log::GetInstance();
    logger->Init(0, "./logs/", ".log", 1024);

    // 初始化测试
    HttpRequest request;

    // 初始化数据库连接池
    SqlConnectPool* conn_pool = SqlConnectPool::GetInstance();
    conn_pool->Init("localhost", 3306, "Tian", "123456", "web_server", 10);


    // 解析请求测试
    std::string http_request = "GET /index.html HTTP/1.1\r\n"
                               "Host: example.com\r\n"
                               "Connection: keep-alive\r\n"
                               "\r\n";
    Buffer buff;
    buff.Append(http_request);
    bool parseResult = request.Parse(buff);
    assert(parseResult);

    // 访问器方法测试
    assert(request.Method() == "GET");
    assert(request.Path() == "/index.html");
    assert(request.Version() == "1.1");

    // 模拟注册 POST 请求
    std::string register_request = "POST /register.html HTTP/1.1\r\n"
                                   "Host: example.com\r\n"
                                   "Content-Type: application/x-www-form-urlencoded\r\n"
                                   "Content-Length: 23\r\n"
                                   "\r\n"
                                   "username=test&password=123456";
    request.Init();
    buff.Append(register_request);
    parseResult = request.Parse(buff);
    assert(parseResult);

    assert(request.Method() == "POST");
    assert(request.Path() == "/welcome.html");
    assert(request.Version() == "1.1");
    assert(request.GetPost("username") == "test");
    assert(request.GetPost("password") == "123456");

    // Post 数据获取测试
    // 模拟登录 POST 请求
    std::string login_request = "POST /login HTTP/1.1\r\n"
                               "Content-Type: application/x-www-form-urlencoded\r\n"
                               "Content-Length: 19\r\n"
                               "\r\n"
                               "username=test&password=123456"
                               "\r\n";
    buff.Append(login_request);
    request.Init();

    parseResult = request.Parse(buff);
    assert(parseResult);

    assert(request.Method() == "POST");
    assert(request.Path() == "/welcome.html");
    assert(request.Version() == "1.1");
    assert(request.GetPost("username") == "test");
    assert(request.GetPost("password") == "123456");

    std::cout << "All tests passed!" << std::endl;
}

int main() {
    TestHttpRequest();
    return 0;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(tests)

# 设置 C++ 标准和编译器选项
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")

# 定义公共源文件和特定文件
set(COMMON_SOURCES ../code/buffer/buffer.cc ../code/log/log.cc)
set(HTTP_SOURCE ../code/http/http_request.cc)
set(POOL_SOURCE ../code/pool/sql_connect_pool.cc)

# 查找 MySQL 库
find_package(PkgConfig REQUIRED)
pkg_check_modules(MYSQL REQUIRED mysqlclient)
# 包含 MySQL 头文件目录
include_directories(${MYSQL_INCLUDE_DIR})

# 添加可执行文件
add_executable(http_request_test http_request_test.cc ${COMMON_SOURCES} ${HTTP_SOURCE} ${POOL_SOURCE})
# 链接库
target_link_libraries(http_request_test ${MYSQL_LIBRARIES})

return 0;
}


**CMakeLists.txt**

```cmake
cmake_minimum_required(VERSION 3.10)
project(tests)

# 设置 C++ 标准和编译器选项
set(CMAKE_BUILD_TYPE Debug)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")

# 定义公共源文件和特定文件
set(COMMON_SOURCES ../code/buffer/buffer.cc ../code/log/log.cc)
set(HTTP_SOURCE ../code/http/http_request.cc)
set(POOL_SOURCE ../code/pool/sql_connect_pool.cc)

# 查找 MySQL 库
find_package(PkgConfig REQUIRED)
pkg_check_modules(MYSQL REQUIRED mysqlclient)
# 包含 MySQL 头文件目录
include_directories(${MYSQL_INCLUDE_DIR})

# 添加可执行文件
add_executable(http_request_test http_request_test.cc ${COMMON_SOURCES} ${HTTP_SOURCE} ${POOL_SOURCE})
# 链接库
target_link_libraries(http_request_test ${MYSQL_LIBRARIES})

相关文章:

  • 3D引擎:Three.js有什么有什么优缺点?
  • 数据人的进阶之路:四年数仓实践与成长思考
  • Elasticsearch + Docker:实现容器化部署指南
  • SpringBoot3使用CompletableFuture时java.util.ConcurrentModificationException异常解决方案
  • 优先级与环境变量的艺术:驾驭 Linux 系统的核心
  • Vue 入门到实战 五
  • Mac | Excel | 列数改为和行数一样用数字表示
  • MyBatis-Plus(SpringBoot版)学习第一讲:简介入门案例
  • Rocky9.2 编译安装Intel系列无线网卡驱动
  • RK3568 I2C底层驱动详解
  • 查找单入口空闲区域[A卷-hw_od]
  • 博弈论中的均衡精炼:完美贝叶斯均衡、序贯均衡与颤抖手均衡详解
  • 定义时钟约束
  • 游戏引擎学习第176天
  • leetcode699-修剪二叉搜索树
  • Agent:大模型中的智能“函数”
  • 【Vue3入门1】02- vue3的基本操作(上)
  • 【商城实战(57)】商城数据迁移与升级实战:开启电商新征程
  • 算法|2025最强优化算法
  • Linux用户组管理深度解析
  • 洛杉矶奥组委确认2028年奥运会和残奥会开闭幕式场地
  • 公募基金改革八大要点:建立浮动管理费收取机制、降低规模排名考核权重
  • 机器人助力、入境游、演出引流:假期纳客千万人次城市有高招
  • “两高”出台司法解释,严打破坏黑土地资源犯罪
  • 1101名优秀运动员拟保送,全红婵、黄雨婷、盛李豪在列
  • 江西浮梁县县长张汉坤被查,此前已有4个月无公开活动