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 @@ libuvcpp logo

-[![version](https://img.shields.io/badge/version-1.8.6-blue.svg)](./RELEASE.md) +[![version](https://img.shields.io/badge/version-1.8.8-blue.svg)](./RELEASE.md) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) [![CI · Linux · Ubuntu](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml/badge.svg)](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml) [![CI · Windows · MSVC](https://github.com/Antruly/libuvcpp/actions/workflows/ci-windows-msvc.yml/badge.svg)](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 @@ libuvcpp logo

-[![版本](https://img.shields.io/badge/version-1.8.6-blue.svg)](./RELEASE.md) +[![版本](https://img.shields.io/badge/version-1.8.8-blue.svg)](./RELEASE.md) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE) [![CI · Linux · Ubuntu](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml/badge.svg)](https://github.com/Antruly/libuvcpp/actions/workflows/ci-linux-ubuntu.yml) [![CI · Windows · MSVC](https://github.com/Antruly/libuvcpp/actions/workflows/ci-windows-msvc.yml/badge.svg)](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