Beast 协程异步 HTTP 服务器

文章目录

    • 整体架构
    • 核心依赖与编译配置
    • [Beast HTTP 核心 API 深入](#Beast HTTP 核心 API 深入)
      • 命名空间约定
      • [`http::request<Body>` --- HTTP 请求](#http::request<Body> — HTTP 请求)
        • [request 核心方法一览](#request 核心方法一览)
        • [`http::verb` 枚举(常用值)](#http::verb 枚举(常用值))
        • [`http::field` 枚举(标准 HTTP 头部名)](#http::field 枚举(标准 HTTP 头部名))
        • [`http::status` 枚举(HTTP 状态码)](#http::status 枚举(HTTP 状态码))
      • [`http::response<Body>` --- HTTP 响应](#http::response<Body> — HTTP 响应)
        • [response 核心方法](#response 核心方法)
        • [设置 Content-Length 的两种方式](#设置 Content-Length 的两种方式)
      • [`http::dynamic_body` --- 动态消息体](#http::dynamic_body — 动态消息体)
      • [`beast::flat_buffer` --- 读写缓冲区](#beast::flat_buffer — 读写缓冲区)
      • [`beast::ostream` --- 流式写入 Body](#beast::ostream — 流式写入 Body)
      • [`beast::buffers_to_string` --- 缓冲区→字符串](#beast::buffers_to_string — 缓冲区→字符串)
      • [`beast::error_code` --- 错误码](#beast::error_code — 错误码)
      • [`http::async_read` --- 异步读取完整 HTTP 请求](#http::async_read — 异步读取完整 HTTP 请求)
      • [`http::async_write` --- 异步发送 HTTP 响应](#http::async_write — 异步发送 HTTP 响应)
      • [`http::request` / `http::response` 的继承关系](#http::request / http::response 的继承关系)
    • [HTTP 请求解析全流程](#HTTP 请求解析全流程)
      • [步骤 1:原始字节到达](#步骤 1:原始字节到达)
      • [步骤 2:`http::async_read` 读取到 `flat_buffer`](#步骤 2:http::async_read 读取到 flat_buffer)
      • [步骤 3:Beast 内部解析器工作](#步骤 3:Beast 内部解析器工作)
      • [步骤 4:解析完成,`request` 对象可查询](#步骤 4:解析完成,request 对象可查询)
      • [Body 读取的两种情况](#Body 读取的两种情况)
    • [HTTP 响应构造与发送](#HTTP 响应构造与发送)
      • 响应构造顺序(重要!)
      • [`content_length()` 的工作原理](#content_length() 的工作原理)
      • [不同 Content-Type 的响应构造](#不同 Content-Type 的响应构造)
    • 路由:请求分发逻辑
    • 协程架构速览
    • [Asio 基础速览](#Asio 基础速览)
      • [`io_context` --- 事件循环](#io_context — 事件循环)
      • [`tcp::acceptor` --- 监听端口](#tcp::acceptor — 监听端口)
      • [`tcp::socket` --- TCP 连接](#tcp::socket — TCP 连接)
      • [`steady_timer` --- 定时器](#steady_timer — 定时器)
      • [`signal_set` --- 信号处理](#signal_set — 信号处理)
    • 行级注释:完整执行流程
      • 入口:`main()`
      • [`listener()` --- 接受连接循环](#listener() — 接受连接循环)
      • [`echo(socket)` --- 处理单个连接](#echo(socket) — 处理单个连接)
    • 自行实现清单
      • [Phase 1:骨架](#Phase 1:骨架)
      • [Phase 2:协程化](#Phase 2:协程化)
      • [Phase 3:读取 HTTP 请求](#Phase 3:读取 HTTP 请求)
      • [Phase 4:构造并发送响应](#Phase 4:构造并发送响应)
      • [Phase 5:路由](#Phase 5:路由)
      • [Phase 6:完善](#Phase 6:完善)
    • 快速参考卡
    • 与回调版的关键差异对照

Beast 协程异步 HTTP 服务器(原文)

基于 http-server-sync-coro.cpp,聚焦架构设计、HTTP 协议解析流程、Beast 核心 API。协程与 Asio 基础仅做概览。

cpp 复制代码
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <memory>
#include <string>
#include <json/json.h>
#include <json/value.h>
#include <json/reader.h>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/this_coro.hpp>  // 协程工具:获取 executor、执行器标识

// ── 命名空间别名 ──────────────────────────────────────────────────
namespace net  = boost::asio;
namespace beast = boost::beast;
namespace http  = beast::http;
using tcp = net::ip::tcp;
using boost::asio::awaitable;
using boost::asio::co_spawn;
using boost::asio::detached;
using boost::asio::use_awaitable;
using namespace std::chrono_literals;  // 字面量:60s

// ── 程序全局状态:请求计数 + 获取当前时间 ───────────────────────────
//    与 easy 版共享同样的工具函数
namespace my_program_state {

    // 线程安全的请求计数器(每次调用 +1)
    std::size_t request_count()
    {
        static std::size_t count = 0;
        // 注意:在单线程协程模型下无需加锁;
        //       若改为多线程 ioc.run() 则需要用 atomic 或 mutex
        return ++count;
    }

    // 获取当前 UNIX 时间戳(秒)
    std::time_t now()
    {
        return std::time(0);
    }

} // namespace my_program_state

// ── 协程超时守护 ─────────────────────────────────────────────────
// 该协程在指定 duration 后关闭 socket,从而强制中止 echo 协程中的异步操作。
// 调用者用 co_spawn 启动它并传入 detached,让它独立运行。
template<typename Socket>
awaitable<void> watchdog(Socket& socket, std::chrono::seconds duration)
{
    // 获取与 socket 绑定的 executor(即同一个 io_context)
    auto executor = co_await boost::asio::this_coro::executor;
    net::steady_timer timer(executor, duration);

    // 等待定时器到期;如果定时器被 cancel() 会收到 operation_aborted
    beast::error_code ec;
    co_await timer.async_wait(net::redirect_error(use_awaitable, ec));

    // 正常到期(未被取消)→ 关闭 socket 以中止挂起的 I/O
    if (!ec)
    {
        // 忽略 close 产生的错误(socket 可能已经关闭)
        socket.close(ec);
    }
}

// ── 处理单个 HTTP 连接的协程 ──────────────────────────────────────
// 与 easy 版 http_connection 类功能完全等价,但用协程替代异步回调,
// 代码读起来像同步流程:read → process → write
awaitable<void> echo(tcp::socket socket)
{
    // ── 此协程内所有局部变量都存活到协程结束 ────────────────────
    // 不需要 shared_from_this / make_shared,协程帧自动保有生命周期
    beast::flat_buffer           buffer{8192};   // 8KB 读写缓冲
    http::request<http::dynamic_body>  request;   // HTTP 请求
    http::response<http::dynamic_body> response;  // HTTP 响应
    beast::error_code                 ec;          // 错误码(复用)
    std::size_t                       nbytes;      // 传输字节数(复用)

    // 启动超时守护协程:60 秒后若还未完成则关闭 socket
    // detached ⇒ 不管它的返回值,让它独立运行
    co_spawn(
        socket.get_executor(),
        watchdog(socket, 60s),
        detached);

    // ...... 1. 异步读取完整的 HTTP 请求 ................................................
    //     协程在此处挂起,直到 http::async_read 完成
    //     如果在读取期间 socket 超时关闭,ec 会带 operation_aborted
    //     redirect_error 将异步操作的错误码写入 ec,co_await 返回传输字节数
    nbytes = co_await http::async_read(
        socket,
        buffer,
        request,
        net::redirect_error(use_awaitable, ec));

    if (ec)
        co_return;  // 读取失败或超时 → 直接退出

    // ...... 2. 构造响应 ...........................................................................

    // 继承请求的 HTTP 版本(如 HTTP/1.1)
    response.version(request.version());
    // 不保持长连接,每次只处理一个请求-响应
    response.keep_alive(false);

    switch (request.method())
        {
            // ── GET 请求 ──────────────────────────────────────────────
            case http::verb::get:
                {
                    response.result(http::status::ok);
                    response.set(http::field::server, "Beast");

                    if (request.target() == "/count")
                    {
                        // /count → 返回请求计数页面
                        response.set(http::field::content_type, "text/html");
                        beast::ostream(response.body())
                            << "<html>\n"
                            << "<head><title>Request count</title></head>\n"
                            << "<body>\n"
                            << "<h1>Request count</h1>\n"
                            << "<p>There have been "
                            << my_program_state::request_count()
                            << " requests so far.</p>\n"
                            << "</body>\n"
                            << "</html>\n";
                    }
                    else if (request.target() == "/time")
                    {
                        // /time → 返回当前时间戳页面
                        response.set(http::field::content_type, "text/html");
                        beast::ostream(response.body())
                            << "<html>\n"
                            << "<head><title>Current time</title></head>\n"
                            << "<body>\n"
                            << "<h1>Current time</h1>\n"
                            << "<p>The current time is "
                            << my_program_state::now()
                            << " seconds since the epoch.</p>\n"
                            << "</body>\n"
                            << "</html>\n";
                    }
                    else
                    {
                        // 其他 GET 路径 → 404
                        response.result(http::status::not_found);
                        response.set(http::field::content_type, "text/plain");
                        beast::ostream(response.body()) << "File not found\r\n";
                    }
                    break;
                }

            // ── POST 请求 ─────────────────────────────────────────────
            case http::verb::post:
                {
                    response.result(http::status::ok);
                    response.set(http::field::server, "Beast");

                    if (request.target() == "/email")
                    {
                        // 获取请求体数据
                        auto& body = request.body();
                        auto body_str = boost::beast::buffers_to_string(body.data());
                        std::cout << "receive body is " << body_str << std::endl;

                        response.set(http::field::content_type, "text/json");

                        // 用 jsoncpp 解析 POST 的 JSON 数据
                        Json::Value root;
                        Json::Reader reader;
                        Json::Value src_root;
                        bool parse_success = reader.parse(body_str, src_root);
                        if (!parse_success)
                        {
                            // JSON 解析失败 → 返回 error: 1001
                            std::cout << "Failed to parse JSON data!" << std::endl;
                            root["error"] = 1001;
                            std::string jsonstr = root.toStyledString();
                            beast::ostream(response.body()) << jsonstr;
                            break;  // 跳出 switch,去写响应
                        }

                        auto email = src_root["email"].asString();
                        std::cout << "email is " << email << std::endl;

                        // 构造成功响应 JSON
                        root["error"] = 0;
                        root["email"] = src_root["email"];
                        root["msg"] = "recevie email post success";
                        std::string jsonstr = root.toStyledString();
                        beast::ostream(response.body()) << jsonstr;
                    }
                    else
                    {
                        // 未知 POST 路径 → 404
                        response.result(http::status::not_found);
                        response.set(http::field::content_type, "text/plain");
                        beast::ostream(response.body()) << "File not found\r\n";
                    }
                    break;
                }

            default:
                // 不支持的方法 → 400 Bad Request
                response.result(http::status::bad_request);
                response.set(http::field::content_type, "text/plain");
                beast::ostream(response.body())
                    << "Invalid request-method '"
                    << std::string(request.method_string())
                    << "'";
                break;
        }

    // ...... 3. 异步发送响应 ........................................................................
    response.content_length(response.body().size());

    // redirect_error 将错误写入 ec,co_await 返回实际传输的字节数
    nbytes = co_await http::async_write(
        socket,
        response,
        net::redirect_error(use_awaitable, ec));

    if (ec)
        co_return;  // 写入失败 → 直接退出

    // 优雅关闭:发送 TCP FIN
    // (注意协程内 socket 生命周期由 echo 协程掌控,退出后自动析构)
    socket.shutdown(tcp::socket::shutdown_send, ec);
    // 忽略 shutdown 错误,不再关心
}

// ── 监听与接受连接的协程 ──────────────────────────────────────────
// 在无限循环中 accept 新连接,每个连接交给一个 echo 协程处理
awaitable<void> listener()
{
    auto executor = co_await boost::asio::this_coro::executor;
    tcp::acceptor acceptor(executor, {tcp::v4(), 10086});

    for (;;)
        {
            // co_await async_accept → 协程挂起到有客户端连接为止
            tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
            // 为每个连接启动一个 echo 协程(detached 不关心返回值)
            co_spawn(executor, echo(std::move(socket)), detached);
        }
}

// ── 入口 ───────────────────────────────────────────────────────────
int main()
{
    try
        {
            // io_context --- ASIO 的事件循环引擎
            net::io_context ioc{1};

            // 注册信号监听(SIGINT / SIGTERM),用于优雅退出
            net::signal_set signals(ioc, SIGINT, SIGTERM);
            signals.async_wait(
                [&](beast::error_code ec, int /*signal_number*/)
                {
                    if (!ec)
                        ioc.stop();  // 停止 io_context::run()
                });

            // 以 detached 模式启动监听协程(不等待返回值)
            co_spawn(ioc, listener(), detached);

            // 启动事件循环;阻塞直到 ioc.stop() 被调用
            ioc.run();

            std::cout << "Server stopped gracefully." << std::endl;
        }
    catch (std::exception const& e)
        {
            std::cerr << "Error: " << e.what() << std::endl;
            return EXIT_FAILURE;
        }
    return EXIT_SUCCESS;
}

整体架构

plain 复制代码
┌─────────────────────────────────────────────────────┐
│                    main()                            │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │ io_context│  │ signals  │  │ co_spawn(ioc,    │  │
│  │   ioc{1}  │  │ (SIGINT) │  │   listener(),    │  │
│  └──────────┘  └──────────┘  │   detached)       │  │
│       │                       └──────────────────┘  │
│       │                              │               │
│       ▼                              ▼               │
│   ioc.run()              ┌───────────────────────┐  │
│   (阻塞事件循环)           │  listener() 协程       │  │
│                           │  ┌─────────────────┐ │  │
│                           │  │ tcp::acceptor    │ │  │
│                           │  │ bind 0.0.0.0:   │ │  │
│                           │  │       10086      │ │  │
│                           │  └─────────────────┘ │  │
│                           │  for(;;) {           │  │
│                           │    socket = co_await │  │
│                           │      async_accept(); │  │
│                           │    co_spawn(echo(    │  │
│                           │      socket),        │  │
│                           │      detached);      │  │
│                           │  }                   │  │
│                           └───────────────────────┘  │
│                                      │               │
│                    ┌─────────────────┘               │
│                    ▼                                 │
│         ┌─────────────────────────┐                 │
│         │  echo(socket) 协程       │  (每个连接一个)  │
│         │                         │                 │
│         │  1. 启动 watchdog(60s)  │                 │
│         │  2. co_await async_read │                 │
│         │  3. process_request()   │                 │
│         │  4. co_await async_write│                 │
│         │  5. socket.shutdown()   │                 │
│         └─────────────────────────┘                 │
└─────────────────────────────────────────────────────┘

关键设计点:

设计决策 说明
单线程 io_context{1} 无需互斥锁,所有协程在同一线程上协作调度
每连接一协程 echo() 协程独立管理一个连接的全生命周期
超时守护协程 watchdog()detached 模式运行,超时直接 socket.close() 强行中断 I/O
局部变量即状态 协程帧自动保存局部变量,不需要 shared_ptr / enable_shared_from_this
detached 启动 listener 和 echo 均以 detached 启动,fire-and-forget 模式

与回调版 (http_server_sync_easy.cpp) 的核心差异:

特性 回调版 协程版
生命周期管理 shared_ptr + enable_shared_from_this 协程帧自动管理
控制流 多层嵌套回调 线性 co_await,读起来像同步代码
错误处理 每个回调检查 ec redirect_error 统一写入 ec,单一检查点
超时实现 steady_timer + 回调 独立协程 watchdog()
C++ 标准 C++17 C++20(协程需要)

核心依赖与编译配置

CMakeLists.txt 关键配置

cmake 复制代码
cmake_minimum_required(VERSION 3.16)
project(beast-http-sync-server VERSION 1.0.0 LANGUAGES CXX)

# 协程版需要 C++20(co_await / co_return / co_spawn)
set(CMAKE_CXX_STANDARD 17)  # easy 版用 C++17
# ...
target_compile_features(http-server-sync-coro PRIVATE cxx_std_20)

target_link_libraries(http-server-sync-coro PRIVATE
    Boost::system      # boost::system::error_code
    Threads::Threads   # pthread (Asio 内部使用)
    JsonCpp::JsonCpp   # JSON 解析(POST /email)
)

头文件清单与作用

cpp 复制代码
// ── Beast ──
#include <boost/beast/core.hpp>      // flat_buffer, error_code, ostream, buffers_to_string
#include <boost/beast/http.hpp>      // http::request, http::response, async_read, async_write
#include <boost/beast/version.hpp>   // BOOST_BEAST_VERSION 宏

// ── Asio ──
#include <boost/asio.hpp>            // io_context 核心
#include <boost/asio/co_spawn.hpp>   // co_spawn() --- 启动协程
#include <boost/asio/detached.hpp>   // detached --- fire-and-forget 模式
#include <boost/asio/io_context.hpp> // io_context 具体定义
#include <boost/asio/ip/tcp.hpp>     // tcp::socket, tcp::acceptor
#include <boost/asio/signal_set.hpp> // SIGINT/SIGTERM 信号处理
#include <boost/asio/write.hpp>      // 通用写操作
#include <boost/asio/this_coro.hpp>  // this_coro::executor --- 协程内获取 executor

// ── 第三方 ──
#include <json/json.h>               // JsonCpp
#include <json/value.h>
#include <json/reader.h>

// ── 标准库 ──
#include <chrono>    // std::chrono::seconds
#include <cstdlib>   // EXIT_SUCCESS / EXIT_FAILURE
#include <ctime>     // std::time()
#include <iostream>  // std::cout, std::cerr
#include <memory>    // std::make_shared (回调版用)
#include <string>    // std::string

Beast HTTP 核心 API 深入

这是本文档的核心章节。理解这些类型和 API 后,你就可以用 Beast 处理任意 HTTP 消息。

命名空间约定

cpp 复制代码
namespace beast = boost::beast;   // Beast 库顶层
namespace http  = beast::http;    // HTTP 协议子模块(核心)
namespace net   = boost::asio;    // Asio 网络库
using tcp = net::ip::tcp;        // TCP 协议类型

http::request<Body> --- HTTP 请求

cpp 复制代码
// 模板声明(简化)
namespace boost::beast::http {
    template<class Body>
    class request : public header<true, fields> {
        // ...
    };
}

**模板参数 **Body 决定请求体的存储方式:

Body 类型 内部存储 适用场景
http::dynamic_body basic_multi_buffer(链表式缓冲区) 通用场景,不确定大小的 body
http::string_body std::string 已知较小的文本 body
http::empty_body 无存储 GET 请求(没有 body)
http::file_body 文件描述符 大文件上传

**本服务器使用 **http::dynamic_body

cpp 复制代码
http::request<http::dynamic_body> request;
request 核心方法一览
cpp 复制代码
// ── 请求行 ──────────────────────────────────────────
http::verb   method() const;          // 返回枚举:verb::get, verb::post, verb::put...
string_view  method_string() const;   // 返回原始字符串:"GET", "POST"...
string_view  target() const;          // URL 路径部分:"/count", "/time", "/email"
unsigned     version() const;         // HTTP 版本:10 (1.0), 11 (1.1)

// ── 头部操作 ────────────────────────────────────────
void set(http::field name, string_view value);  // 设置标准头部
void set(string_view name, string_view value);  // 设置自定义头部
string_view operator[](http::field name) const; // 读取头部值

// ── Body ───────────────────────────────────────────
Body&       body();                   // 可写访问 body(如 dynamic_body)
const Body& body() const;             // 只读访问

// ── 基类访问 ────────────────────────────────────────
header<true, fields>& base();         // 直接操作 header 层
bool keep_alive() const;              // 是否请求保持连接
http::verb 枚举(常用值)
cpp 复制代码
enum class verb {
    delete_,  // "DELETE"(delete 是 C++ 关键字所以加下划线)
    get,      // "GET"
    head,     // "HEAD"
    post,     // "POST"
    put,      // "PUT"
    options,  // "OPTIONS"
    patch,    // "PATCH"
    // ... 共 30+ 种
    unknown   // 无法识别的动词
};
http::field 枚举(标准 HTTP 头部名)
cpp 复制代码
enum class field {
    server,           // "Server"
    content_type,     // "Content-Type"
    content_length,   // "Content-Length"
    accept,           // "Accept"
    host,             // "Host"
    user_agent,       // "User-Agent"
    connection,       // "Connection"
    // ... 共 50+ 标准头部
};

使用 request.set(http::field::server, "Beast") 等价于设置头部 Server: Beast

http::status 枚举(HTTP 状态码)
cpp 复制代码
enum class status {
    ok                    = 200,
    bad_request           = 400,
    not_found             = 404,
    internal_server_error = 500,
    // ... 完整状态码
};

http::response<Body> --- HTTP 响应

cpp 复制代码
template<class Body>
class response : public header<false, fields> {
    // 注意:第二个模板参数是 false(响应),request 是 true(请求)
};
response 核心方法
cpp 复制代码
// ── 状态行 ──────────────────────────────────────────
void result(http::status code);     // 设置状态码:response.result(http::status::ok);
void version(unsigned v);           // 设置 HTTP 版本(通常从 request 继承)
void keep_alive(bool);              // 设置是否保持连接

// ── 头部 ───────────────────────────────────────────
void set(http::field name, string_view value);  // 同 request
void content_length(std::size_t n);             // 快捷设置 Content-Length

// ── Body ──────────────────────────────────────────
Body& body();                       // 获取 body 引用,用于写入响应体
设置 Content-Length 的两种方式
cpp 复制代码
// 方式一:手动设置
response.set(http::field::content_length, "1234");

// 方式二:使用便捷方法(推荐)
response.content_length(response.body().size());

http::dynamic_body --- 动态消息体

cpp 复制代码
namespace boost::beast::http {
    using dynamic_body = basic_dynamic_body<basic_multi_buffer<std::allocator<char>>>;
}

核心特性:

  • 内部使用链式缓冲区 (basic_multi_buffer),数据分散在多个内存块中
  • 支持追加写入,自动扩展
  • 不适合连续内存访问,需用 buffers_to_string()beast::ostream 操作

关键方法:

cpp 复制代码
// 获取底层缓冲区序列(ConstBufferSequence)
auto data() const;  // 返回可用于 buffers_to_string 的缓冲区视图
auto size() const;  // body 总字节数
void clear();       // 清空 body

beast::flat_buffer --- 读写缓冲区

cpp 复制代码
beast::flat_buffer buffer{8192};  // 初始 8KB,不够自动扩容

特性:

  • 实现 DynamicBuffer concept,是 http::async_read 的必需参数
  • 内部是一块连续内存(与 multi_buffer 不同)
  • 自动管理读写位置:data() 返回已读入未消费区,prepare() 分配写入空间,commit() 标记写入完成

在你自己的实现中只需知道:async_read** 将网络数据读取到此缓冲区,同时从中解析 HTTP 消息**。

beast::ostream --- 流式写入 Body

cpp 复制代码
// beast::ostream 包装一个 dynamic_body,提供 operator<<
beast::ostream(response.body())
    << "<html>\n"
    << "<head><title>Request count</title></head>\n"
    << "<body>\n"
    << "<h1>Request count</h1>\n"
    << "</body>\n"
    << "</html>\n";

等价于:把一个 dynamic_body 当作 std::ostream 来用,支持连续 << 拼接字符串。每次 << 都会向 body 内部缓冲区追加数据。

beast::buffers_to_string --- 缓冲区→字符串

cpp 复制代码
template<class ConstBufferSequence>
std::string buffers_to_string(ConstBufferSequence const& buffers);

将缓冲区序列拼接成单个 std::string。典型用法:

cpp 复制代码
auto& body = request.body();
auto body_str = boost::beast::buffers_to_string(body.data());
// body_str 现在是完整的请求体字符串,可传给 JSON 解析器

为什么需要这个函数: dynamic_body.data() 返回的是 ConstBufferSequence(可能是多个不连续内存块的视图),不能直接当字符串用。buffers_to_string 负责将它们拼接。

beast::error_code --- 错误码

cpp 复制代码
// beast::error_code 本质上是 boost::system::error_code 的别名
namespace beast {
    using error_code = boost::system::error_code;
}

本服务器中的用法:

cpp 复制代码
beast::error_code ec;

// redirect_error 让 co_await 不抛异常,而是把错误写入 ec
co_await some_async_op(net::redirect_error(use_awaitable, ec));

if (ec)
    co_return;  // 有错误就退出

http::async_read --- 异步读取完整 HTTP 请求

cpp 复制代码
template<
    class SyncReadStream,   // 如 tcp::socket
    class DynamicBuffer,    // 如 flat_buffer
    bool isRequest,         // true=请求, false=响应(由参数推导)
    class Body,
    class Fields,
    class CompletionToken   // use_awaitable 或回调
>
auto async_read(
    SyncReadStream&     stream,
    DynamicBuffer&      buffer,
    http::message<isRequest, Body, Fields>& msg,  // request 或 response
    CompletionToken&&   token
);

实际调用:

cpp 复制代码
nbytes = co_await http::async_read(
    socket,                                  // tcp::socket&
    buffer,                                  // flat_buffer&
    request,                                 // http::request<dynamic_body>&
    net::redirect_error(use_awaitable, ec)   // 错误重定向
);

async_read** 内部做了四件事:**

plain 复制代码
┌──────────────────────────────────────────────────────┐
│                  http::async_read                     │
│                                                      │
│  第 1 步:从 socket 读取原始字节到 buffer             │
│          GET /count HTTP/1.1\r\n                     │
│          Host: localhost\r\n                         │
│          \r\n                                        │
│                                                      │
│  第 2 步:解析请求行                                  │
│          "GET"  → method_ = http::verb::get          │
│          "/count" → target_ = "/count"                │
│          "HTTP/1.1" → version_ = 11                  │
│                                                      │
│  第 3 步:解析所有头部直到空行                         │
│          "Host: localhost" → fields["Host"]           │
│                                                      │
│  第 4 步:根据 Content-Length 或                      │
│          Transfer-Encoding: chunked 读取完整 body     │
│          (如无 body 则跳过)                          │
│                                                      │
│  结果:request 对象完整填充                           │
└──────────────────────────────────────────────────────┘

返回值: co_await 返回实际传输的总字节数(包括头部 + body)。

http::async_write --- 异步发送 HTTP 响应

cpp 复制代码
template<
    class SyncWriteStream,  // 如 tcp::socket
    bool isRequest,
    class Body,
    class Fields,
    class CompletionToken
>
auto async_write(
    SyncWriteStream&                            stream,
    http::message<isRequest, Body, Fields>&     msg,    // request 或 response
    CompletionToken&&                           token
);

实际调用:

cpp 复制代码
nbytes = co_await http::async_write(
    socket,
    response,
    net::redirect_error(use_awaitable, ec)
);

async_write** 内部做了两件事:**

  1. 序列化 :将 response 对象转换为 HTTP/1.1 线格式
  2. 发送 :通过 socket 将序列化后的数据写入网络

注意: async_write 不需要 DynamicBuffer 参数,因为序列化是直接从 message 对象内部数据生成的,不需要外部缓冲。

http::request / http::response 的继承关系

plain 复制代码
message<isRequest, Body, Fields>
  │
  ├── header<isRequest, Fields>
  │     │
  │     ├── fields  (头部键值对)
  │     ├── method() / method_string()   [仅 isRequest=true]
  │     ├── target()                     [仅 isRequest=true]
  │     ├── result()                     [仅 isRequest=false]
  │     ├── version()
  │     ├── set(field, value)
  │     ├── operator[](field)
  │     └── keep_alive()
  │
  └── Body body_  (消息体)
        ├── body()       --- 获取 Body 引用
        └── body().data() / body().size()

关键理解: request = message<true, Body, Fields>response = message<false, Body, Fields>。区别在于请求有 method()/target(),响应有 result()


HTTP 请求解析全流程

以客户端发送以下请求为例:

http 复制代码
POST /email HTTP/1.1
Host: localhost:10086
Content-Type: application/json
Content-Length: 45

{"email":"hello@example.com","msg":"hi"}

步骤 1:原始字节到达

plain 复制代码
TCP 连接上收到字节流:
50 4F 53 54 20 2F 65 6D 61 69 6C 20 48 54 54 50  → "POST /email HTTP"
2F 31 2E 31 0D 0A 48 6F 73 74 3A 20 6C 6F 63 61  → "/1.1\r\nHost: loca"
...

步骤 2:http::async_read 读取到 flat_buffer

步骤 3:Beast 内部解析器工作

plain 复制代码
┌──────────────────────────────────────────────────────┐
│  Beast 内部 basic_parser<isRequest, Body>            │
│                                                      │
│  阶段 ① 解析请求行:                                  │
│  "POST /email HTTP/1.1\r\n"                          │
│    │      │        │                                 │
│    │      │        └→ version_ = 11 (HTTP/1.1)       │
│    │      └→ target_ = "/email"                      │
│    └→ method_ = http::verb::post                     │
│                                                      │
│  阶段 ② 逐行解析头部直到 \r\n:                       │
│  "Host: localhost:10086\r\n"                         │
│  "Content-Type: application/json\r\n"                │
│  "Content-Length: 45\r\n"                            │
│  "\r\n"  ← 空行标志头部结束                           │
│                                                      │
│  阶段 ③ 读取 body:                                   │
│  Content-Length=45 → 精确读取 45 字节                  │
│  → body_ 包含 {"email":"hello@example.com","msg":"hi"}│
└──────────────────────────────────────────────────────┘

步骤 4:解析完成,request 对象可查询

cpp 复制代码
request.method()      // → http::verb::post
request.method_string() // → "POST"
request.target()      // → "/email"
request.version()     // → 11
request["Host"]       // → "localhost:10086"
request["Content-Type"] // → "application/json"
request.body().size() // → 45

// 获取 body 字符串:
auto& body = request.body();
auto body_str = boost::beast::buffers_to_string(body.data());
// → {"email":"hello@example.com","msg":"hi"}

Body 读取的两种情况

Beast 的 async_read 自动处理两种 body 传输模式:

传输模式 头部标志 读取方式
Content-Length Content-Length: 1234 精确读取 1234 字节
分块传输 Transfer-Encoding: chunked 逐块读取直到 0\r\n\r\n
无 body 两者皆无 不读任何 body 数据

作为服务器开发者,你无需关心 这些细节 --- async_read 全部自动处理。


HTTP 响应构造与发送

响应构造顺序(重要!)

cpp 复制代码
// ① 设置 HTTP 版本(继承自请求)
response.version(request.version());

// ② 设置连接模式
response.keep_alive(false);

// ③ 设置状态码
response.result(http::status::ok);

// ④ 设置头部
response.set(http::field::server, "Beast");
response.set(http::field::content_type, "text/html");

// ⑤ 写入响应体
beast::ostream(response.body()) << "Hello World";

// ⑥ ⚠️ 必须在调用 async_write 之前设置 Content-Length
response.content_length(response.body().size());

// ⑦ 发送
co_await http::async_write(socket, response, ...);

坑点警告: 步骤 ⑥ 必须在步骤 ⑦ 之前完成!content_length() 设置的是序列化时输出的 Content-Length 头部值,如果 body 在 content_length() 之后又被修改,发送的长度就会不匹配。

content_length() 的工作原理

cpp 复制代码
void content_length(std::size_t n) {
    // 等价于:
    this->set(http::field::content_length, std::to_string(n));
}

它只是帮你设置 Content-Length 头部。内部调用 response.body().size() 来计算。

不同 Content-Type 的响应构造

cpp 复制代码
// ── text/html ───────────────────────────────────
response.set(http::field::content_type, "text/html");
beast::ostream(response.body()) << "<html>...</html>";

// ── text/plain ──────────────────────────────────
response.set(http::field::content_type, "text/plain");
beast::ostream(response.body()) << "File not found\r\n";

// ── text/json ───────────────────────────────────
response.set(http::field::content_type, "text/json");
beast::ostream(response.body()) << jsonstr;  // 预序列化的 JSON 字符串

路由:请求分发逻辑

本服务器支持的路由表:

plain 复制代码
┌──────────┬────────┬──────────────────────────────┐
│  方法     │  路径   │  行为                         │
├──────────┼────────┼──────────────────────────────┤
│  GET     │ /count │  返回请求计数器 HTML 页面       │
│  GET     │ /time  │  返回当前时间戳 HTML 页面       │
│  GET     │ 其他   │  404 Not Found                │
│  POST    │ /email │  JSON 解析 → 提取 email → 响应 │
│  POST    │ 其他   │  404 Not Found                │
│  其他    │ 任意   │  400 Bad Request              │
└──────────┴────────┴──────────────────────────────┘

分发实现模式

cpp 复制代码
switch (request.method())
{
case http::verb::get:
    // 处理 GET
    if (request.target() == "/count")  { /* ... */ }
    else if (request.target() == "/time") { /* ... */ }
    else { /* 404 */ }
    break;

case http::verb::post:
    // 处理 POST
    if (request.target() == "/email") { /* JSON 解析 */ }
    else { /* 404 */ }
    break;

default:
    // 不支持的方法 → 400
    response.result(http::status::bad_request);
    break;
}

POST JSON 解析流程

cpp 复制代码
// 1. 提取 body 为字符串
auto& body = request.body();
auto body_str = boost::beast::buffers_to_string(body.data());

// 2. jsoncpp 解析
Json::Value src_root;
Json::Reader reader;
bool ok = reader.parse(body_str, src_root);

// 3. 取字段
if (!ok) {
    // 返回错误 JSON
    root["error"] = 1001;
} else {
    auto email = src_root["email"].asString();
    root["error"] = 0;
    root["email"] = src_root["email"];
    root["msg"]   = "receive email post success";
}

// 4. 序列化写入响应体
std::string jsonstr = root.toStyledString();
beast::ostream(response.body()) << jsonstr;

协程架构速览

核心类型

cpp 复制代码
using boost::asio::awaitable;       // 协程返回类型模板
using boost::asio::co_spawn;        // 启动协程
using boost::asio::detached;        // fire-and-forget 完成标记
using boost::asio::use_awaitable;   // 适配异步操作为 co_await 可用

声明协程

cpp 复制代码
// 返回类型必须是 awaitable<T> 或 awaitable<void>
awaitable<void> echo(tcp::socket socket)
{
    // 协程体 --- 支持 co_await, co_return
    co_await some_async_operation(use_awaitable);
    co_return;  // 可选
}

启动协程的三种模式

cpp 复制代码
// ① detached --- 不关心结果,fire-and-forget
co_spawn(executor, echo(std::move(socket)), detached);

// ② use_awaitable --- 等待子协程完成(用于嵌套)
co_await co_spawn(executor, child_coro(), use_awaitable);

// ③ 自定义 completion --- 传入回调函数
co_spawn(executor, some_coro(), [](std::exception_ptr ep) { /* ... */ });

本服务器全部使用 detached listener 不需要返回值,每个 echo 也是独立生命周期。

获取当前 executor

cpp 复制代码
auto executor = co_await boost::asio::this_coro::executor;
// executor 是与当前协程关联的 io_context 绑定对象
// 用于创建 timer、acceptor 等需要 executor 的对象

错误处理

cpp 复制代码
// redirect_error 将异步操作的错误捕获到 ec,而不是抛出异常
co_await socket.async_read_some(
    net::buffer(data),
    net::redirect_error(use_awaitable, ec));
//                                    ^^
//                                    错误被写入 ec

if (ec)
    co_return;  // 有错误则退出协程

超时守护协程(watchdog)模式

cpp 复制代码
template<typename Socket>
awaitable<void> watchdog(Socket& socket, std::chrono::seconds duration)
{
    auto executor = co_await boost::asio::this_coro::executor;
    net::steady_timer timer(executor, duration);

    beast::error_code ec;
    co_await timer.async_wait(net::redirect_error(use_awaitable, ec));

    if (!ec)  // 未被取消,正常到期 → 强制关闭 socket
        socket.close(ec);
}

// 使用:
co_spawn(
    socket.get_executor(),
    watchdog(socket, 60s),  // 60 秒超时
    detached);

原理: watchdog 和 echo 是两个独立的协程 ,共享同一个 socket。60 秒后 watchdog 调用 socket.close(),这会导致 echo 协程中所有在该 socket 上挂起的异步操作立即以 operation_aborted 错误返回。


Asio 基础速览

io_context --- 事件循环

cpp 复制代码
net::io_context ioc{1};  // 构造参数=1 表示提示单线程运行

// 添加异步任务...
co_spawn(ioc, listener(), detached);

// 阻塞运行事件循环
ioc.run();  // 直到 ioc.stop() 被调用或所有任务完成

tcp::acceptor --- 监听端口

cpp 复制代码
tcp::acceptor acceptor(executor, {tcp::v4(), 10086});
//                              │        │
//                              │        └── 端口号
//                              └── IPv4

异步接受连接:

cpp 复制代码
tcp::socket socket = co_await acceptor.async_accept(use_awaitable);
// async_accept 挂起协程,直到有客户端连接
// 返回一个已建立连接的 socket

tcp::socket --- TCP 连接

cpp 复制代码
// socket 提供的方法:
socket.get_executor();     // 获取绑定的 executor
socket.close(ec);          // 关闭连接
socket.shutdown(           // 半关闭
    tcp::socket::shutdown_send,  // 关闭发送方向(发送 FIN)
    ec);

steady_timer --- 定时器

cpp 复制代码
net::steady_timer timer(executor, std::chrono::seconds(60));

// 异步等待
co_await timer.async_wait(use_awaitable);

// 取消定时器
timer.cancel();  // async_wait 以 operation_aborted 返回

signal_set --- 信号处理

cpp 复制代码
net::signal_set signals(ioc, SIGINT, SIGTERM);
signals.async_wait([&](beast::error_code ec, int sig) {
    if (!ec) ioc.stop();  // 收到信号 → 停止事件循环
});

行级注释:完整执行流程

入口:main()

plain 复制代码
1. io_context ioc{1} 创建
2. 注册 SIGINT/SIGTERM 信号处理
3. co_spawn(ioc, listener(), detached) --- 启动监听协程
4. ioc.run() --- 阻塞,进入事件循环

listener() --- 接受连接循环

plain 复制代码
1. co_await this_coro::executor → 获取 executor
2. 创建 acceptor,绑定 0.0.0.0:10086
3. for(;;) {
       socket = co_await acceptor.async_accept(use_awaitable)
        ↓ 协程挂起,等待客户端连接
        ↓ 连接到达,拿到 socket
       co_spawn(executor, echo(std::move(socket)), detached)
        → 启动 echo 协程,不等待其完成
   }

echo(socket) --- 处理单个连接

plain 复制代码
1. 创建局部变量 buffer, request, response, ec, nbytes
2. co_spawn watchdog(socket, 60s) --- 启动超时守护
3. co_await http::async_read(socket, buffer, request, ...)
        ↓ 协程挂起,等待完整 HTTP 请求
        ↓ 请求到达并解析完成
4. if (ec) co_return --- 检查读取错误/超时
5. 构造响应(switch request.method() / request.target())
6. co_await http::async_write(socket, response, ...)
        ↓ 协程挂起,等待发送完成
7. socket.shutdown(shutdown_send) --- 优雅关闭
8. 函数结束 → 协程帧销毁,socket 自动关闭

自行实现清单

按以下顺序逐步构建服务器:

Phase 1:骨架

  • 创建 io_context,调用 ioc.run()
  • 创建 tcp::acceptor 绑定端口
  • async_accept 接受一个连接
  • 验证能接受 curl 连接

Phase 2:协程化

  • 把 accept 逻辑放入 awaitable<void> listener()
  • co_spawn + detached 启动 listener
  • 改用 co_await async_accept(use_awaitable)

Phase 3:读取 HTTP 请求

  • 在 echo 协程中创建 flat_bufferhttp::request<http::dynamic_body>
  • 调用 co_await http::async_read(socket, buffer, request, redirect_error(use_awaitable, ec))
  • 打印 request.method_string()request.target() 验证解析正确

Phase 4:构造并发送响应

  • 创建 http::response<http::dynamic_body>
  • 设置 versionkeep_aliveresultcontent_type
  • beast::ostream(response.body()) 写入内容
  • response.content_length(response.body().size())
  • co_await http::async_write(...) 发送
  • socket.shutdown(shutdown_send, ec)

Phase 5:路由

  • 实现 switch(request.method()) 路由
  • GET /count、GET /time、404
  • POST /emailbuffers_to_string + jsoncpp 解析

Phase 6:完善

  • 添加 watchdog 超时协程
  • 添加 signal_set 优雅退出
  • 添加 for(;;) accept 循环

快速参考卡

cpp 复制代码
// ═══════════════════════════════════════════════════════
//  类型速查
// ═══════════════════════════════════════════════════════

// 请求
http::request<http::dynamic_body> req;
req.method()              // → http::verb (枚举)
req.method_string()       // → string_view ("GET", "POST"...)
req.target()              // → string_view ("/count", "/time"...)
req.version()             // → unsigned (10=1.0, 11=1.1)
req.set(field, value)     // → 设置头部
req[field]                // → 读取头部值
req.body()                // → dynamic_body&
req.body().size()         // → body 字节数
req.body().data()         // → ConstBufferSequence
req.keep_alive()          // → bool

// 响应
http::response<http::dynamic_body> res;
res.result(status)        // → 设置状态码
res.version(v)            // → 设置版本
res.keep_alive(bool)      // → 连接模式
res.set(field, value)     // → 设置头部
res.content_length(n)     // → 设置 Content-Length
res.body()                // → dynamic_body&

// 缓冲区与工具
beast::flat_buffer buf{8192};            // 读缓冲
beast::ostream(res.body()) << "...";     // 流式写入 body
beast::buffers_to_string(buf.data());    // 缓冲→string

// 异步 I/O
co_await http::async_read(sock, buf, req, ...);   // 读请求
co_await http::async_write(sock, res, ...);        // 写响应
co_await sock.async_accept(use_awaitable);          // 接受连接
co_await timer.async_wait(use_awaitable);           // 等待定时器

// 错误处理
beast::error_code ec;
co_await op(net::redirect_error(use_awaitable, ec));
if (ec) co_return;

// ═══════════════════════════════════════════════════════
//  常用枚举值
// ═══════════════════════════════════════════════════════

// http::verb
verb::get     verb::post    verb::put
verb::delete_ verb::head    verb::options

// http::field
field::server         field::content_type
field::content_length field::host
field::user_agent     field::accept
field::connection

// http::status
status::ok                = 200   status::created      = 201
status::bad_request       = 400   status::not_found    = 404
status::method_not_allowed= 405   status::server_error = 500

与回调版的关键差异对照

方面 回调版 协程版
C++ 标准 C++17 C++20
类定义 class http_connection : enable_shared_from_this 只需 awaitable<void> echo(socket) 函数
成员变量 类成员 (socket_, buffer_, request_, response_, deadline_) 函数局部变量
生命周期 shared_ptr + shared_from_this() 协程帧自动持有
读请求 http::async_read(..., callback) co_await http::async_read(...)
写响应 http::async_write(..., callback) co_await http::async_write(...)
超时 deadline_.async_wait(callback) co_spawn(watchdog(...), detached)
错误处理 每个回调内 if(ec) return; 统一 if(ec) co_return;
accept 循环 递归 http_server(acceptor, socket) for(;;) { co_await accept; co_spawn; }
优雅关闭 deadline_.cancel() socket.shutdown(shutdown_send) + 协程退出自动析构