fix(libhttp): WebSocket 发送/代理队列上限防 OOM (P1-6)
- 新增 setMaxWsQueueSize()(default 1000),每个入队点检查 - wsSend / onProxyReadClient / onProxyReadBackend 超限关闭连接 - 更新审计文档 P1-6 状态、README v3.4.3
This commit is contained in:
1 parent
37c6d401d6
commit
4d9bb5d940
4 files changed
+38
-4
No files matched your search
@@ -15,6 +15,7 @@ C++20 network library for the UniServe microservice framework, providing unified
|
||||
- **Client Management**: WebSocket client tracking, broadcast, and targeted send
|
||||
- **WS Message Size Limit**: `setMaxWsMessageSize()` caps single WebSocket message (default 16MB, aligned with Beast; applies to server and proxy streams) to prevent OOM
|
||||
- **Read Timeouts (Slowloris Protection)**: `setHeaderTimeout()` (default 10s) / `setReadTimeout()` (default 30s) enforce two-stage header/body read timeouts plus WS idle timeout, aligned with WAF
|
||||
- **WS Queue Size Limit**: `setMaxWsQueueSize()` (default 1000) caps WS send / proxy queues; slow consumers get disconnected to prevent unbounded-memory OOM
|
||||
|
||||
### HttpClient
|
||||
- **HTTP Client**: Synchronous HTTP requests via libcurl (GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)
|
||||
@@ -179,6 +180,10 @@ std::chrono::seconds getHeaderTimeout() const;
|
||||
void setReadTimeout(std::chrono::seconds t);
|
||||
std::chrono::seconds getReadTimeout() const;
|
||||
|
||||
// WS 发送/代理队列上限(条数)。默认 1000;超限判定慢消费者并关闭连接(防 OOM)。
|
||||
void setMaxWsQueueSize(std::size_t n);
|
||||
std::size_t getMaxWsQueueSize() const;
|
||||
|
||||
**WebSocket Routes**
|
||||
```cpp
|
||||
void ws(const std::string& path, WebSocketMessageHandler handler);
|
||||
@@ -295,6 +300,7 @@ using WebSocketClientBase = HttpClient;
|
||||
|
||||
## Version History
|
||||
|
||||
- **v3.4.3**: WebSocket 发送/代理队列上限(P1-6)。新增 `setMaxWsQueueSize()`(默认 1000);`wsSend` / `onProxyReadClient` / `onProxyReadBackend` 入队前检查,超限判定慢消费者并关闭连接,防止无界队列内存耗尽。
|
||||
- **v3.4.2**: 服务器读超时防 Slowloris(P1-4)。新增 `setHeaderTimeout()`(默认 10s)/`setReadTimeout()`(默认 30s);`doRead` 两阶段读(`async_read_header` 头超时 → `async_read` 体超时),`doSSLHandshake` 覆盖握手超时,`doWsRead` 设 WS 空闲超时,对齐 WAF ReadHeaderTimeout/ReadTimeout。
|
||||
- **v3.4.1**: WebSocket 单条消息大小限制(P1-3)。新增 `setMaxWsMessageSize()`/`getMaxWsMessageSize()`(默认 16MB,与 Beast 默认一致),服务端 `doWsUpgrade`/`doWssUpgrade` 与代理 `onProxyConnect`/`onProxySslHandshake` 创建流后调用 `read_message_max()`,超限消息由 Beast 在分配内存前拒绝并关闭连接,防 OOM。
|
||||
- **v3.4.0**: HttpClient 响应大小限制(防 OOM)。`request_stream_sync` 改为真流式(按块回调,不二次缓冲);新增 `set_max_response_size()`/`get_max_response_size()`(默认 10MB,对齐 WAF,0 表示不限制),超限时 `write_callback` 返回 0 中止传输并抛异常。
|
||||
|
||||
@@ -132,6 +132,11 @@ public:
|
||||
void setReadTimeout(std::chrono::seconds t) { read_timeout_ = t; }
|
||||
std::chrono::seconds getReadTimeout() const { return read_timeout_; }
|
||||
|
||||
// WS 发送/代理队列上限(条数)。默认 1000。防止慢消费者导致无界队列内存耗尽(P1-6)。
|
||||
// 超限时判定为慢消费者,关闭对应连接。
|
||||
void setMaxWsQueueSize(std::size_t n) { max_ws_queue_size_ = n; }
|
||||
std::size_t getMaxWsQueueSize() const { return max_ws_queue_size_; }
|
||||
|
||||
class debug {
|
||||
public:
|
||||
static void print_all_header_fields(const http::request<http::string_body>& req);
|
||||
@@ -150,6 +155,7 @@ private:
|
||||
std::size_t max_ws_message_size_ = 16 * 1024 * 1024; // 默认 16MB,与 Beast 默认一致
|
||||
std::chrono::seconds header_timeout_{10}; // 头/握手超时,防 Slowloris(对齐 WAF ReadHeaderTimeout)
|
||||
std::chrono::seconds read_timeout_{30}; // 体超时(对齐 WAF ReadTimeout)
|
||||
std::size_t max_ws_queue_size_ = 1000; // WS 发送/代理队列上限,防慢消费者 OOM(P1-6)
|
||||
|
||||
bool ssl_enabled_{false};
|
||||
std::string cert_file_;
|
||||
|
||||
@@ -8,6 +8,14 @@ namespace net {
|
||||
void HttpServer::Session::wsSend(const std::string& message) {
|
||||
auto exec = stream_.get_executor();
|
||||
asio::post(exec, [self = shared_from_this(), message]() {
|
||||
// P1-6:超过队列上限,判定慢消费者,关闭连接防内存耗尽
|
||||
if (self->ws_send_queue_.size() >= self->server_->getMaxWsQueueSize()) {
|
||||
std::cerr << "[WS] ws_send_queue_ overflow (" << self->ws_send_queue_.size()
|
||||
<< " >= " << self->server_->getMaxWsQueueSize()
|
||||
<< ") from " << self->getRemoteAddress() << ", closing" << std::endl;
|
||||
self->close();
|
||||
return;
|
||||
}
|
||||
self->ws_send_queue_.push_back(message);
|
||||
if (self->ws_send_queue_.size() == 1) {
|
||||
self->doWsWrite();
|
||||
@@ -373,6 +381,13 @@ void HttpServer::Session::onProxyReadClient(beast::error_code ec, std::size_t by
|
||||
// Queue to backend
|
||||
auto exec = stream_.get_executor();
|
||||
asio::post(exec, [self = shared_from_this(), data = std::move(data), is_binary]() {
|
||||
// P1-6:超过队列上限,判定慢后端,关闭代理连接防内存耗尽
|
||||
if (self->proxy_to_backend_queue_.size() >= self->server_->getMaxWsQueueSize()) {
|
||||
std::cerr << "[WS-Proxy] proxy_to_backend_queue_ overflow ("
|
||||
<< self->proxy_to_backend_queue_.size() << ") closing" << std::endl;
|
||||
self->closeProxy(websocket::close_code::internal_error);
|
||||
return;
|
||||
}
|
||||
self->proxy_to_backend_queue_.emplace_back(std::move(data), is_binary);
|
||||
if (self->proxy_to_backend_queue_.size() == 1) {
|
||||
self->doProxyWriteBackend();
|
||||
@@ -449,6 +464,13 @@ void HttpServer::Session::onProxyReadBackend(beast::error_code ec, std::size_t b
|
||||
// Queue to client
|
||||
auto exec = stream_.get_executor();
|
||||
asio::post(exec, [self = shared_from_this(), data = std::move(data), is_binary]() {
|
||||
// P1-6:超过队列上限,判定慢客户端,关闭代理连接防内存耗尽
|
||||
if (self->proxy_to_client_queue_.size() >= self->server_->getMaxWsQueueSize()) {
|
||||
std::cerr << "[WS-Proxy] proxy_to_client_queue_ overflow ("
|
||||
<< self->proxy_to_client_queue_.size() << ") closing" << std::endl;
|
||||
self->closeProxy(websocket::close_code::internal_error);
|
||||
return;
|
||||
}
|
||||
self->proxy_to_client_queue_.emplace_back(std::move(data), is_binary);
|
||||
if (self->proxy_to_client_queue_.size() == 1) {
|
||||
self->doProxyWriteClient();
|
||||
|
||||
Reference in new issue
Block a user