云计算百科
云计算领域专业知识百科平台

Beast 协程异步 HTTP 服务器

文章目录

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

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

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

#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;
}


整体架构

┌─────────────────────────────────────────────────────┐
│ 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_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)
)

头文件清单与作用

// ── 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 消息。

命名空间约定

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 请求

// 模板声明(简化)
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:

http::request<http::dynamic_body> request;

request 核心方法一览

// ── 请求行 ──────────────────────────────────────────
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 枚举(常用值)

enum class verb {
delete_, // "DELETE"(delete 是 C++ 关键字所以加下划线)
get, // "GET"
head, // "HEAD"
post, // "POST"
put, // "PUT"
options, // "OPTIONS"
patch, // "PATCH"
// … 共 30+ 种
unknown // 无法识别的动词
};

http::field 枚举(标准 HTTP 头部名)

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 状态码)

enum class status {
ok = 200,
bad_request = 400,
not_found = 404,
internal_server_error = 500,
// … 完整状态码
};

http::response<Body> — HTTP 响应

template<class Body>
class response : public header<false, fields> {
// 注意:第二个模板参数是 false(响应),request 是 true(请求)
};

response 核心方法

// ── 状态行 ──────────────────────────────────────────
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 的两种方式

// 方式一:手动设置
response.set(http::field::content_length, "1234");

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

http::dynamic_body — 动态消息体

namespace boost::beast::http {
using dynamic_body = basic_dynamic_body<basic_multi_buffer<std::allocator<char>>>;
}

核心特性:

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

关键方法:

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

beast::flat_buffer — 读写缓冲区

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

特性:

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

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

beast::ostream — 流式写入 Body

// 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 — 缓冲区→字符串

template<class ConstBufferSequence>
std::string buffers_to_string(ConstBufferSequence const& buffers);

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

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 — 错误码

// beast::error_code 本质上是 boost::system::error_code 的别名
namespace beast {
using error_code = boost::system::error_code;
}

本服务器中的用法:

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 请求

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
);

实际调用:

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** 内部做了四件事:**

┌──────────────────────────────────────────────────────┐
│ 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 响应

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
);

实际调用:

nbytes = co_await http::async_write(
socket,
response,
net::redirect_error(use_awaitable, ec)
);

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

  • 序列化:将 response 对象转换为 HTTP/1.1 线格式
  • 发送:通过 socket 将序列化后的数据写入网络
  • 注意: async_write 不需要 DynamicBuffer 参数,因为序列化是直接从 message 对象内部数据生成的,不需要外部缓冲。

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

    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 请求解析全流程

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

    POST /email HTTP/1.1
    Host: localhost:10086
    Content-Type: application/json
    Content-Length: 45

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

    步骤 1:原始字节到达

    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 内部解析器工作

    ┌──────────────────────────────────────────────────────┐
    │ 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 对象可查询

    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 响应构造与发送

    响应构造顺序(重要!)

    // ① 设置 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() 的工作原理

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

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

    不同 Content-Type 的响应构造

    // ── 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 字符串


    路由:请求分发逻辑

    本服务器支持的路由表:

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

    分发实现模式

    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 解析流程

    // 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;


    协程架构速览

    核心类型

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

    声明协程

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

    启动协程的三种模式

    // ① 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

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

    错误处理

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

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

    超时守护协程(watchdog)模式

    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 — 事件循环

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

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

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

    tcp::acceptor — 监听端口

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

    异步接受连接:

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

    tcp::socket — TCP 连接

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

    steady_timer — 定时器

    net::steady_timer timer(executor, std::chrono::seconds(60));

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

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

    signal_set — 信号处理

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


    行级注释:完整执行流程

    入口:main()

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

    listener() — 接受连接循环

    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) — 处理单个连接

    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_buffer 和 http::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>
    • 设置 version、keep_alive、result、content_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 /email:buffers_to_string + jsoncpp 解析

    Phase 6:完善

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

    快速参考卡

    // ═══════════════════════════════════════════════════════
    // 类型速查
    // ═══════════════════════════════════════════════════════

    // 请求
    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) + 协程退出自动析构
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Beast 协程异步 HTTP 服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!