Boost Beast服务接收JSON负载时返回body limit exceeded问题
Boost Beast服务大JSON负载报错问题
我维护着一个前同事开发的Boost Beast服务,之前运行正常,但当JSON负载增大到约2.4MB时,服务返回"body limit exceeded"错误。查阅文档得知默认请求体限制是1MB,尝试修改限制后,发送负载又出现"Unknown HTTP request"错误,需要解决。
服务由ServerService、Listener、Session三个类实现,代码如下:
ServerService
void ServerService::startServer(const std::string& address, const unsigned short& port, const std::string& baseRessourceName, const unsigned short& threadNumber) { try { const auto srvAddress = boost::asio::ip::make_address(address); // The io_context is required for all I/O auto const nbThreads = std::max<int>(1, threadNumber); boost::asio::io_context ioContext(nbThreads); // Create listener and launch a listening port std::shared_ptr<Listener> listener = std::make_shared<Listener>(ioContext, tcp::endpoint{ srvAddress, port }, baseRessourceName); listener->run(); // Run the I/O service on the requested number of threads std::vector<std::thread> threads; threads.reserve(nbThreads - 1); for (auto i = nbThreads - 1; i > 0; --i) { threads.emplace_back([&ioContext] { ioContext.run(); }); } ioContext.run(); } catch (std::exception const& e) { LBC_ERROR("{}", e.what()); } }
Listener
// Used namespace using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> namespace Http { class Listener : public std::enable_shared_from_this<Listener> { private: tcp::acceptor m_acceptor; tcp::socket m_socket; std::string const& m_baseResourceName; // Report a failure void logError(boost::system::error_code errorCode, char const* what) { LBC_ERROR("{}: {}", what, errorCode.message()); } public: Listener(boost::asio::io_context& ioContext, tcp::endpoint endpoint, std::string const& docRoot) : m_acceptor(ioContext) , m_socket(ioContext) , m_baseResourceName(docRoot) { boost::system::error_code errorCode; // Open the acceptor m_acceptor.open(endpoint.protocol(), errorCode); if (errorCode) { logError(errorCode, "open"); return; } // Allow address reuse m_acceptor.set_option(boost::asio::socket_base::reuse_address(true)); if (errorCode) { logError(errorCode, "set_option"); return; } // Bind to the server address m_acceptor.bind(endpoint, errorCode); if (errorCode) { logError(errorCode, "bind"); return; } // Start listening for connections m_acceptor.listen(boost::asio::socket_base::max_listen_connections, errorCode); if (errorCode) { logError(errorCode, "listen"); return; } } // Start accepting incoming connections void run() { if (!m_acceptor.is_open()) { return; } doAccept(); } void doAccept() { m_acceptor.async_accept(m_socket, std::bind( &Listener::onAccept, shared_from_this(), std::placeholders::_1)); } void onAccept(boost::system::error_code errorCode) { if (errorCode) { logError(errorCode, "accept"); } else { // Create the session and run it std::make_shared<Session>( std::move(m_socket), m_baseResourceName)->run(); } // Accept another connection doAccept(); } }; } // namespace Http
Session
// Used namespaces using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> namespace boostHttp = boost::beast::http; // from <boost/beast/http.hpp> namespace Http { class Session : public std::enable_shared_from_this<Session> { private: // This is the C++11 equivalent of a generic lambda. // The function object is used to send an HTTP message. struct send_lambda { Session& self_; explicit send_lambda(Session& self) : self_(self) {} template<bool isRequest, class Body, class Fields> void operator()(boostHttp::message<isRequest, Body, Fields>&& msg) const { // The lifetime of the message has to extend // for the duration of the async operation so // we use a shared_ptr to manage it. auto sp = std::make_shared<boostHttp::message<isRequest, Body, Fields>>(std::move(msg)); // Store a type-erased version of the shared // pointer in the class to keep it alive. self_.res_ = sp; // Write the response boostHttp::async_write(self_.socket_, *sp, boost::asio::bind_executor( self_.strand_, std::bind( &Session::onWrite, self_.shared_from_this(), std::placeholders::_1, std::placeholders::_2, sp->need_eof()))); } }; // Report a failure void logError(boost::system::error_code errorCode, char const* what) { LBC_ERROR("{}: {}", what, errorCode.message()); } tcp::socket socket_; boost::asio::strand<boost::asio::any_io_executor> strand_; boost::beast::flat_buffer buffer_; std::string const& baseResourceName_; boostHttp::request<boostHttp::string_body> req_; std::shared_ptr<void> res_; send_lambda lambda_; public: // Take ownership of the socket explicit Session(tcp::socket socket, std::string const& docRoot) : socket_(std::move(socket)) , strand_(socket_.get_executor()) , baseResourceName_(docRoot) , lambda_(*this) {} // Start the asynchronous operation void run() { doRead(); } void doRead() { // Make the request empty before reading, // otherwise the operation behavior is undefined. req_ = {}; // Read a request boostHttp::async_read(socket_, buffer_, req_, boost::asio::bind_executor( strand_, std::bind( &Session::onRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2))); } void onRead(boost::system::error_code errorCode, std::size_t transferredBytes) { boost::ignore_unused(transferredBytes); // This means they closed the connection if (errorCode == boostHttp::error::end_of_stream) { return doClose(); } if (errorCode) { return logError(errorCode, "*** read"); // Error is here } // Some stuff here to manage request } void onWrite(boost::system::error_code ec, std::size_t transferredBytes, bool close) { boost::ignore_unused(transferredBytes); if (ec) { return logError(ec, "write"); } if (close) { // This means we should close the connection, usually because // the response indicated the "Connection: close" semantic. return doClose(); } // We're done with the response so delete it res_ = nullptr; // Read another request doRead(); } void doClose() { // Send a TCP shutdown boost::system::error_code ec; socket_.shutdown(tcp::socket::shutdown_send, ec); // At this point the connection is closed gracefully } }; } // namespace Http
服务启动代码
Service::ServerService serverService; serverService.startServer("127.0.0.1", 8080, "service_name", 5);
解决方案
"Unknown HTTP request"错误是因为修改请求体限制的方式不正确。Boost Beast默认async_read使用的是默认配置的解析器,直接修改请求对象无法生效,正确做法是显式创建request_parser并设置body_limit:
- 修改Session类私有成员
替换原有的boostHttp::request<boostHttp::string_body> req_;为解析器对象:
boostHttp::request_parser<boostHttp::string_body> parser_;
- 更新doRead方法
初始化解析器并设置允许的最大请求体大小:
void doRead() { // 重置解析器状态 parser_.reset(); // 设置3MB的请求体限制(可根据实际需求调整) parser_.body_limit(3 * 1024 * 1024); // 使用解析器异步读取请求 boostHttp::async_read(socket_, buffer_, parser_, boost::asio::bind_executor( strand_, std::bind( &Session::onRead, shared_from_this(), std::placeholders::_1, std::placeholders::_2))); }
- 调整onRead方法获取请求
通过解析器获取解析后的完整请求对象:
void onRead(boost::system::error_code errorCode, std::size_t transferredBytes) { boost::ignore_unused(transferredBytes); if (errorCode == boostHttp::error::end_of_stream) { return doClose(); } if (errorCode) { return logError(errorCode, "*** read"); } // 获取解析后的请求对象 boostHttp::request<boostHttp::string_body> const& req = parser_.get(); // 后续请求处理逻辑使用req替代原有的req_ // Some stuff here to manage request }
注意事项
- 设置的
body_limit需大于实际负载大小(比如2.4MB负载可设为3MB或更大) - 若负载可能持续增大,可考虑使用
file_body或dynamic_body处理超大请求,避免内存占用过高
内容的提问来源于stack exchange,提问作者user19334409
相关产品推荐
相关产品推荐

