Boost Beast:内容长度未知时,如何用自定义JSON body生成非分块响应
你的思路完全没问题,这里有个可行的解决方案
首先得说:你的想法非常合理——内存中的JSON对象和最终要传输的序列化字节流本来就是两个东西,让负责生成字节流的writer来提供实际长度才更贴合逻辑。只不过Boost Beast的默认body模型设计里,size()方法是用来提前获取负载长度、从而避免分块编码的必经之路,所以我们需要调整自定义body的实现来适配这个要求,同时避免重复序列化的开销。
核心解决方案:预序列化缓存
我们可以给自定义body的value_type绑定一个序列化缓存,在size()方法里完成一次序列化并缓存结果,之后writer直接复用这个缓存的内容发送。这样既满足了Beast对size()的要求,又不会重复序列化JSON,还能避免分块编码。
举个具体的实现例子(假设用Boost.Json作为JSON库):
#include <boost/beast.hpp> #include <boost/json.hpp> namespace http = boost::beast::http; namespace json = boost::json; // 包装JSON对象和序列化缓存的结构体 struct CachedJson { json::value data; mutable std::optional<std::string> cached_serialized; }; // 自定义JSON Body类型 class JsonBody { public: // 我们的value_type是带缓存的JSON对象 using value_type = CachedJson; // 静态size方法:计算并缓存序列化后的长度 static std::uint64_t size(value_type const& val) { if (!val.cached_serialized) { // 第一次调用时序列化并缓存 val.cached_serialized = json::serialize(val.data); } return val.cached_serialized->size(); } // Writer类:直接使用缓存的序列化数据发送 class writer { public: template<bool isRequest, class Fields> writer(http::header<isRequest, Fields> const&, value_type const& val) : val_(val) {} void init(boost::system::error_code& ec) { ec = {}; // 初始化无错误 } // 同步写入 template<class SyncWriteStream> std::size_t write(SyncWriteStream& stream, boost::system::error_code& ec) { // 兜底:如果size()没被调用过,这里补做序列化 if (!val_.cached_serialized) { const_cast<value_type&>(val_).cached_serialized = json::serialize(val_.data); } return boost::beast::write(stream, boost::beast::buffer(*val_.cached_serialized), ec); } // 异步写入(如果需要支持异步) template<class AsyncWriteStream, class WriteHandler> void async_write(AsyncWriteStream& stream, WriteHandler&& handler) { if (!val_.cached_serialized) { const_cast<value_type&>(val_).cached_serialized = json::serialize(val_.data); } boost::beast::async_write( stream, boost::beast::buffer(*val_.cached_serialized), std::forward<WriteHandler>(handler) ); } private: value_type const& val_; }; // Reader类(如果需要解析JSON请求/响应的话) class reader { public: template<bool isRequest, class Fields> reader(http::header<isRequest, Fields> const&, value_type& val) : val_(val) {} void init(boost::system::error_code& ec) { ec = {}; } // 同步读取 template<class SyncReadStream> std::size_t read(SyncReadStream& stream, std::uint64_t bytes_remaining, boost::system::error_code& ec) { std::string buffer; buffer.resize(bytes_remaining); auto bytes_read = boost::beast::read(stream, boost::beast::buffer(buffer), ec); if (!ec) { val_.data = json::parse(buffer, ec); } return bytes_read; } // 异步读取(按需实现) template<class AsyncReadStream, class ReadHandler> void async_read(AsyncReadStream& stream, std::uint64_t bytes_remaining, ReadHandler&& handler) { std::string buffer; buffer.resize(bytes_remaining); boost::beast::async_read( stream, boost::beast::buffer(buffer), [this, handler = std::forward<ReadHandler>(handler), buffer = std::move(buffer)] (boost::system::error_code ec, std::size_t bytes_read) mutable { if (!ec) { val_.data = json::parse(buffer, ec); } handler(ec, bytes_read); } ); } private: value_type& val_; }; };
使用方式
当你构建HTTP消息时,直接使用这个自定义Body类型:
// 创建请求 http::request<JsonBody> req(http::verb::post, "/api/endpoint", 11); req.body().data = json::object{{"key", "value"}, {"number", 42}}; // Beast会自动调用JsonBody::size()获取长度,设置Content-Length // 发送时writer直接用缓存的序列化内容,无需重复序列化 boost::beast::write(stream, req, ec);
为什么这个方案可行?
- 完全满足Beast的body模型要求:
size()方法提前提供了准确的负载长度,因此Beast会使用Content-Length头而非分块编码。 - 避免重复序列化:JSON只在第一次需要长度时序列化一次,之后writer直接复用缓存的结果,性能开销极小。
- 贴合你的需求:内存中始终保存解析后的JSON对象,序列化只在必要时发生,而且对外部脚本/测试完全透明,不需要修改它们。
如果你的JSON数据量极大,预序列化缓存会占用过多内存,那可能需要权衡,但大部分场景下这个方案都是最优解。
内容的提问来源于stack exchange,提问作者Vlad.Z
相关产品推荐
相关产品推荐

