基于C++ asio网络库手搓一个简易的web服务

上学时,我总是梦想着能够用C++亲手打造一个属于自己的Web服务。随着时间的流逝,对计算机的了解越来越深,也逐渐认识到这个目标的复杂性。然而,多年后的今天,我已经对计算机有了更深的理解,也知道了这项任务的艰巨性。因此,我决定动手实现一个最简单的Web服务器。

在当今的网络编程中,构建高效的Web服务是一项复杂但令人激动的任务。C++作为一门高性能编程语言,其丰富的库和强大的性能使其成为构建网络应用程序的理想选择。其中,ASIO(Asynchronous Input/Output)库是一个跨平台的C++库,专门用于网络和底层I/O编程,提供了高效且灵活的异步I/O操作。

本文将带您一步步实现一个简易的Web服务器,该服务器能够处理基本的HTTP请求并返回响应。我们将使用C++和ASIO网络库,从基础开始,逐步构建一个可以运行的Web服务器,并深入了解其中的核心概念和实现细节。这个过程不仅是对网络编程技术的探索,更是对自己技术能力的挑战和提升。

什么是 ASIO?

ASIO 是一个用于网络编程的独立库,提供了异步 I/O 操作的支持。它的设计目标是提供高效、可扩展的 I/O 操作,适用于各种类型的网络应用。ASIO 主要特点包括:

  • 跨平台支持:ASIO 支持多种操作系统,包括 Windows、Linux 和 macOS。
  • 异步操作:通过异步 I/O 操作,ASIO 可以提高程序的响应性和性能。
  • 易于集成:ASIO 可以很容易地与其他库和框架集成,如 Boost 库。

在开始编写代码之前,我们先捋捋逻辑

基于C++ asio网络库手搓一个简易的web服务插图

首先,我们需要一个 ASIO 服务类(Server)来启动我们的 Web 服务。然后,我们需要一个线程池(ThreadPool)将 Server 接收到的套接字请求提交到线程池中,以便在其中创建会话类(Session)来处理套接字请求。接着,我们需要一个业务逻辑类(BusinessLogic)来处理 HTTP 请求。最后,Session 类将处理结果写回客户端。此外,我们还需要一个 HttpStruct 类来保存请求头、响应头、HTTP 状态码、HTTP 头类型以及返回结果。

服务类 Server

接下来我们将开始编写代码。首先,我们创建了一个服务类 Server,然后在 main 函数中实例化该类并调用其启动方法,以便启动服务器。

//
// Created by fallrain on 2023/4/20.
//

#ifndef ASIO_DEMO_SERVER_H

#include "Session.h"
#include "ThreadPool.h"

#define ASIO_DEMO_SERVER_H


class Server {
public:
    Server(boost::asio::io_context &io_context, std::uint16_t port);

    void start();

    void stop();

private:

    void do_accept();

    boost::asio::ip::tcp::acceptor acceptor_;
    ThreadPool pool;
};

#endif //ASIO_DEMO_SERVER_H
//
// Created by fallrain on 2023/4/20.
//

#include "Server.h"

Server::Server(boost::asio::io_context &io_context, std::uint16_t port) :
        acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
        pool(std::thread::hardware_concurrency()) {
}

void Server::start() {
    do_accept();
}

void Server::stop() {
    acceptor_.close();
    pool.stop();
}

void Server::do_accept() {
    acceptor_.async_accept(
            [this](std::error_code ec, boost::asio::ip::tcp::socket socket) {
                if (!ec) {
                    pool.post([session = std::make_shared<Session>(socket)] {
                        session->start();
                    });
                }
                do_accept();
            });
}
#include "Server.h"
#include "user.h"
#include "result.h"
int main() {
    boost::asio::io_context io_context;
    Server server(io_context, 8090);
    server.start();
    io_context.run();
    server.stop();
}

ThreadPool线程池

他主要是负责管理多个线程执行任务,避免频繁创建和销毁线程,提高系统性能和资源利用率。

//
// Created by fallrain on 2023/4/20.
//

#ifndef ASIO_DEMO_THREADPOOL_H

#include "boost/asio.hpp"

#define ASIO_DEMO_THREADPOOL_H


class ThreadPool {
public:
    ThreadPool(std::size_t size);

    template<typename T>
    void post(T &&task) {
        io_context_.post(std::forward<T>(task));
    }

    void stop();

private:
    boost::asio::io_context io_context_;
    boost::asio::io_context::work work_;
    std::vector<std::thread> threads_;
};


#endif //ASIO_DEMO_THREADPOOL_H
//
// Created by fallrain on 2023/4/20.
//

#include "ThreadPool.h"

ThreadPool::ThreadPool(std::size_t size) : work_(io_context_) {
    for (std::size_t i = 0; i < size; ++i) {
        threads_.emplace_back([this] {
            io_context_.run();
        });
    }
}

void ThreadPool::stop() {
    io_context_.stop();
    for (auto &thread: threads_) {
        thread.join();
    }
}

Session会话类

Session类在处理HTTP请求时会调用业务逻辑处理类(BusinessLogic)来执行具体的业务逻辑。这包括根据请求内容进行数据处理、逻辑计算或其他操作,并根据处理结果生成HTTP响应。通过调用业务逻辑处理类,Session类能够将请求的具体逻辑与通信部分分离,提高了代码的可维护性和可扩展性。

//
// Created by fallrain on 2023/4/20.
//

#ifndef ASIO_DEMO_SESSION_H

#include "boost/asio.hpp"
#include "boost/regex.hpp"
#include "iostream"
#include <boost/asio/ip/tcp.hpp>
#include "boost/algorithm/string.hpp"
#include "business_logic.h"
#include "http_struct.h"
#include "numeric"
#include "boost/beast.hpp"
#include "cctype"
#include "boost/network/protocol/http/client.hpp"
#define ASIO_DEMO_SESSION_H

class Session : public std::enable_shared_from_this<Session> {
public:

    Session(boost::asio::ip::tcp::socket &socket);

    void start();


private:
    //读取请求
    void do_read();
    //写入请求
    void do_write();
    //处理参数
    void process_params();

    //处理内容
    void process_content_type();
    //请求头
    http_request_struct request;
    //应大头
    http_response_struct response;
    //客户端socket
    boost::asio::ip::tcp::socket client_socket_;
    //客服端的buffer
    boost::asio::streambuf client_buffer_;
//
//    std::map<std::string, std::string> headers;
//    std::string request_body, response_body, method, uri, http_version;
};


#endif //ASIO_DEMO_SESSION_H
//
// Created by fallrain on 2023/4/20.
//

#include "Session.h"


typedef std::string string;

Session::Session(boost::asio::ip::tcp::socket &socket) : client_socket_(std::move(socket)) {}


void Session::start() {
    do_read();
}


// 处理请求的参数部分,从 URI 中解析参数
void Session::process_params() {
    auto self = shared_from_this(); // 获取当前 Session 的 shared_ptr
    size_t pos = self->request.uri.find("?"); // 查找 URI 中的参数部分起始位置
    if (pos == std::string::npos) { // 如果没有找到参数部分,直接返回
        return;
    }
    string query_string = self->request.uri.substr(pos + 1); // 提取参数部分
    std::vector<string> query_line; // 存储每个参数的键值对
    boost::split(query_line, query_string, boost::is_any_of("&")); // 使用 '&' 分割参数
    self->request.params = std::accumulate(query_line.begin(), // 将参数键值对添加到 params 映射中
                                           query_line.end(),
                                           std::map<string, string>(),
                                           [](std::map<std::string, std::string> &result, const string line) {
                                               std::vector<string> params; // 存储每个参数的键值对
                                               boost::split(params, line, boost::is_any_of("=")); // 使用 '=' 分割键值对
                                               if (params.size() == 2) { // 如果键值对的大小为 2
                                                   result[params[0]] = params[1]; // 将键值对添加到 params 映射中
                                               }
                                               return result;
                                           });
}



// 处理请求的Content-Type,根据不同的类型解析请求体
void Session::process_content_type() {
    auto self = shared_from_this(); // 获取当前 Session 的 shared_ptr
    string body = self->request.body; // 获取请求体
    if (body.empty()) { // 如果请求体为空,则直接返回
        return;
    }
    std::map<std::string, std::string> headers = self->request.headers; // 获取请求头
    auto content_type = headers.find("Content-Type"); // 查找 Content-Type 头部
    if (content_type == headers.end()) { // 如果未找到 Content-Type 头部,则直接返回
        return;
    }
    ContentType contentType; // 定义 ContentType 枚举
    // 遍历 ContentType 映射,找到对应的 ContentType
    for (const auto &item: contentTypeToString) {
        if (item.second == content_type->second) {
            contentType = item.first;
            break;
        }
    }

    std::stringstream stringstream(body); // 创建 stringstream 用于解析请求体
    switch (contentType) {
        case ContentType::APPLICATION_XML: // 如果请求体是 XML 格式
            boost::property_tree::read_xml(stringstream, self->request.ptree); // 解析 XML 数据
            return;
        case ContentType::APPLICATION_JSON: // 如果请求体是 JSON 格式
            boost::property_tree::read_json(stringstream, self->request.ptree); // 解析 JSON 数据
            return;
    }

    boost::regex contentTypeRegex(R"(multipart/form-data;\s*boundary=(.*))"); // 正则表达式匹配 multipart/form-data 类型的 Content-Type
    boost::smatch match;
    if (boost::regex_search(content_type->second, match, contentTypeRegex)) { // 如果 Content-Type 匹配成功
        if (match.size() > 1) { // 如果匹配结果的大小大于 1
            string boundary = match[1].str(); // 获取边界字符串
            std::vector<string> params; // 存储字段参数
            std::size_t pos = 0;
            std::size_t lastPos = 0;
            while ((pos = body.find(boundary, lastPos)) != std::string::npos) { // 循环查找边界
                string field_name; // 字段名
                string header = body.substr(lastPos, pos - lastPos); // 获取字段头部信息
                std::string::size_type name_pos = header.find("name=\""); // 查找字段名的起始位置
                if (name_pos != std::string::npos) { // 如果找到字段名
                    std::string::size_type name_end_pos = header.find('\"', name_pos + 6); // 查找字段名的结束位置
                    field_name = header.substr(name_pos + 6, name_end_pos - name_pos - 6); // 提取字段名
                    std::cout << "field_name:" << field_name << std::endl; // 输出字段名
                    // 查找字段值的开始位置
                    std::string::size_type value_start_pos = body.find("\r\n\r\n", lastPos);
                    if (value_start_pos == string::npos) { // 如果找不到字段值的起始位置
                        std::cerr << "Invalid part format: missing value start" << std::endl; // 输出错误信息
                        continue;
                    } else {
                        value_start_pos += 4;
                    }

                    // 查找字段值的结束位置
                    std::string::size_type value_end_pos = body.find(boundary, value_start_pos);
                    if (value_start_pos == string::npos) { // 如果找不到字段值的结束位置
                        std::cerr << "Invalid part format: missing value end" << std::endl; // 输出错误信息
                        continue;
                    }
                    // 提取字段的值数据
                    std::string value = body.substr(value_start_pos, value_end_pos - value_start_pos - 4); // 提取字段的值
                    self->request.form_data[field_name] = value; // 将字段名和值添加到 form_data 映射中
                }
                lastPos = pos + boundary.length(); // 更新上一个边界的位置
            }
        }
        return;
    }
}

// 执行读取操作,从客户端读取请求
void Session::do_read() {
    // 异步读取直到遇到"\r\n\r\n",表示请求头结束
    boost::asio::async_read_until(client_socket_, client_buffer_, "\r\n\r\n",
            // 读取完成后的回调函数
                                  [self = shared_from_this()](std::error_code ec, std::size_t bytes_transferred) {
                                      if (!ec) { // 如果没有发生错误
                                          // 将客户端缓冲区中的数据转换为字符串
                                          std::string request_header(
                                                  boost::asio::buffers_begin(self->client_buffer_.data()),
                                                  boost::asio::buffers_begin(self->client_buffer_.data()) +
                                                  bytes_transferred);
                                          // 分割请求头字符串为单独的行
                                          std::vector<string> header_vec;
                                          boost::split(header_vec, request_header, boost::is_any_of("\r\n"),
                                                       boost::token_compress_on);
                                          // 解构请求结构体
                                          auto &[headers, params, form_data, cookie_map, json_map, session_id, method, body, uri, http_version] = self->request;
                                          // 分割请求行,提取方法、URI和HTTP版本
                                          std::vector<string> line;
                                          boost::split(line, header_vec[0], boost::is_any_of(" "));
                                          method = stringToHttpMethod[line[0]], uri = line[1], http_version = line[2];
                                          // 解析请求头部并填充请求结构体的headers成员
                                          std::for_each(header_vec.begin() + 1, header_vec.end() - 1,
                                                        [&](std::string v) {
                                                            std::vector<string> header;
                                                            boost::split(header, v, boost::is_any_of(":"),
                                                                         boost::token_compress_on);
                                                            // 移除头部值中的空白字符
                                                            header[1].erase(std::remove_if(header[1].begin(),
                                                                                           header[1].end(),
                                                                                           [](unsigned char c) {
                                                                                               return std::isspace(c);
                                                                                           }), header[1].end());
                                                            // 填充请求头部到headers映射
                                                            self->request.headers[header[0]] = header[1];
                                                        });

                                          // 解析Cookie头部
                                          auto header_cookie = headers.find("Cookie");
                                          if (header_cookie != headers.end()) {
                                              std::vector<string> cookie_vec;
                                              boost::split(cookie_vec, header_cookie->second, boost::is_any_of(";"),
                                                           boost::token_compress_on);
                                              std::for_each(cookie_vec.begin(), cookie_vec.end(),
                                                            [&](const auto &item) {
                                                                std::vector<string> cookie;
                                                                boost::split(cookie, item, boost::is_any_of("="),
                                                                             boost::token_compress_on);
                                                                // 填充Cookie到cookie_map映射
                                                                cookie_map[cookie[0]] = cookie[1];
                                                            });
                                          }
                                          // 检查是否存在会话ID,如果不存在则创建一个新的会话ID
                                          if (cookie_map.find("session") == cookie_map.end()) {
                                              session_id = business_logic::create_session_map();
                                          } else {
                                              session_id = cookie_map["session"];
                                          }
                                          // 检查是否有请求体
                                          auto content_length = headers.find("Content-Length");
                                          if (content_length != headers.end()) {
                                              // 计算请求体中剩余的数据长度
                                              std::size_t excess_data_length =
                                                      self->client_buffer_.size() - bytes_transferred;
                                              std::vector<char> excess_data(excess_data_length);
                                              // 将多余的数据复制到excess_data中
                                              boost::asio::buffer_copy(boost::asio::buffer(excess_data),
                                                                       self->client_buffer_.data() + bytes_transferred);
                                              // 将多余的数据添加到请求体中
                                              body = string(excess_data.data(), excess_data_length);
                                              // 计算需要读取的数据长度
                                              size_t content_length = std::stoi(
                                                      headers.find("Content-Length")->second);
                                              if (content_length - body.size() > 0) {
                                                  std::vector<char> buffer(content_length - body.size());
                                                  boost::system::error_code error;
                                                  // 从套接字中读取剩余的请求体数据
                                                  size_t bytes_read = boost::asio::read(self->client_socket_,
                                                                                        boost::asio::buffer(buffer),
                                                                                        boost::asio::transfer_exactly(
                                                                                                content_length -
                                                                                                body.size()),
                                                                                        error);
                                                  if (!error) {
                                                      // 将读取的数据附加到请求体中
                                                      body.append(buffer.data(), bytes_read);
                                                  } else {
                                                      // 输出错误信息
                                                      std::cout << "Error reading request body: " << error.message()
                                                                << std::endl;
                                                  }
                                              }
                                          }
                                          // 处理请求的参数和内容类型
                                          self->process_params();
                                          self->process_content_type();
                                          try {
                                              // 处理请求并获取响应
                                              self->response = business_logic::process_request(self->request);
                                              // 如果请求中不包含会话ID,则将会话ID添加到响应的Cookie中
                                              if (self->request.cookie.find("session") == cookie_map.end()) {
                                                  self->response.cookie["session"] = session_id;
                                              }
                                          } catch (const std::exception &e) {
                                              // 如果发生异常,将异常信息添加到响应体中
                                              self->response.body = e.what();
                                          }
                                          // 执行写入操作,将响应发送给客户端
                                          self->do_write();
                                      }
                                  }
    );
}



// 执行写入操作,将响应发送给客户端
void Session::do_write() {
    // 解构响应结构体
    auto &[headers, cookie, body, content_type, http_status] = shared_from_this()->response;
    // 创建输出流
    std::ostringstream response_stream;
    // 获取状态码和状态字符串
    int status_code = static_cast<int>(http_status);
    string status_string = httpStatusToString[http_status];
    string contentType = contentTypeToString[content_type];
    // 如果Cookie不为空,构建Set-Cookie头部
    if (!cookie.empty()) {
        std::ostringstream ostringstream;
        for (const auto &item: cookie) {
            ostringstream << item.first << "=" << item.second << ";";
        }
        ostringstream.str().pop_back();
        headers["Set-Cookie"] = ostringstream.str();
    }

    // 构建响应头部
    response_stream << "HTTP/1.1" << " " << status_code << " " << status_string << "\r\n";
    response_stream << "Content-Type: " << contentType << "\r\n";
    response_stream << "Server: c++ server" << "\r\n";
    for (auto &item: headers) {
        response_stream << item.first << ":" << item.second << "\r\n";
    }
    // 如果内容类型为TEXT_HTML,则添加Content-Length头部
    if (content_type == ContentType::TEXT_HTML) {
        response_stream << "Content-Length:" << body.size() << "\r\n";
    }
    response_stream << "\r\n"; // 空行,分隔头部和主体
    response_stream << body; // 添加主体内容
    string response_str = response_stream.str(); // 获取完整的响应字符串

    // 异步写入响应到客户端
    boost::asio::async_write(client_socket_, boost::asio::buffer(response_str),
                             [self = shared_from_this()](std::error_code ec, std::size_t length) {
                                 if (!ec) {
                                 } else {
                                     // 如果发生错误,输出错误信息
                                     std::cerr << "write to remote server error: " << ec.message() << std::endl;
                                 }
                             });
}

http_struct一些结构体

定义了一些用于处理 HTTP 请求和响应的结构体和映射。在这些结构体和映射中,我们将 HTTP 状态码映射为对应的状态消息,将内容类型枚举映射为 MIME 类型字符串,并将 HTTP 请求方法字符串映射为枚举值。除此之外,我们还定义了 http_response_struct 结构体,用于表示 HTTP 响应,并提供了多个构造函数,以便于创建不同类型的响应对象。

//
// Created by fallrain on 2023/5/18.
//

#ifndef ASIO_DEMO_HTTP_STRUCT_H
#define ASIO_DEMO_HTTP_STRUCT_H

#include <boost/property_tree/ptree.hpp>
#include "map"


enum class HttpStatusCode {
    OK = 200,
    FOUND = 302,
    BAD_REQUEST = 400,
    UNAUTHORIZED = 401,
    FORBIDDEN = 403,
    NOT_FOUND = 404,
    INTERNAL_SERVER_ERROR = 500
};


enum class ContentType {
    TEXT_PLAIN,
    TEXT_HTML,
    APPLICATION_JSON,
    APPLICATION_XML
};

enum class HttpMethod {
    GET, POST, HEAD, OPTION, PUT, DELECT, TRACE, CONNECT
};

extern std::map<HttpStatusCode, std::string> httpStatusToString;
extern std::map<ContentType, std::string> contentTypeToString;
//extern std::map<HttpMethod, std::string> httpMethodToString;
extern std::map<std::string, HttpMethod> stringToHttpMethod;


struct http_request_struct {
    std::map<std::string, std::string> headers;
    std::map<std::string, std::string> params;
    std::map<std::string, std::string> form_data;
    std::map<std::string, std::string> cookie;
    boost::property_tree::ptree ptree;
    std::string session_id;
    HttpMethod method;
    std::string body;
    std::string uri, http_version;
};

struct http_response_struct {
    http_response_struct(std::string body);

    http_response_struct(std::string body, ContentType content_type);

    http_response_struct(HttpStatusCode http_status);

    http_response_struct();

    std::map<std::string, std::string> headers;
    std::map<std::string, std::string> cookie;
    std::string body;
    ContentType content_type = ContentType::TEXT_HTML;
    HttpStatusCode http_status = HttpStatusCode::OK;
};


#endif //ASIO_DEMO_HTTP_STRUCT_H
//
// Created by fallrain on 2023/5/18.
//
#include "http_struct.h"

std::map<HttpStatusCode, std::string> httpStatusToString = {
        {HttpStatusCode::OK,                    "OK"},
        {HttpStatusCode::BAD_REQUEST,           "Bad Request"},
        {HttpStatusCode::UNAUTHORIZED,          "Unauthorized"},
        {HttpStatusCode::FORBIDDEN,             "Forbidden"},
        {HttpStatusCode::NOT_FOUND,             "Not Found"},
        {HttpStatusCode::INTERNAL_SERVER_ERROR, "Internal Server Error"},
        {HttpStatusCode::FOUND,                 "Found"}
};
std::map<ContentType, std::string> contentTypeToString = {
        {ContentType::TEXT_PLAIN,       "text/plain"},
        {ContentType::TEXT_HTML,        "text/html;charset=UTF-8"},
        {ContentType::APPLICATION_JSON, "application/json"},
        {ContentType::APPLICATION_XML,  "application/xml"}
};


std::map<std::string, HttpMethod> stringToHttpMethod = {
        {"GET",     HttpMethod::GET},
        {"POST",    HttpMethod::POST},
        {"HEAD",    HttpMethod::HEAD},
        {"OPTION",  HttpMethod::OPTION},
        {"PUT",     HttpMethod::PUT},
        {"DELECT",  HttpMethod::DELECT},
        {"TRACE",   HttpMethod::TRACE},
        {"CONNECT", HttpMethod::CONNECT}
};

http_response_struct::http_response_struct(std::string body) : body(body) {}

http_response_struct::http_response_struct(std::string body, ContentType content_type) : body(body),
                                                                                         content_type(content_type) {}

http_response_struct::http_response_struct(HttpStatusCode http_status) : http_status(http_status) {}



http_response_struct::http_response_struct() {}

business_logic 业务类

business_logic 业务类提供了 register_handle 和 set_root 方法,允许在主函数中直接添加业务逻辑而不必修改整体代码。register_handle 方法用于注册处理函数,根据 HTTP 请求的方法和 URI 进行匹配;set_root 方法用于设置服务器根目录。这种设计使得业务逻辑的添加和修改变得简单和灵活。

//
// Created by fallrain on 2023/5/18.
//

#ifndef ASIO_DEMO_BUSINESS_LOGIC_H
#define ASIO_DEMO_BUSINESS_LOGIC_H

#include <functional>
#include "algorithm"
#include "map"
#include "http_struct.h"
#include "boost/property_tree/json_parser.hpp"
#include "boost/property_tree/xml_parser.hpp"
#include "boost/uuid/uuid.hpp"
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <filesystem>

typedef std::function<http_response_struct(http_request_struct)> FunctionPtr;

class business_logic {
public:

    static void register_handle(HttpMethod httpMethod, const std::string &handle_name, FunctionPtr function);

    static http_response_struct process_request(const http_request_struct &request);

    static std::string create_session_map();

    static std::map<std::string, std::string> &get_session_map(const std::string &cookie_id);

    static void set_root(const std::string &value);

private:

    static std::string root;
    static std::map<std::string, std::map<std::string, std::string >> session_map;

    static std::map<HttpMethod, std::map<std::string, FunctionPtr>> function_map;
};


#endif //ASIO_DEMO_BUSINESS_LOGIC_H
//
// Created by fallrain on 2023/5/18.
//
#include <iostream>
#include <utility>
#include "business_logic.h"

std::map<HttpMethod, std::map<std::string, FunctionPtr>> business_logic::function_map = {
        {HttpMethod::GET,     {}},
        {HttpMethod::POST,    {}},
        {HttpMethod::HEAD,    {}},
        {HttpMethod::OPTION,  {}},
        {HttpMethod::PUT,     {}},
        {HttpMethod::DELECT,  {}},
        {HttpMethod::TRACE,   {}},
        {HttpMethod::CONNECT, {}}
};

std::map<std::string, std::map<std::string, std::string >> business_logic::session_map;

std::string business_logic::root;

void business_logic::set_root(const std::string &value) {
    root = value; // 设置根路径
}

void business_logic::register_handle(HttpMethod httpMethod, const std::string &handle_name, FunctionPtr function) {
    function_map[httpMethod][handle_name] = std::move(function);
}

http_response_struct business_logic::process_request(const http_request_struct &request) {
    std::string uri = request.uri;
    size_t pos = uri.find("?");
    if (pos != std::string::npos) {
        auto func = request.uri.substr(0, pos);
        uri = uri.substr(0, pos);
    }
    auto func = function_map.find(request.method)->second.find(uri);
    if (func != function_map.find(request.method)->second.end()) {
        return func->second(request);
    }
    if (request.method == HttpMethod::GET) {
        for (const auto &item: session_map[request.session_id]) {
            std::cout << item.first << "=" << item.second << std::endl;
        }

        std::string filePath = root + uri;
        if (std::filesystem::is_directory(std::filesystem::path(filePath))) {
            filePath += "index.html";
        }

        if (std::filesystem::exists(std::filesystem::path(filePath))) {
            std::ifstream file_stream(filePath);
            if (file_stream.is_open()) {
                std::ostringstream ss;
                ss << file_stream.rdbuf();
                return {ss.str()};
            } else {
                std::cout << "Failed to open file: " << filePath << std::endl;
            }
        }
    }
    return {HttpStatusCode::NOT_FOUND};
}

std::string business_logic::create_session_map() {
    boost::uuids::uuid uuid = boost::uuids::random_generator()();
    std::string uuidStr = boost::uuids::to_string(uuid);
    session_map[uuidStr] = {};
    return uuidStr;
}

std::map<std::string, std::string> &business_logic::get_session_map(const std::string &cookie_id) {
    return session_map[cookie_id];
}

result 返回类

result 类用于统一封装业务处理的返回结果。它支持包含消息、响应体和状态码的返回。通过 to_json() 方法,可以将返回结果转换为 JSON 格式的属性树,而 to_json_string() 方法则将属性树转换为 JSON 字符串返回。这种设计使得业务处理的返回结果能够被统一处理和序列化。

//
// Created by fallrain on 2023/6/15.
//

#ifndef ASIO_DEMO_RESULT_H

#include <variant>
#include "string"
#include "boost/property_tree/json_parser.hpp"

#define ASIO_DEMO_RESULT_H


class result {
public:
    result(std::string message, std::variant<boost::property_tree::ptree, std::string, std::nullptr_t> body, int code);

    result(std::string message, int code);

    result();

    std::string message;
    std::variant<boost::property_tree::ptree, std::string, std::nullptr_t> body;
//    boost::property_tree::ptree body;
    int code;

    boost::property_tree::ptree to_json();

    std::string to_json_string();
};


#endif //ASIO_DEMO_RESULT_H
//
// Created by fallrain on 2023/6/15.
//

#include "result.h"

result::result(std::string message, int code) : message(message), code(code) {}

result::result(std::string message, std::variant<boost::property_tree::ptree, std::string, std::nullptr_t> body,
               int code) : message(message), body(body), code(code) {}

result::result() {}

boost::property_tree::ptree result::to_json() {
    boost::property_tree::ptree ptree;
    ptree.put("message", message);
    if (std::holds_alternative<boost::property_tree::ptree>(body)) {
        ptree.add_child("body", std::get<boost::property_tree::ptree>(body));
    } else if (std::holds_alternative<std::string>(body)) {
        ptree.put("body", std::get<std::string>(body));
    }
    ptree.put("code", code);
    return ptree;
}

std::string result::to_json_string() {
    std::stringstream stringstream;
    boost::property_tree::write_json(stringstream, to_json());
    return stringstream.str();

}

user 类

最后是业务的user 类用于表示用户信息。它包含用户的 ID、用户名、密码和姓名等属性。通过 to_json() 方法,可以将用户信息转换为 JSON 格式的属性树,以便于在业务处理中进行序列化和传输。

//
// Created by fallrain on 2023/6/8.
//

#ifndef ASIO_DEMO_USER_H

#include "string"
#include "boost/property_tree/json_parser.hpp"

#define ASIO_DEMO_USER_H


class user {
public:
    user(std::string id, std::string username, std::string password, std::string name);

    boost::property_tree::ptree to_json();

    std::string id;
    std::string username;
    std::string password;
    std::string name;
};


#endif //ASIO_DEMO_USER_H
//
// Created by fallrain on 2023/6/8.
//

#include "user.h"

user::user(std::string id, std::string username, std::string password, std::string name) : id(id), username(username),
                                                                                           password(password),
                                                                                           name(name) {}

boost::property_tree::ptree user::to_json() {
    boost::property_tree::ptree ptree;

    ptree.put("id", id);
    ptree.put("username", username);
    ptree.put("password", "******");
    ptree.put("name", name);
    return ptree;
}

在这篇博文中,我们一同探索了使用C++的asio网络库手动编写简易的Web服务器的过程。从创建服务器、处理会话到业务逻辑的实现,我们逐步领略了网络编程的精妙之处。这个过程中,我们回想起学生时代的幻想,梦想着有朝一日能够亲手搭建属于自己的Web服务。时隔多年,通过对计算机的深入了解,我们终于有了实现这个梦想的机会。于是,我们义无反顾地着手实现了这个最简单的Web服务器。

通过本文的学习,我们不仅掌握了搭建基本服务器框架的方法,还深入了解了网络通信的原理和机制,以及如何处理不同类型的请求和响应。这为我们打下了坚实的基础,也让我们对网络开发领域有了更深入的理解和认识。

然而,这个小项目还有许多功能未完成,比如图片的处理、数据库等等。这些功能的完善将进一步丰富服务器的功能和提升性能。未来,我们将继续努力,不断完善这个小项目,并探索更多网络编程的奥秘。

最后,感谢您的阅读和支持,希望本文能够为您带来启发和收获。

项目地址:https://github.com/Fall-Rain/asio_web_service

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值