文章目录
-
- 整体架构
- 核心依赖与编译配置
-
- [CMakeLists.txt 关键配置](#CMakeLists.txt 关键配置)
- 头文件清单与作用
- [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 的响应构造)
- 路由:请求分发逻辑
-
- 分发实现模式
- [POST JSON 解析流程](#POST JSON 解析流程)
- 协程架构速览
-
- 核心类型
- 声明协程
- 启动协程的三种模式
- [获取当前 executor](#获取当前 executor)
- 错误处理
- 超时守护协程(watchdog)模式
- [Asio 基础速览](#Asio 基础速览)
-
- [`io_context` --- 事件循环](#
io_context— 事件循环) - [`tcp::acceptor` --- 监听端口](#
tcp::acceptor— 监听端口) - [`tcp::socket` --- TCP 连接](#
tcp::socket— TCP 连接) - [`steady_timer` --- 定时器](#
steady_timer— 定时器) - [`signal_set` --- 信号处理](#
signal_set— 信号处理)
- [`io_context` --- 事件循环](#
- 行级注释:完整执行流程
-
- 入口:`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:完善)
- 快速参考卡
- 与回调版的关键差异对照
基于
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,不够自动扩容
特性:
- 实现
DynamicBufferconcept,是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** 内部做了两件事:**
- 序列化 :将
response对象转换为 HTTP/1.1 线格式 - 发送 :通过
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_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 循环
快速参考卡
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) + 协程退出自动析构 |