diff --git a/CHANGELOG.md b/CHANGELOG.md
index a2aa670..b254534 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,11 +11,11 @@
`1.1.x` → `v1.2.0`、`1.2.x` → `v1.3.0`、`1.3.x` → `v1.4.0`、`1.4.1`–`1.4.4` → `v1.5.0`、
`1.5.1`–`1.5.3` → `v1.6.0`;`1.6.1`–`1.6.4` 是它之后的补丁版,不折叠、直接发布;
`1.6.5`–`1.6.61` → `v1.7.0`;`1.7.1`–`1.7.3` 是 `v1.7.0` 之后的开发档线,折进
-`v1.7.4`;`1.7.5`–`1.7.6` 折进 `v1.8.0`;`1.8.1`、`1.8.2`、`1.8.3`、`1.8.4`、`1.8.5`、`1.8.6` 是它之后的补丁版,
+`v1.7.4`;`1.7.5`–`1.7.6` 折进 `v1.8.0`;`1.8.1`、`1.8.2`、`1.8.3`、`1.8.4`、`1.8.5`、`1.8.6`、`1.8.7`、`1.8.8` 是它之后的补丁版,
不折叠、直接发布。想按时间线读,翻到最后的「逐个版本」。
-当前源码树是 **1.8.6**(发布版) —— 即 `src/uvcpp/uvcpp_version.h` 里 `UVCPP_VERSION_STRING`
-报告的那个串。已打过的 tag:`v1.0.0`、`v1.1.0`、`v1.2.0`、`v1.3.0`、`v1.4.0`、`v1.5.0`、`v1.6.0`、`v1.6.1`、`v1.6.2`、`v1.6.3`、`v1.6.4`、`v1.7.0`、`v1.7.4`、`v1.8.0`、`v1.8.1`、`v1.8.2`、`v1.8.3`、`v1.8.4`、`v1.8.5`、`v1.8.6`。
+当前源码树是 **1.8.8**(发布版) —— 即 `src/uvcpp/uvcpp_version.h` 里 `UVCPP_VERSION_STRING`
+报告的那个串。已打过的 tag:`v1.0.0`、`v1.1.0`、`v1.2.0`、`v1.3.0`、`v1.4.0`、`v1.5.0`、`v1.6.0`、`v1.6.1`、`v1.6.2`、`v1.6.3`、`v1.6.4`、`v1.7.0`、`v1.7.4`、`v1.8.0`、`v1.8.1`、`v1.8.2`、`v1.8.3`、`v1.8.4`、`v1.8.5`、`v1.8.6`、`v1.8.7`、`v1.8.8`。
其中若干条来自本仓第一位外部贡献者 [@sercebr](https://github.com/sercebr) 报的 issue。
## 目录
@@ -471,6 +471,9 @@
### HTTP/3(web 层)
+- **extended CONNECT 流的发送队列上限**(`1.8.7`):`write_stream_data()` 的
+ 待发队列限制在 4 MiB,接收端不读(QUIC 窗口打满、nghttp3 停止消费)时整笔
+ 拒绝并返回 `UV_ENOBUFS`,不再无界增长到 OOM。
- **`uvcpp_h3_ws_server` 便捷封装**(`1.8.6`,#58):把 "QUIC 连接 →
`uvcpp_h3_connection` → `on_connect_request` → `uvcpp_h3_ws_connection::accept`"
这段每条应用都要手拼一遍的生命周期收进库里 —— 每条连接一个 h3 连接、按流号
@@ -1340,6 +1343,9 @@ ODBC** 四个后端,结果行按表取(列名或列号)。**默认关**(
### HTTP 与 WebSocket 语义
+- **`connect_wait()` 超时后迟到的连接回调不再写死栈帧**(`1.8.7`):回调状态
+ 改到堆上(`shared_ptr`)。连接超时返回后客户端析构会泵循环,先前那一笔迟到
+ 回调写进已退栈的局部变量,表现为 `SIGSEGV in ~uvcpp_http_client`。
- **v120 上的 WebSocket:帧掩码的两处编译缺口**(`1.8.4`)。`uvcpp_ws_frame` 的
`mask_key[4] = {0}` 是数组成员上的默认初始化器(v120 报 C2536),改成构造函数清零;
掩码 PRNG 的 `static thread_local std::mt19937_64`(类类型,v120 的
@@ -1573,6 +1579,12 @@ ODBC** 四个后端,结果行按表取(列名或列号)。**默认关**(
### TLS 与网络
+- **客户端析构不再漏掉 `uvcpp_ssl`**(`1.8.8`):`~uvcpp_tcp_client()` 的
+ "循环还在跑"分支原先只把 `tls_ssl_` 置空、不释放,每接一条 TLS 连接就漏
+ 一个 `uvcpp_ssl` 及其 OpenSSL 状态(实测 ~57 KiB/连接;20 条连接 valgrind
+ 报 1,172,840 字节)。现在按句柄状态释放:句柄已摘 ⇒ 当场删;句柄活着 ⇒
+ 交给 `uv_close` 的完成回调(那时调用栈已解开);关闭槽已被占 ⇒ 与包装对象
+ 同一条已知的罕见泄漏路径并告警。
- **域名解析改用 `AF_UNSPEC`,多地址逐个尝试**(`1.8.2`,#44):异步/同步两条路径
都按 `getaddrinfo` 顺序尝试,全部失败返回最后一次的真实错误;SNI 与证书校验仍用
目标域名。SOCKS5 代理连接与 `connect_wait()` 一并受益。
@@ -4006,6 +4018,33 @@ ODBC** 四个后端,结果行按表取(列名或列号)。**默认关**(
这一节按发版时间倒序,只列**已发布**的 tag。开发档线已折叠进对应的稳定版
(折叠表见开头),所以这里读到的每一版都是一个真实的 tag。
+### v1.8.8 (2026-10-27)
+
+`v1.8.7` 之后的补丁:**TLS 客户端析构的内存泄漏**。
+
+**修复**:
+
+- `~uvcpp_tcp_client()` 在"事件循环还在跑"那条分支上只把 `tls_ssl_` 置空、没有
+ 释放 `enable_tls()` 分配的 `uvcpp_ssl`。服务端每接受一条 TLS 连接就漏一份
+ (valgrind:20 条连接 1,172,840 字节 definitely lost;实测 500 条短连接
+ RSS +26 MB,常驻服务在空闲时仍以 12–14 MB/min 增长)。现在按句柄状态释放:
+ 句柄已摘 ⇒ 当场删;句柄活着 ⇒ 交给 `uv_close` 的完成回调;关闭槽已被占
+ ⇒ 与包装对象同一条已知罕见的泄漏路径并告警(`tests/functional` 既有
+ TLS 用例覆盖所有权时序,本版未新增用例)。
+
+### v1.8.7 (2026-10-26)
+
+`v1.8.6` 的补丁:目录/服务端不可达时不再崩溃,并给 HTTP/3 扩展 CONNECT 流
+加上发送队列上限。
+
+**修复**:
+
+- `uvcpp_http_client::connect_wait()` 的连接回调状态改到堆上。连接超时返回后,
+ 析构客户端会泵事件循环;迟到的回调不再写入已失效的栈帧(此前表现为
+ `SIGSEGV in ~uvcpp_http_client`)。
+- `uvcpp_h3_session::write_stream_data()` 的待发队列限制为 4 MiB,接收端不读时
+ 返回 `UV_ENOBUFS`,避免 h3 中继把进程拖到 OOM。
+
### v1.8.6 (2026-10-26)
`v1.8.5` 之后的一版:**浏览器那条 h2 WebSocket 通了**(RFC 8441 extended
diff --git a/README.md b/README.md
index d6cfaee..19097f2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
-[](./RELEASE.md)
+[](./RELEASE.md)
[](./LICENSE)
[](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml)
[](https://github.com/Antruly/libuvcpp/actions/workflows/ci-windows-msvc.yml)
@@ -14,25 +14,25 @@
🔧 Modern C++11 wrapper for [libuv](https://github.com/libuv/libuv) — event-driven I/O with
object-oriented APIs, dual-mode async/sync support, HTTP/1.1, WebSocket (RFC 6455), and SSL/TLS.
-- **Version**: `1.8.6` — **Author**: `zhuweiye` — **License**: `MIT`
+- **Version**: `1.8.8` — **Author**: `zhuweiye` — **License**: `MIT`
- **Languages**: [English](./README.md) · [中文](./README.zh.md)
## Download
-The newest release is **v1.8.6**. Six prebuilt packages, each one self-contained — the
+The newest release is **v1.8.8**. Six prebuilt packages, each one self-contained — the
Release and Debug builds of the library, all the public headers, `uvcpp.pc` and the CMake
package files, and no extra DLLs or `.so` files to ship alongside. Which modules a given
package enables is recorded per version in [CHANGELOG.md](./CHANGELOG.md).
| Platform | Toolchain | Package |
|---|---|---|
-| Windows x64 | MSVC 2022 | [libuvcpp-1.8.6-msvc-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-msvc-x64.zip) · 24.0 MB |
-| Windows arm64 | MSVC 2022 | [libuvcpp-1.8.6-msvc-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-msvc-arm64.zip) · 24.9 MB |
-| Windows x64 | MinGW-w64 (GCC) | [libuvcpp-1.8.6-mingw-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-mingw-x64.zip) · 29.8 MB |
-| Windows arm64 | MinGW-w64 (clang + libc++) | [libuvcpp-1.8.6-mingw-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-mingw-arm64.zip) · 22.7 MB |
-| Linux x64 | GCC | [libuvcpp-1.8.6-linux-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-linux-x64.zip) · 27.3 MB |
-| Linux arm64 | GCC | [libuvcpp-1.8.6-linux-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-linux-arm64.zip) · 27.5 MB |
+| Windows x64 | MSVC 2022 | [libuvcpp-1.8.7-msvc-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-msvc-x64.zip) · 24.0 MB |
+| Windows arm64 | MSVC 2022 | [libuvcpp-1.8.7-msvc-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-msvc-arm64.zip) · 24.9 MB |
+| Windows x64 | MinGW-w64 (GCC) | [libuvcpp-1.8.7-mingw-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-mingw-x64.zip) · 29.8 MB |
+| Windows arm64 | MinGW-w64 (clang + libc++) | [libuvcpp-1.8.7-mingw-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-mingw-arm64.zip) · 22.7 MB |
+| Linux x64 | GCC | [libuvcpp-1.8.7-linux-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-linux-x64.zip) · 27.3 MB |
+| Linux arm64 | GCC | [libuvcpp-1.8.7-linux-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-linux-arm64.zip) · 27.5 MB |
Every earlier release is on the **[Releases page](https://github.com/Antruly/libuvcpp/releases)**.
What a package contains, and why it is laid out that way:
diff --git a/README.zh.md b/README.zh.md
index fb92781..c204467 100644
--- a/README.zh.md
+++ b/README.zh.md
@@ -2,7 +2,7 @@
-[](./RELEASE.md)
+[](./RELEASE.md)
[](./LICENSE)
[](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml)
[](https://github.com/Antruly/libuvcpp/actions/workflows/ci-windows-msvc.yml)
@@ -14,24 +14,24 @@
🔧 基于 [libuv](https://github.com/libuv/libuv) 的现代 C++11 封装库 — 面向对象的异步 I/O,
支持双模式(异步回调/同步等待)、HTTP/1.1、WebSocket(RFC 6455)和 SSL/TLS。
-- **版本**:`1.8.6` — **作者**:`zhuweiye` — **许可证**:`MIT`
+- **版本**:`1.8.8` — **作者**:`zhuweiye` — **许可证**:`MIT`
- **语言**:[English](./README.md) · [中文](./README.zh.md)
## 下载
-最新版本是 **v1.8.6**。六个预编译包,每一个都自包含 —— 带 Release 与 Debug 两档库、
+最新版本是 **v1.8.8**。六个预编译包,每一个都自包含 —— 带 Release 与 Debug 两档库、
全部公开头文件、`uvcpp.pc` 与 CMake 包配置,旁边不需要再放任何 DLL 或 `.so`。
某一版的包里到底开了哪些模块,按版本记在 [CHANGELOG.md](./CHANGELOG.md) 里。
| 平台 | 工具链 | 包 |
|---|---|---|
-| Windows x64 | MSVC 2022 | [libuvcpp-1.8.6-msvc-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-msvc-x64.zip) · 24.0 MB |
-| Windows arm64 | MSVC 2022 | [libuvcpp-1.8.6-msvc-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-msvc-arm64.zip) · 24.9 MB |
-| Windows x64 | MinGW-w64(GCC) | [libuvcpp-1.8.6-mingw-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-mingw-x64.zip) · 29.8 MB |
-| Windows arm64 | MinGW-w64(clang + libc++) | [libuvcpp-1.8.6-mingw-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-mingw-arm64.zip) · 22.7 MB |
-| Linux x64 | GCC | [libuvcpp-1.8.6-linux-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-linux-x64.zip) · 27.3 MB |
-| Linux arm64 | GCC | [libuvcpp-1.8.6-linux-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.6/libuvcpp-1.8.6-linux-arm64.zip) · 27.5 MB |
+| Windows x64 | MSVC 2022 | [libuvcpp-1.8.7-msvc-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-msvc-x64.zip) · 24.0 MB |
+| Windows arm64 | MSVC 2022 | [libuvcpp-1.8.7-msvc-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-msvc-arm64.zip) · 24.9 MB |
+| Windows x64 | MinGW-w64(GCC) | [libuvcpp-1.8.7-mingw-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-mingw-x64.zip) · 29.8 MB |
+| Windows arm64 | MinGW-w64(clang + libc++) | [libuvcpp-1.8.7-mingw-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-mingw-arm64.zip) · 22.7 MB |
+| Linux x64 | GCC | [libuvcpp-1.8.7-linux-x64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-linux-x64.zip) · 27.3 MB |
+| Linux arm64 | GCC | [libuvcpp-1.8.7-linux-arm64.zip](https://github.com/Antruly/libuvcpp/releases/download/v1.8.7/libuvcpp-1.8.7-linux-arm64.zip) · 27.5 MB |
更早的版本都在 **[Releases 页](https://github.com/Antruly/libuvcpp/releases)**。
包里装了什么、为什么是这个形状:[doc/release-process.md](doc/release-process.md)。
diff --git a/RELEASE.md b/RELEASE.md
index 4539c8a..66e13f9 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -19,6 +19,24 @@
**libuvcpp** 是一个基于 libuv 的现代 C++ 封装库,提供简洁的面向对象接口来使用 libuv 的异步 I/O 功能。
+## v1.8.8 重点 (Highlights)
+
+`v1.8.7` 的补丁:**TLS 客户端析构泄漏 `uvcpp_ssl`**。
+
+- 服务端每接受一条 TLS 连接,`~uvcpp_tcp_client()` 都会漏掉一份 `uvcpp_ssl` 及其
+ OpenSSL 状态(实测 ~57 KiB/连接)。改动后按句柄状态释放:句柄已摘就地删、
+ 句柄活着交给 `uv_close` 的完成回调,只有"关闭槽已被占"这条已知罕见的路径
+ 继续告警并留下。
+- 其余行为与 1.8.7 一致;六个平台包与调试档不变。
+
+## v1.8.7 重点 (Highlights)
+
+`v1.8.6` 的补丁:目录/服务端不可达时不再崩溃,并给 HTTP/3 扩展 CONNECT 流加上发送队列上限。
+
+- **不可达服务器的析构崩溃**:`uvcpp_http_client::connect_wait()` 的连接回调状态改到堆上。连接超时返回后,析构客户端会泵事件循环;迟到的回调不再写入已失效的栈帧(此前表现为 `SIGSEGV in ~uvcpp_http_client`)。
+- **HTTP/3 发送队列上限**:`write_stream_data()` 的待发队列限制为 4 MiB,接收端不读时返回 `UV_ENOBUFS`,避免 h3 中继把进程拖到 OOM。
+- 其余行为与 1.8.6 一致;六个平台包与调试档不变。
+
## v1.8.6 重点 (Highlights)
`v1.8.5` 之后的一版:**WebSocket 不再只有 HTTP/1.1 那一档**(#58)。
diff --git a/doc/http2-guide.md b/doc/http2-guide.md
index bb9d338..9bf9601 100644
--- a/doc/http2-guide.md
+++ b/doc/http2-guide.md
@@ -279,7 +279,7 @@ done)` → 最后一块 `end_stream = true`。三处要点:
**低层没有请求侧的合并入口** —— `uvcpp_h2_connection` 上的四个 `send_*` 全是响应侧的。
客户端提交完请求**必须自己调 `flush()`**。这一点在
`tests/functional/web_ssl_h2_server_func.cpp:461-461` 有一段专门的告诫,
-库内正解在 `src/web/uvcpp_http_client.cpp:1840-1840`:
+库内正解在 `src/web/uvcpp_http_client.cpp:1849-1849`:
```cpp
// doc-snippet: fragment — 从库内调用点摘的三句,前后文不在本页
@@ -314,10 +314,10 @@ void doc_h2_client_submit(uvcpp::uvcpp_h2_connection* conn) {
```
`uvcpp_http_client` 那条线是同一套顺序:`set_http2_enabled(true)`
-(`src/web/uvcpp_http_client.cpp:1744-1744`)→ `connect()` 里 `enable_tls` 加 ALPN 名单
+(`src/web/uvcpp_http_client.cpp:1753-1753`)→ `connect()` 里 `enable_tls` 加 ALPN 名单
(`src/web/uvcpp_http_client.cpp:292-292` / `src/web/uvcpp_http_client.cpp:308-308`,`h2` 在前、`http/1.1` 兜底)→ connect 成功回调里读
-`tls_alpn_selected()`(`src/web/uvcpp_http_client.cpp:327-327`)→ `start_h2()`(`src/web/uvcpp_http_client.cpp:331-331` → `src/web/uvcpp_http_client.cpp:1772-1772`)→ 装回调(`src/web/uvcpp_http_client.cpp:1774-1774`)
-→ `h2_->start(sc, cc)`(`src/web/uvcpp_http_client.cpp:1815-1815`)→ `send()` 分流到 `send_h2`(`src/web/uvcpp_http_client.cpp:480-480`)。
+`tls_alpn_selected()`(`src/web/uvcpp_http_client.cpp:327-327`)→ `start_h2()`(`src/web/uvcpp_http_client.cpp:331-331` → `src/web/uvcpp_http_client.cpp:1781-1781`)→ 装回调(`src/web/uvcpp_http_client.cpp:1783-1783`)
+→ `h2_->start(sc, cc)`(`src/web/uvcpp_http_client.cpp:1824-1824`)→ `send()` 分流到 `send_h2`(`src/web/uvcpp_http_client.cpp:489-489`)。
**`set_http2_enabled` 默认是关的**(`src/web/uvcpp_http_client.h:624-624`、
`src/web/uvcpp_http_server.h:357-357`)—— 低层的两条线都要显式打开,
@@ -349,7 +349,7 @@ void doc_h2_client_submit(uvcpp::uvcpp_h2_connection* conn) {
**二、客户端交付响应只能放 `on_response_end`。** 带 body 的响应里 `on_response` 的
`end_stream` **恒为 false**,而 DATA 的 END_STREAM 不经过任何回调 ——
没有 `on_response_end` 就分不出"响应到头了"和"响应全收完了"
-(`src/web/uvcpp_http_client.cpp:1786-1786`、`src/http2/uvcpp_h2_session.h:155-161`)。
+(`src/web/uvcpp_http_client.cpp:1795-1795`、`src/http2/uvcpp_h2_session.h:155-161`)。
另外,**服务端侧的 `uvcpp_h2_stream::response` 初值是 `HTTP_STATUS_NONE` 而不是 `200`**
(`src/http2/uvcpp_h2_session.h:71`)。这不是疏漏:流在响应头到达之前被 RST
@@ -473,7 +473,7 @@ if (rv < 0) { // src/http2/uvcpp_h2_session.cpp:973-973
可不可重试的判定**不在这一层**,在 `web/` 侧:`H2_ERR_REFUSED_STREAM` 才算
`retryable`,`NO_ERROR` 与 `CANCEL` 都算正常收尾
-(`src/web/uvcpp_http_client.cpp:1926-1926`)。
+(`src/web/uvcpp_http_client.cpp:1935-1935`)。
---
diff --git a/doc/http2-status.md b/doc/http2-status.md
index 97e461b..441d1ba 100644
--- a/doc/http2-status.md
+++ b/doc/http2-status.md
@@ -53,7 +53,7 @@
会把明文整条腿判死,而两个都收等于给混淆代理开后门,RFC 9113 §8.1.1)、连接专属头一律拒、
重复且不一致的 `content-length` 即拒、多份 `cookie` 按 `; ` 拼回原样、
收尾的 trailer 识别成"流的结束信号"(`src/http2/uvcpp_h2_session.cpp:590-590`)。
-- **流关闭的错误码分三档**(`src/web/uvcpp_http_client.cpp:1906-1906`,RFC 9113 §8.7):
+- **流关闭的错误码分三档**(`src/web/uvcpp_http_client.cpp:1915-1915`,RFC 9113 §8.7):
`NO_ERROR` 是我们自己收摊、`REFUSED_STREAM(7)` 是"这条请求没被处理过"、
`CANCEL(8)` 是"对端不要这条流了" —— 三档都报 `UV_ECANCELED`;其余一律
`UV_EPROTO`(协议失败)。其中**只有 `REFUSED_STREAM`** 会把
@@ -159,10 +159,10 @@
也是空的,一个字节都不影响发出去的请求。
现在两个方向都兑现了:响应说 `close`(含 `Connection` 逗号列表里不在首项的
`close`)就在交付回调之前清掉 `HTTP_CLIENT_CONNECTED`
- (`src/web/uvcpp_http_client.cpp:927-947`),第二次 `send()` 同步拒成
+ (`src/web/uvcpp_http_client.cpp:936-956`),第二次 `send()` 同步拒成
`UV_ENOTCONN` —— 与 h2 那条 `peer_goaway_received()` 的提前拦截同一形状;
`set_keep_alive(false)` 则让请求真的带上 `connection: close`
- (`src/web/uvcpp_http_client.cpp:582-601`)。
+ (`src/web/uvcpp_http_client.cpp:591-610`)。
用例:`tests/functional/web_http_client_keepalive_func.cpp`。
两件事都属于 h1,与本页的 h2 无关,留在这儿只为了让下次盘查的人不必再核一遍。
diff --git a/doc/net-guide.md b/doc/net-guide.md
index b0d424b..686ca45 100644
--- a/doc/net-guide.md
+++ b/doc/net-guide.md
@@ -534,7 +534,7 @@ n−1 条工作线程起好了(见 §4),而接着的 `bind()` / `listen()`
| `set_read_callback(cb)` | 服务端 `:265` | 同上 | 一份回调覆盖所有连接 |
**`read_start` 与 `read_start_events` 互斥。** 用过其中一个再用另一个,拿到
-`UV_EALREADY`。挡在前面的理由写在实现里(`src/net/uvcpp_tcp_client.cpp:3129-3137`):
+`UV_EALREADY`。挡在前面的理由写在实现里(`src/net/uvcpp_tcp_client.cpp:3165-3173`):
两条路共用同一个底层 stream,同时注册的话底层 `read_start` 会把先注册的那个
**静默覆盖**掉——用户以为两个回调都在收数据,实际只有一个。
@@ -674,7 +674,7 @@ int write_wait(uvcpp_buf* buf, int timeout_ms = 30000);
| 已经有异步写在飞,再调异步 `write` | 返回 `UV_EALREADY` |
| 没连上就写 | 返回 `UV_ENOTCONN` |
-**异步/同步其实是由 `cb` 是不是空决定的**(`src/net/uvcpp_tcp_client.cpp:2129-2132`):
+**异步/同步其实是由 `cb` 是不是空决定的**(`src/net/uvcpp_tcp_client.cpp:2165-2168`):
`cb` 非空 ⇒ 立即返回 0,完成时回调;`cb` 为空 ⇒ 退化成 `write_wait(data, len, 30000)`。
**这就是为什么回调里必须传 cb。** 事件循环回调里禁止同步等待,而三参数的
diff --git a/doc/ssl-guide.md b/doc/ssl-guide.md
index 1125fe2..50b6316 100644
--- a/doc/ssl-guide.md
+++ b/doc/ssl-guide.md
@@ -342,7 +342,7 @@ read/write(): > 0 = 处理的字节数,0 = 需要更多 I/O,< 0 = 真出错
**`is_ready()` 不反映证书装载失败。** `status_` **只在构造时**赋值
(`src/ssl/uvcpp_ssl_context.cpp:68,70,75,77`),之后任何 `load_*` 失败都**不改**它。而
`uvcpp_tcp_client::enable_tls()` 恰好只查 `ctx->is_ready()`
-(`src/net/uvcpp_tcp_client.cpp:3423-3423`)——所以**必须看各 `load_*` 的返回值**,
+(`src/net/uvcpp_tcp_client.cpp:3459-3459`)——所以**必须看各 `load_*` 的返回值**,
不能只看 `is_ready()`。
**先装私钥后装证书会返回 `false`**,见 §4。
diff --git a/doc/web-http-guide.md b/doc/web-http-guide.md
index eb3a7d2..6cd4888 100644
--- a/doc/web-http-guide.md
+++ b/doc/web-http-guide.md
@@ -526,35 +526,35 @@ int doc_client_sync() {
```
**同一个 client 上同步与异步不能交叉**,越界一律 `UV_ENOTSUP`
-(`src/web/uvcpp_http_client.cpp:288-288`、`src/web/uvcpp_http_client.cpp:471-471`、`src/web/uvcpp_http_client.cpp:753-753`、`src/web/uvcpp_http_client.cpp:762-762`)。
+(`src/web/uvcpp_http_client.cpp:288-288`、`src/web/uvcpp_http_client.cpp:480-480`、`src/web/uvcpp_http_client.cpp:762-762`、`src/web/uvcpp_http_client.cpp:771-771`)。
### 三个容易写错的地方
**一、keep-alive 有两个方向:请求那头听调用方的,响应那头听对端的。**
`keep_alive_` 是**调用方的偏好**(`src/web/uvcpp_http_client.h:566-566` 初值 `true`、
-`src/web/uvcpp_http_client.h:295-295` 声明 `set_keep_alive`、`src/web/uvcpp_http_client.cpp:1079-1079` setter)。`set_keep_alive(false)`
-会让请求报文带上 `connection: close`(`src/web/uvcpp_http_client.cpp:582-601`);
+`src/web/uvcpp_http_client.h:295-295` 声明 `set_keep_alive`、`src/web/uvcpp_http_client.cpp:1088-1088` setter)。`set_keep_alive(false)`
+会让请求报文带上 `connection: close`(`src/web/uvcpp_http_client.cpp:591-610`);
默认的 `true` 则不补头,报文里那条 `connection: keep-alive` 是
`uvcpp_http_request::to_string()` 补的(`src/web/uvcpp_http_request.cpp:156-159`)。
**响应**说的同样作数:`on_response_complete()` 按 llhttp 算出的
`should_keep_alive()`(HTTP 版本默认值 + `Connection` 的**逗号列表**)判断这条连接
还能不能再用,不能就当场清掉 `HTTP_CLIENT_CONNECTED`
-(`src/web/uvcpp_http_client.cpp:927-947`)—— 此后第二次 `send()` 拿到
+(`src/web/uvcpp_http_client.cpp:936-956`)—— 此后第二次 `send()` 拿到
`UV_ENOTCONN`,而不是往一条服务端已决定关闭的连接上写。
`tests/functional/web_http_client_keepalive_func.cpp` 把这三条都钉住了。
**二、开了 OpenSSL 的构建里 `send_wait()` 走的是阻塞 fd,不是事件循环。**
-`src/web/uvcpp_http_client.cpp:767-784` 是一个 `#if UVCPP_OPENSSL_ENABLE` 分支:
+`src/web/uvcpp_http_client.cpp:776-793` 是一个 `#if UVCPP_OPENSSL_ENABLE` 分支:
有 TLS 就走阻塞路径,没有才走"轮询 loop + 1ms sleep"的异步实现。
-后果是**同一个程序的 keep-alive 行为会随构建而变**(`src/web/uvcpp_http_client.cpp:1331-1334` 把这段历史写下来了)。
+后果是**同一个程序的 keep-alive 行为会随构建而变**(`src/web/uvcpp_http_client.cpp:1340-1343` 把这段历史写下来了)。
**三、阻塞路径的 `timeout_ms` 是整次请求的总 deadline(两个平台都生效)。**
`setsockopt(SO_RCVTIMEO/SO_SNDTIMEO)` 统一走 `set_socket_timeout()`
-(`src/web/uvcpp_http_client.cpp:1114-1129`):Windows 用毫秒 `DWORD`,POSIX 用
+(`src/web/uvcpp_http_client.cpp:1123-1138`):Windows 用毫秒 `DWORD`,POSIX 用
`struct timeval`。进入 `send_wait_plain()` / `send_wait_ssl()` 时算一次单调时钟
deadline,**每次读/写之前**按剩余时间收紧这个选项(`sync_recv_until()` /
`sync_ssl_read_until()`),到点返回 `UV_ETIMEDOUT`;`connect_wait()` 的 TCP
-连接与 TLS 握手共用同一个 deadline(`src/web/uvcpp_http_client.cpp:429-429`)。
+连接与 TLS 握手共用同一个 deadline(`src/web/uvcpp_http_client.cpp:438-438`)。
于是对端每隔略小于 `timeout_ms` 发一个字节也拖不长整次请求 —— `1.8.2` 之前
它是**每次 I/O** 的超时,这种"滴字节"的对端能把请求无限拖下去。
头文件的两处参数说明同步改了(`src/web/uvcpp_http_client.h:181-185`、
@@ -562,12 +562,12 @@ deadline,**每次读/写之前**按剩余时间收紧这个选项(`sync_recv
**四、阻塞路径失败时不再交默认的 200 OK。**
`read_one_message()` 把读错误、超时和"半条报文"作为负的 libuv 错误码往上传
-(`src/web/uvcpp_http_client.cpp:1339-1346`),两条 `send_wait_*` 在入口就把
+(`src/web/uvcpp_http_client.cpp:1348-1355`),两条 `send_wait_*` 在入口就把
`resp.status_code` 复位成 `HTTP_STATUS_NONE`
-(`src/web/uvcpp_http_client.cpp:1511-1511`、`src/web/uvcpp_http_client.cpp:1614-1614`),
+(`src/web/uvcpp_http_client.cpp:1520-1520`、`src/web/uvcpp_http_client.cpp:1623-1623`),
并把错误码原样返回。**判据只能是返回值**(`status_code == 0` 只说明"没收到过
状态行")。(异步 h1 路径同理:错误时也会把 `status_code` 设成
-`HTTP_STATUS_NONE`,`src/web/uvcpp_http_client.cpp:499-501`。)
+`HTTP_STATUS_NONE`,`src/web/uvcpp_http_client.cpp:508-510`。)
**五、TLS 客户端的对端证书是公开的。** `set_ssl_context()` 之后、连接建立到
关闭之前,可以读 `peer_cert_chain_der()`(DER、叶子在前)、
diff --git a/doc/webapp-guide.md b/doc/webapp-guide.md
index 1c3bc3e..e0a1f63 100644
--- a/doc/webapp-guide.md
+++ b/doc/webapp-guide.md
@@ -908,7 +908,7 @@ void set_ssl_context(uvcpp_ssl_context* ctx); // 仅 UVCPP_OPENSSL_ENABLE
协议层的 `uvcpp_ws_client` **不承诺可重连**:它内部那个 `uvcpp_tcp_client` 的句柄关掉
之后 `uvcpp_tcp` 会把底层指针置空,且 `has_async_connect_cb_` 在明文成功路径上从不清除
-(`src/net/uvcpp_tcp_client.cpp:893-897`)——**同一个协议层客户端对象根本连不了第二次**。
+(`src/net/uvcpp_tcp_client.cpp:929-933`)——**同一个协议层客户端对象根本连不了第二次**。
所以本层的做法是:每次连接尝试都换一个协议层客户端,新客户端带一个新的事件循环,在
`connect()` 之前把本层存下的回调重新装一遍。对使用者不可见。重连定时器挂在 `inner_` 的
@@ -1415,7 +1415,7 @@ uvcpp_logger::instance().flush(); // 转发给 sink 的 flush()
| **静态 dotfile 默认值是 404(`HIDE`)** | 枚举成员刻意**不**叫 `IGNORE` —— `winnt.h` 有 `#define IGNORE 0`,而枚举类的作用域挡不住宏(§7)。 |
| **上传上限全 App 一份** | 没有按路由覆盖(§8)。 |
| **在回调里 `delete` 客户端:能用,但那份内存不回收** | 在 `connect()` / `send()` 的回调里 `delete` 掉 `uvcpp_http_client` 是**支持**的("响应回来就把客户端扔了"是本类最自然的用法),删掉之后不许再碰、也不许再 `send()`。代价是那一次析构**故意不拆内部对象**:`uvcpp_loop` + `uvcpp_tcp_client` + 解析器(h2 上再加一个 nghttp2 会话)留在循环上不释放 —— 换掉一个必然发生的 use-after-free(析构里那段"泵到句柄关完"会把**还压在栈上**的那条读路径再叫一遍,实测 `0xC0000005`)。要让内存回归,就别在回调里删:回调里置一个标志,循环退出之后再删。 |
-| **`uvcpp_http_client` 只在"有请求在飞"时才看得见对端断开** | 异步路径上对端在响应收完之前断开,`send()` 的回调会**落地**并以 `UV_ECONNRESET` 交付(本层写死的值,与传输层看到 EOF 还是 RST 无关),`HTTP_CLIENT_CONNECTED` 同时被清掉,此后的 `send()` 直接回 `UV_ENOTCONN` 而不是写进一条死 socket。**但只 `connect()` 过、从没 `send()` 过的客户端仍然不知道** —— 关闭通知只能搭在读路径上,而读是在第一次 `send()` 里才装的(`src/web/uvcpp_http_client.cpp:563-574`)。 |
+| **`uvcpp_http_client` 只在"有请求在飞"时才看得见对端断开** | 异步路径上对端在响应收完之前断开,`send()` 的回调会**落地**并以 `UV_ECONNRESET` 交付(本层写死的值,与传输层看到 EOF 还是 RST 无关),`HTTP_CLIENT_CONNECTED` 同时被清掉,此后的 `send()` 直接回 `UV_ENOTCONN` 而不是写进一条死 socket。**但只 `connect()` 过、从没 `send()` 过的客户端仍然不知道** —— 关闭通知只能搭在读路径上,而读是在第一次 `send()` 里才装的(`src/web/uvcpp_http_client.cpp:572-583`)。 |
| **h2 上"可以重试"只有 `REFUSED_STREAM` 一个来源** | 对端关掉一条 h2 流时分三档(RFC 9113 §8.7):`NO_ERROR`(多半是我们自己收摊)、`REFUSED_STREAM`(**这条请求没被处理过**)、`CANCEL`(对端不要这条流了)—— 三档都以 `UV_ECANCELED` 交付,其余错误码是 `UV_EPROTO`(协议失败)。其中只有 `REFUSED_STREAM` 会把 `resp.retryable` 置真。**假不等于"处理过了"**:连接断开是"结果未知",`CANCEL` 不保证对端没处理过(重发就是重复副作用),两者都为假 —— 判据只能是"真 ⇒ 可以重试",反过来推不成立。h1 上它恒为假(这个协议没有等价信号)。这个字段挂在**响应**上而不是客户端上:h2 一条连接同时有好几条流在飞,挂在客户端上分不清是哪一条。 |
| **多循环下跨线程读登记表仍未做** | `app.connection(id)` / `app.find(id)` 按 id 定位到**那一格**,但那一格里的容器不是线程安全的(§19)。**聚合量**(`connection_count()` / `inflight_count()`)不受影响,它们可以从任何线程读。 |
| **`set_loops(n>1)` 之后,运行中注册路由从"会跑错业务"升级成 UB** | 链缓存是全进程共享的一张表,n 条循环会同时读写它。单循环时最坏是命中一条过期链;多循环时没有"最坏"(§19)。 |
diff --git a/src/http3/uvcpp_h3_session.cpp b/src/http3/uvcpp_h3_session.cpp
index c1e9c49..1e0a7d0 100644
--- a/src/http3/uvcpp_h3_session.cpp
+++ b/src/http3/uvcpp_h3_session.cpp
@@ -546,7 +546,18 @@ int uvcpp_h3_session::write_stream_data(int64_t stream_id, const char* data,
if (it == streams_.end() || !it->second.out_streaming) return UV_EINVAL;
stream_rec& s = it->second;
if (s.out_eof) return UV_EINVAL;
- if (data != nullptr && len > 0) s.out_pending.append(data, len);
+ if (data != nullptr && len > 0) {
+ // Bound the application queue. A peer that stops reading needs its QUIC
+ // flow-control window filled before nghttp3 slows down; without this cap
+ // an extended-CONNECT relay (WebSocket over h3) can append here until the
+ // process is OOM-killed. Returning UV_ENOBUFS tells the caller to drop
+ // this message; MOLK's tile clock repairs the missing pixels.
+ constexpr std::size_t kMaxOutPendingBytes = 4u * 1024u * 1024u;
+ if (s.out_pending.size() + len > kMaxOutPendingBytes) {
+ return UV_ENOBUFS;
+ }
+ s.out_pending.append(data, len);
+ }
if (fin) s.out_eof = true;
// 之前 reader 返回过 WOULDBLOCK 的话,这一句把它重新排上。
nghttp3_conn_resume_stream(conn_, stream_id);
diff --git a/src/net/uvcpp_tcp_client.cpp b/src/net/uvcpp_tcp_client.cpp
index 14fe239..9e13a10 100644
--- a/src/net/uvcpp_tcp_client.cpp
+++ b/src/net/uvcpp_tcp_client.cpp
@@ -402,6 +402,18 @@ uvcpp_tcp_client::~uvcpp_tcp_client() {
// 0.74–1.17 MB/min,都不收敛)。
// -------------------------------------------------------------------
if (loop_ != nullptr && loop_->is_running()) {
+#if UVCPP_OPENSSL_ENABLE
+ // TLS 对象不能无条件当场 delete:析构可能正发生在自己的 TLS 读回调
+ // (`tls_feed` / `tls_flush_out`)里,那一帧还压着它。按句柄状态分三种:
+ // - 句柄已摘 / 没有句柄:不可能还有 TLS 回调在栈上,当场释放;
+ // - 句柄还活着且能排一次 uv_close:交给关闭完成回调(本轮 uv_run
+ // 收尾时跑,栈已解开),与 `read_arg_` 同一时机;
+ // - 关闭回调槽已被占:只能留下(与 wrapper 那条路相同,罕见且已告警)。
+ // 原先这里直接 `tls_ssl_ = nullptr;` 丢掉指针 —— 每次 TLS 连接泄漏一个
+ // `uvcpp_ssl` 及其 OpenSSL 状态(实测 ~57 KiB/连接)。
+ uvcpp_ssl* tls_orphan = tls_ssl_;
+ tls_ssl_ = nullptr;
+#endif
if (tcp_ != nullptr) {
const bool handle_detached = (tcp_->get_handle() == nullptr);
if (handle_detached) {
@@ -412,6 +424,10 @@ uvcpp_tcp_client::~uvcpp_tcp_client() {
delete static_cast*>(read_arg_);
read_arg_ = nullptr;
}
+#if UVCPP_OPENSSL_ENABLE
+ delete tls_orphan;
+ tls_orphan = nullptr;
+#endif
} else {
tcp_->read_stop();
if (!tcp_->is_closing()) {
@@ -424,14 +440,30 @@ uvcpp_tcp_client::~uvcpp_tcp_client() {
// 每次连接漏一个 `std::function` 块 —— 实测那是剩下的大头。
void* ra = read_arg_;
read_arg_ = nullptr;
+#if UVCPP_OPENSSL_ENABLE
+ uvcpp_ssl* tls = tls_orphan;
+ tls_orphan = nullptr;
+ tcp_->close([ra, tls](uvcpp_handle* wrapper) {
+ delete static_cast*>(ra);
+ delete tls;
+ delete wrapper;
+ g_reclaim_deferred.fetch_add(1, std::memory_order_relaxed);
+ });
+#else
tcp_->close([ra](uvcpp_handle* wrapper) {
delete static_cast*>(ra);
delete wrapper;
g_reclaim_deferred.fetch_add(1, std::memory_order_relaxed);
});
+#endif
} else {
// 槽被占,只能留下(见上)。
g_reclaim_skipped.fetch_add(1, std::memory_order_relaxed);
+#if UVCPP_OPENSSL_ENABLE
+ // 这一支同时把 `tls_orphan` 留下:读回调的帧可能还压在栈上,
+ // 无法安全释放(与 wrapper 同一个理由,已在下面告警)。
+ tls_orphan = nullptr;
+#endif
std::fprintf(stderr,
"[uvcpp_tcp_client] 析构时句柄正在关闭:包装对象的"
"关闭回调槽已被占用,只能留在事件循环里\n");
@@ -440,9 +472,13 @@ uvcpp_tcp_client::~uvcpp_tcp_client() {
tcp_ = nullptr;
}
#if UVCPP_OPENSSL_ENABLE
- // TLS 读路径上 `tls_ssl_` 正压在栈上(`tls_feed` / `tls_flush_out` 还在
- // 下面那几层里),不能删。
- tls_ssl_ = nullptr;
+ else {
+ // 没有 TCP 句柄 ⇒ 没有任何回调可能在栈上,可以直接释放。
+ delete tls_orphan;
+ tls_orphan = nullptr;
+ }
+#endif
+#if UVCPP_OPENSSL_ENABLE
// 握手超时定时器:先停掉(停是安全的,从它自己的回调里停也行)。
// 释放要分情况 —— 如果本对象正是在那个超时回调里被拆掉的,删除包装对象
diff --git a/src/uvcpp/uvcpp_version.h b/src/uvcpp/uvcpp_version.h
index 0935263..3d7d41f 100644
--- a/src/uvcpp/uvcpp_version.h
+++ b/src/uvcpp/uvcpp_version.h
@@ -14,7 +14,7 @@
#define UVCPP_VERSION_MAJOR 1
#define UVCPP_VERSION_MINOR 8
-#define UVCPP_VERSION_PATCH 6
+#define UVCPP_VERSION_PATCH 8
// 0 = 非发布版;1 = 发布版
#define UVCPP_VERSION_IS_RELEASE 1
diff --git a/src/web/uvcpp_http_client.cpp b/src/web/uvcpp_http_client.cpp
index c48a98b..7d386a4 100644
--- a/src/web/uvcpp_http_client.cpp
+++ b/src/web/uvcpp_http_client.cpp
@@ -383,12 +383,20 @@ int uvcpp_http_client::connect_wait(const char* host, int port,
}
#endif
- bool done = false;
- int result = 0;
-
- int init_rc = tcp_->connect(host, port, [&done, &result](int status) {
- result = status;
- done = true;
+ // The connect callback can fire *after* this function returns: on timeout
+ // the caller destroys the client, whose destructor pumps the loop to close
+ // the still-pending TCP handle. Capturing stack references here made that
+ // late callback write into a dead frame (SIGSEGV in
+ // ~uvcpp_http_client when the directory is unreachable). Keep the state on
+ // the heap so a late callback is harmless.
+ struct connect_state {
+ bool done = false;
+ int result = 0;
+ };
+ const auto state = std::make_shared();
+ int init_rc = tcp_->connect(host, port, [state](int status) {
+ state->result = status;
+ state->done = true;
});
if (init_rc != 0) {
// 连接未能发起(例如主机名解析失败)——立即返回,避免空等超时
@@ -399,7 +407,7 @@ int uvcpp_http_client::connect_wait(const char* host, int port,
auto start = std::chrono::steady_clock::now();
const std::chrono::steady_clock::time_point connect_deadline =
start + std::chrono::milliseconds(timeout_ms);
- while (!done) {
+ while (!state->done) {
loop_->run(UV_RUN_NOWAIT);
if (std::chrono::duration_cast(
std::chrono::steady_clock::now() - start)
@@ -409,6 +417,7 @@ int uvcpp_http_client::connect_wait(const char* host, int port,
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
+ const int result = state->result;
if (result == 0) {
set_status(HTTP_CLIENT_CONNECTED);
diff --git a/tests/tools/doc_line_refs.lock b/tests/tools/doc_line_refs.lock
index d70d008..60a9a06 100644
--- a/tests/tools/doc_line_refs.lock
+++ b/tests/tools/doc_line_refs.lock
@@ -305,13 +305,13 @@ src/net/uvcpp_net_read.h:64-66 43399d7dcc1030a1 6a7190feba1e96eb c06619ce873af23
src/net/uvcpp_net_read.h:68-68 46111d0759e14584 46111d0759e14584 -
src/net/uvcpp_socket_handoff.cpp:65-75 4d25a74c938455d1 236b2c747eac9d35 7819f4acd244ac14
src/net/uvcpp_socket_handoff.h:73-73 3686c76bdea51b26 3686c76bdea51b26 -
-src/net/uvcpp_tcp_client.cpp:2129-2132 f9b7ec492ca2bd44 0976d8ddeccb405c e05f5bee21b4ecaf
+src/net/uvcpp_tcp_client.cpp:2165-2168 f9b7ec492ca2bd44 0976d8ddeccb405c e05f5bee21b4ecaf
src/net/uvcpp_tcp_client.cpp:237-239 b9d221d83892b526 06638547ede78bae 9d56ef46118aa3bf
src/net/uvcpp_tcp_client.cpp:253-256 368c836c79756789 a30cd62faf3fc446 da95b372e94e3078
-src/net/uvcpp_tcp_client.cpp:3129-3137 2b97d438f82a8d95 2a4777d6668e546d 44b57a7c06710438
+src/net/uvcpp_tcp_client.cpp:3165-3173 2b97d438f82a8d95 2a4777d6668e546d 44b57a7c06710438
src/net/uvcpp_tcp_client.cpp:326-326 f1be8ca66f4149b5 f1be8ca66f4149b5 -
-src/net/uvcpp_tcp_client.cpp:3423-3423 d1b465733f53f68c d1b465733f53f68c -
-src/net/uvcpp_tcp_client.cpp:893-897 ed327f1941467537 79dc8849065080ef da39a3ee5e6b4b0d
+src/net/uvcpp_tcp_client.cpp:3459-3459 d1b465733f53f68c d1b465733f53f68c -
+src/net/uvcpp_tcp_client.cpp:929-933 ed327f1941467537 79dc8849065080ef da39a3ee5e6b4b0d
src/net/uvcpp_tcp_client.h:193-205 b82385b59a1dcbd5 431c7b51a40a3c3a c4d564fd38050022
src/net/uvcpp_tcp_client.h:217-217 0efd806ccd819af8 0efd806ccd819af8 -
src/net/uvcpp_tcp_client.h:221-221 760ad7b5012e9f1f 760ad7b5012e9f1f -
@@ -458,39 +458,39 @@ src/uvcpp/uvcpp_json_writer.h:137-137 aab8718d2ae03336 aab8718d2ae03336 -
src/uvcpp/uvcpp_json_writer.h:157-157 0d44ae99f979822a 0d44ae99f979822a -
src/uvcpp/uvcpp_json_writer.h:288-288 37533ef1554c9209 37533ef1554c9209 -
src/uvcpp/uvcpp_version.h:69-69 c53358a01bd0d639 c53358a01bd0d639 -
-src/web/uvcpp_http_client.cpp:1079-1079 643136f9dd3e1790 643136f9dd3e1790 -
-src/web/uvcpp_http_client.cpp:1114-1129 b118434eac1d8491 c2b7df6201fdd336 236b2c747eac9d35
-src/web/uvcpp_http_client.cpp:1331-1334 1b0964cd17867d3e be31bdd36bbbcfc4 8d91bb3c624bcd4b
-src/web/uvcpp_http_client.cpp:1339-1346 e455726ea341da2e da63a3f6e9d32a1f 7bac92ef277a28b0
-src/web/uvcpp_http_client.cpp:1511-1511 238f0847208893c8 238f0847208893c8 -
-src/web/uvcpp_http_client.cpp:1614-1614 238f0847208893c8 238f0847208893c8 -
-src/web/uvcpp_http_client.cpp:1744-1744 7cb06f8ab7910945 7cb06f8ab7910945 -
-src/web/uvcpp_http_client.cpp:1772-1772 82b5429d194071da 82b5429d194071da -
-src/web/uvcpp_http_client.cpp:1774-1774 c4f28dac995e9fef c4f28dac995e9fef -
-src/web/uvcpp_http_client.cpp:1786-1786 73485f20bdce6ed2 73485f20bdce6ed2 -
-src/web/uvcpp_http_client.cpp:1815-1815 a00c14f028c56cab a00c14f028c56cab -
-src/web/uvcpp_http_client.cpp:1840-1840 f90bffbc2ad74adf f90bffbc2ad74adf -
-src/web/uvcpp_http_client.cpp:1906-1906 73e8320ef3a6631b 73e8320ef3a6631b -
-src/web/uvcpp_http_client.cpp:1926-1926 37f9edf546868157 37f9edf546868157 -
+src/web/uvcpp_http_client.cpp:1088-1088 643136f9dd3e1790 643136f9dd3e1790 -
+src/web/uvcpp_http_client.cpp:1123-1138 b118434eac1d8491 c2b7df6201fdd336 236b2c747eac9d35
+src/web/uvcpp_http_client.cpp:1340-1343 1b0964cd17867d3e be31bdd36bbbcfc4 8d91bb3c624bcd4b
+src/web/uvcpp_http_client.cpp:1348-1355 e455726ea341da2e da63a3f6e9d32a1f 7bac92ef277a28b0
+src/web/uvcpp_http_client.cpp:1520-1520 238f0847208893c8 238f0847208893c8 -
+src/web/uvcpp_http_client.cpp:1623-1623 238f0847208893c8 238f0847208893c8 -
+src/web/uvcpp_http_client.cpp:1753-1753 7cb06f8ab7910945 7cb06f8ab7910945 -
+src/web/uvcpp_http_client.cpp:1781-1781 82b5429d194071da 82b5429d194071da -
+src/web/uvcpp_http_client.cpp:1783-1783 c4f28dac995e9fef c4f28dac995e9fef -
+src/web/uvcpp_http_client.cpp:1795-1795 73485f20bdce6ed2 73485f20bdce6ed2 -
+src/web/uvcpp_http_client.cpp:1824-1824 a00c14f028c56cab a00c14f028c56cab -
+src/web/uvcpp_http_client.cpp:1849-1849 f90bffbc2ad74adf f90bffbc2ad74adf -
+src/web/uvcpp_http_client.cpp:1915-1915 73e8320ef3a6631b 73e8320ef3a6631b -
+src/web/uvcpp_http_client.cpp:1935-1935 37f9edf546868157 37f9edf546868157 -
src/web/uvcpp_http_client.cpp:288-288 b46945cf76f3cf22 b46945cf76f3cf22 -
src/web/uvcpp_http_client.cpp:292-292 ce8513068ff69a1f ce8513068ff69a1f -
src/web/uvcpp_http_client.cpp:308-308 fc1b340d1d4d31a9 fc1b340d1d4d31a9 -
src/web/uvcpp_http_client.cpp:327-327 00bb095121c0026e 00bb095121c0026e -
src/web/uvcpp_http_client.cpp:329-329 7ad32943def66ec1 7ad32943def66ec1 -
src/web/uvcpp_http_client.cpp:331-331 aa6bf7130e33d3e9 aa6bf7130e33d3e9 -
-src/web/uvcpp_http_client.cpp:429-429 0fcf4f1576f0245f 0fcf4f1576f0245f -
-src/web/uvcpp_http_client.cpp:471-471 0b69c13cca5cffbd 0b69c13cca5cffbd -
-src/web/uvcpp_http_client.cpp:480-480 f587f4518a3db25d f587f4518a3db25d -
-src/web/uvcpp_http_client.cpp:499-501 8fa03f9d6d529976 0edafa70a7481903 3a773c0c7a40b2f1
-src/web/uvcpp_http_client.cpp:563-574 146db8f0849a2fae da39a3ee5e6b4b0d 48d033810df113d5
-src/web/uvcpp_http_client.cpp:582-601 278b123975a90677 236b2c747eac9d35 48d033810df113d5
+src/web/uvcpp_http_client.cpp:438-438 0fcf4f1576f0245f 0fcf4f1576f0245f -
+src/web/uvcpp_http_client.cpp:480-480 0b69c13cca5cffbd 0b69c13cca5cffbd -
+src/web/uvcpp_http_client.cpp:489-489 f587f4518a3db25d f587f4518a3db25d -
+src/web/uvcpp_http_client.cpp:508-510 8fa03f9d6d529976 0edafa70a7481903 3a773c0c7a40b2f1
+src/web/uvcpp_http_client.cpp:572-583 146db8f0849a2fae da39a3ee5e6b4b0d 48d033810df113d5
src/web/uvcpp_http_client.cpp:59-78 f118bde558092109 c2b7df6201fdd336 ce5f32d88a8e28ee
+src/web/uvcpp_http_client.cpp:591-610 278b123975a90677 236b2c747eac9d35 48d033810df113d5
src/web/uvcpp_http_client.cpp:64-68 a6454d8e70a85a9f edf89977b9c5f36e 45db41f216c48f31
-src/web/uvcpp_http_client.cpp:753-753 0b69c13cca5cffbd 0b69c13cca5cffbd -
src/web/uvcpp_http_client.cpp:762-762 0b69c13cca5cffbd 0b69c13cca5cffbd -
-src/web/uvcpp_http_client.cpp:767-784 90a5e4698d2cd79c 22c780bd792c12c7 08d0b65c5d543501
+src/web/uvcpp_http_client.cpp:771-771 0b69c13cca5cffbd 0b69c13cca5cffbd -
+src/web/uvcpp_http_client.cpp:776-793 90a5e4698d2cd79c 22c780bd792c12c7 08d0b65c5d543501
src/web/uvcpp_http_client.cpp:84-84 5168ca2b15819499 5168ca2b15819499 -
-src/web/uvcpp_http_client.cpp:927-947 37da23829c80fb45 48d033810df113d5 dadefa29b73704de
+src/web/uvcpp_http_client.cpp:936-956 37da23829c80fb45 48d033810df113d5 dadefa29b73704de
src/web/uvcpp_http_client.h:181-185 01e8534a32047f0a ab14b4c97188a5f4 b0c8c528b211425f
src/web/uvcpp_http_client.h:200-206 6e0783e5e61ecd37 8f07fe0c931c8d1e f8feb85440a61e55
src/web/uvcpp_http_client.h:217-272 68d3495d6dc6d27f 859ac3537b2a51dc f75806f32b5bb95d