From eefb1e5652d86414462ff98d20698cccbbd3cada Mon Sep 17 00:00:00 2001 From: Antruly <61441059+Antruly@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:35:41 +0800 Subject: [PATCH 1/2] fix(net): expose the SOCKS5 proxy-connect failure stage socks5_last_error() reports "connection refused" both when the proxy TCP connect fails and when the proxy rejects CONNECT with REP=0x05. Callers that classify errors cannot tell "proxy unreachable" from "target rejected" without joining the two pieces of information. Track the stage in uvcpp_tcp_client: set a flag when the proxy TCP connect, its DNS resolution, or the connect timeout fails; clear it when a new SOCKS5 attempt starts; expose socks5_proxy_connect_failed(). Forward the accessor from uvcpp_http_client. Add a functional test covering a dead proxy, a proxy-rejected target, a wrong password and the successful path. --- src/net/uvcpp_tcp_client.cpp | 17 +++- src/net/uvcpp_tcp_client.h | 12 +++ src/web/uvcpp_http_client.cpp | 4 + src/web/uvcpp_http_client.h | 2 + tests/functional/socks5_client_func.cpp | 110 ++++++++++++++++++++++++ 5 files changed, 144 insertions(+), 1 deletion(-) diff --git a/src/net/uvcpp_tcp_client.cpp b/src/net/uvcpp_tcp_client.cpp index cb74439..212d2ec 100644 --- a/src/net/uvcpp_tcp_client.cpp +++ b/src/net/uvcpp_tcp_client.cpp @@ -944,6 +944,7 @@ int uvcpp_tcp_client::connect_wait(const char* ip, int port, int timeout_ms) { socks5_in_.clear(); socks5_plain_.clear(); socks5_last_error_.clear(); + socks5_proxy_connect_failed_ = false; socks5_phase_ = socks5_phase::NONE; socks5_pending_ = true; } @@ -955,6 +956,7 @@ int uvcpp_tcp_client::connect_wait(const char* ip, int port, int timeout_ms) { via_socks5 ? socks5_proxy_port_ : port, resolved, timeout_ms); if (rc != 0) { + if (via_socks5) socks5_proxy_connect_failed_ = true; socks5_pending_ = false; set_status(TCP_CLIENT_ERROR); last_error_code_ = rc; @@ -992,6 +994,7 @@ int uvcpp_tcp_client::connect_wait(const char* ip, int port, int timeout_ms) { }); if (rc != 0) { + if (via_socks5) socks5_proxy_connect_failed_ = true; socks5_pending_ = false; clear_status(TCP_CLIENT_CONNECTING); set_status(TCP_CLIENT_ERROR); @@ -1004,6 +1007,7 @@ int uvcpp_tcp_client::connect_wait(const char* ip, int port, int timeout_ms) { [this]() { return sync_connect_done_; }, timeout_ms); if (!completed) { + if (via_socks5) socks5_proxy_connect_failed_ = true; socks5_pending_ = false; set_status(TCP_CLIENT_ERROR); last_error_code_ = UV_ETIMEDOUT; @@ -1011,6 +1015,7 @@ int uvcpp_tcp_client::connect_wait(const char* ip, int port, int timeout_ms) { } if (sync_connect_result_ != 0) { + if (via_socks5) socks5_proxy_connect_failed_ = true; socks5_pending_ = false; return sync_connect_result_; } @@ -1119,6 +1124,7 @@ int uvcpp_tcp_client::connect_async(const struct sockaddr* addr, } else { set_status(TCP_CLIENT_ERROR); last_error_code_ = status; + if (socks5_pending_) socks5_proxy_connect_failed_ = true; // 配了代理却连不上代理:把这句也记进 `socks5_last_error()`。 // 否则调用方只能看到一个裸的 ECONNREFUSED,还得自己去猜"是目标 // 还是代理"。协商阶段的失败由 `socks5_finish()` 负责写。 @@ -1173,6 +1179,7 @@ int uvcpp_tcp_client::connect_sockaddr(const struct sockaddr* addr, // 客户端会被一次立即失败永久毒化(下一次 connect 恒 UV_EALREADY)。 has_async_connect_cb_ = false; clear_status(TCP_CLIENT_CONNECTING); + if (socks5_pending_) socks5_proxy_connect_failed_ = true; } return rc; } @@ -1228,6 +1235,10 @@ const std::string& uvcpp_tcp_client::socks5_last_error() const { return socks5_last_error_; } +bool uvcpp_tcp_client::socks5_proxy_connect_failed() const { + return socks5_proxy_connect_failed_; +} + int uvcpp_tcp_client::socks5_connect_async(const std::string& target_host, int target_port, std::function cb) { @@ -1236,6 +1247,7 @@ int uvcpp_tcp_client::socks5_connect_async(const std::string& target_host, socks5_in_.clear(); socks5_plain_.clear(); socks5_last_error_.clear(); + socks5_proxy_connect_failed_ = false; socks5_phase_ = socks5_phase::NONE; socks5_sync_ = false; socks5_pending_ = true; @@ -1244,7 +1256,10 @@ int uvcpp_tcp_client::socks5_connect_async(const std::string& target_host, // `connect_transport` 在完成分支里看到 `socks5_pending_` 就转进协商。 const int rc = connect_transport(socks5_proxy_host_.c_str(), socks5_proxy_port_, std::move(cb)); - if (rc != 0) socks5_pending_ = false; + if (rc != 0) { + socks5_proxy_connect_failed_ = true; + socks5_pending_ = false; + } return rc; } diff --git a/src/net/uvcpp_tcp_client.h b/src/net/uvcpp_tcp_client.h index 06e61f1..366781e 100644 --- a/src/net/uvcpp_tcp_client.h +++ b/src/net/uvcpp_tcp_client.h @@ -415,6 +415,15 @@ class UVCPP_API uvcpp_tcp_client { /** @brief 最近一次 SOCKS5 失败的人类可读原因(无失败时为空串)。 */ const std::string& socks5_last_error() const; + /** + * @brief 最近一次 SOCKS5 失败是否发生在"连代理"这一步。 + * + * `socks5_last_error()` 里,代理不可达与目标拒绝都可能写成 + * `connection refused`;本方法把两者分开:true 表示 TCP 连接/DNS 失败在 + * 代理一侧,false 表示代理已经应答(认证、请求或协议失败)。 + */ + bool socks5_proxy_connect_failed() const; + // ----------------------------------------------------------------- // Write // ----------------------------------------------------------------- @@ -963,6 +972,9 @@ class UVCPP_API uvcpp_tcp_client { /// 由 `connect_wait` 接着走 TLS / 返回,不在这里触发连接回调。 bool socks5_sync_ = false; + /// 最近一次失败在"连代理"阶段(见 `socks5_proxy_connect_failed()`)。 + bool socks5_proxy_connect_failed_ = false; + socks5_phase socks5_phase_ = socks5_phase::NONE; std::string socks5_target_host_; int socks5_target_port_ = 0; diff --git a/src/web/uvcpp_http_client.cpp b/src/web/uvcpp_http_client.cpp index 7a980a9..80d4591 100644 --- a/src/web/uvcpp_http_client.cpp +++ b/src/web/uvcpp_http_client.cpp @@ -2084,6 +2084,10 @@ const std::string& uvcpp_http_client::socks5_last_error() const { return tcp_->socks5_last_error(); } +bool uvcpp_http_client::socks5_proxy_connect_failed() const { + return tcp_ != nullptr && tcp_->socks5_proxy_connect_failed(); +} + } // namespace uvcpp #endif // UVCPP_WEB_ENABLE diff --git a/src/web/uvcpp_http_client.h b/src/web/uvcpp_http_client.h index b5d7698..a2d11b1 100644 --- a/src/web/uvcpp_http_client.h +++ b/src/web/uvcpp_http_client.h @@ -159,6 +159,8 @@ class UVCPP_API uvcpp_http_client { bool socks5_enabled() const; /** @brief 上一次 SOCKS5 失败的人类可读原因(代理拒绝、认证失败、REP 码)。 */ const std::string& socks5_last_error() const; + /** @brief 上一次失败是否发生在"连代理"阶段;见 `uvcpp_tcp_client`. */ + bool socks5_proxy_connect_failed() const; // ------------------------------------------------------------------- // Send request diff --git a/tests/functional/socks5_client_func.cpp b/tests/functional/socks5_client_func.cpp index 20ed5a6..0164fc8 100644 --- a/tests/functional/socks5_client_func.cpp +++ b/tests/functional/socks5_client_func.cpp @@ -507,6 +507,115 @@ bool test_udp_associate() { return true; } +// ------------------------------------------------------------------------- +// uvcpp_tcp_client 的 SOCKS5 失败阶段 +// ------------------------------------------------------------------------- +int closed_port() { + uvcpp::uvcpp_tcp_server server; + if (server.bind("127.0.0.1", 0) != 0) return 0; + struct sockaddr_in name; + int namelen = static_cast(sizeof(name)); + std::memset(&name, 0, sizeof(name)); + if (server.get_tcp() == nullptr || + server.get_tcp()->getsockname(reinterpret_cast(&name), + &namelen) != 0) { + return 0; + } + return ntohs(name.sin_port); +} + +bool test_proxy_failure_stage() { + std::cout << "[socks5] proxy_failure_stage start\n"; + + // --- 代理连不上:阶段必须是"连代理" --- + { + const int dead_port = closed_port(); + uvcpp::uvcpp_tcp_client client; + if (client.set_socks5_proxy("127.0.0.1", dead_port) != 0) { + std::cout << "[socks5] set_socks5_proxy failed\n"; + return false; + } + const int rc = client.connect_wait("127.0.0.1", 80, 2000); + if (rc == 0) { + std::cout << "[socks5] dead proxy unexpectedly connected\n"; + return false; + } + if (!client.socks5_proxy_connect_failed()) { + std::cout << "[socks5] dead proxy was not reported as a proxy-connect failure\n"; + return false; + } + client.close(); + pump_for(client.get_loop(), 20); + } + + // --- 代理回了 REP=connection refused:阶段不能算"连代理" --- + { + SocksServer proxy; + proxy.start(); + const int target_port = closed_port(); + uvcpp::uvcpp_tcp_client client; + if (client.set_socks5_proxy("127.0.0.1", proxy.port) != 0) return false; + const int rc = client.connect_wait("127.0.0.1", target_port, 3000); + if (rc == 0) { + std::cout << "[socks5] refused target unexpectedly connected\n"; + return false; + } + if (client.socks5_proxy_connect_failed()) { + std::cout << "[socks5] target refusal was misreported as a proxy-connect failure\n"; + return false; + } + client.close(); + pump_for(client.get_loop(), 20); + } + + // --- 认证失败发生在代理应答之后 --- + { + SocksServer proxy; + proxy.opts.allow_no_auth = false; + proxy.opts.allow_username_password = true; + proxy.opts.authenticator = [](const std::string& u, const std::string& p) { + return u == "alice" && p == "secret"; + }; + proxy.start(); + uvcpp::uvcpp_tcp_client client; + if (client.set_socks5_proxy("127.0.0.1", proxy.port, "alice", "wrong") != 0) { + return false; + } + const int rc = client.connect_wait("127.0.0.1", 80, 3000); + if (rc == 0) { + std::cout << "[socks5] wrong password unexpectedly connected\n"; + return false; + } + if (client.socks5_proxy_connect_failed()) { + std::cout << "[socks5] auth failure was misreported as a proxy-connect failure\n"; + return false; + } + client.close(); + pump_for(client.get_loop(), 20); + } + + // --- 成功路径不复位错:标志必须保持 false --- + { + TcpEchoServer echo; + echo.start(); + SocksServer proxy; + proxy.start(); + uvcpp::uvcpp_tcp_client client; + if (client.set_socks5_proxy("127.0.0.1", proxy.port) != 0) return false; + const int rc = client.connect_wait("127.0.0.1", echo.port, 3000); + if (rc != 0 || client.socks5_proxy_connect_failed()) { + std::cout << "[socks5] successful proxy connect reported a failure stage: " + << uv_strerror(rc) << "\n"; + return false; + } + client.close(); + pump_for(client.get_loop(), 20); + } + + std::cout << "[socks5] proxy_failure_stage ok\n"; + return true; +} + } // namespace int main() { @@ -516,6 +625,7 @@ int main() { ok = test_connect_auth() && ok; ok = test_bind() && ok; ok = test_udp_associate() && ok; + ok = test_proxy_failure_stage() && ok; if (!ok) { std::cout << "[socks5] FAILED\n"; From f1b43ca90fcc17e64cd9c965c4b6995541528b6f Mon Sep 17 00:00:00 2001 From: Antruly <61441059+Antruly@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:47:53 +0800 Subject: [PATCH 2/2] docs: refresh line references after the SOCKS5 accessor The new public/private members shift uvcpp_tcp_client and uvcpp_http_client line numbers; move the documented references by the same offsets and refresh doc_line_refs.lock. --- doc/http2-guide.md | 2 +- doc/http2-status.md | 2 +- doc/http3-guide.md | 6 ++--- doc/net-guide.md | 14 +++++------ doc/ssl-guide.md | 6 ++--- doc/web-http-guide.md | 18 ++++++------- doc/worker-process-design.md | 2 +- tests/tools/doc_line_refs.lock | 46 +++++++++++++++++----------------- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/doc/http2-guide.md b/doc/http2-guide.md index 1dec845..ef36708 100644 --- a/doc/http2-guide.md +++ b/doc/http2-guide.md @@ -319,7 +319,7 @@ void doc_h2_client_submit(uvcpp::uvcpp_h2_connection* conn) { `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:1483-1483`)→ 装回调(`src/web/uvcpp_http_client.cpp:1485-1485`) → `h2_->start(sc, cc)`(`src/web/uvcpp_http_client.cpp:1519-1519`)→ `send()` 分流到 `send_h2`(`src/web/uvcpp_http_client.cpp:469-469`)。 -**`set_http2_enabled` 默认是关的**(`src/web/uvcpp_http_client.h:508-508`、 +**`set_http2_enabled` 默认是关的**(`src/web/uvcpp_http_client.h:510-510`、 `src/web/uvcpp_http_server.h:357-357`)—— 低层的两条线都要显式打开, 只有 `webapp/` 框架层是零配置自动协商。 diff --git a/doc/http2-status.md b/doc/http2-status.md index 10d9d34..a2f18cd 100644 --- a/doc/http2-status.md +++ b/doc/http2-status.md @@ -73,7 +73,7 @@ ### 1.3 三个接入面 -- **低层库**:`uvcpp_http_client::set_http2_enabled`(`src/web/uvcpp_http_client.h:307-307`) +- **低层库**:`uvcpp_http_client::set_http2_enabled`(`src/web/uvcpp_http_client.h:309-309`) 与 `uvcpp_http_server::set_http2_enabled`(`src/web/uvcpp_http_server.h:355-355`), **都默认关**。 - **框架(webapp)**:零配置自动协商。ALPN 名单里 `h2` 在前 diff --git a/doc/http3-guide.md b/doc/http3-guide.md index 48a193d..6d55608 100644 --- a/doc/http3-guide.md +++ b/doc/http3-guide.md @@ -287,7 +287,7 @@ void connect_over_http3(uvcpp_http_client& cli, int udp_port) { ``` - `uvcpp_http_server::listen_quic()` 在 `src/web/uvcpp_http_server.h:270`, - 客户端那一侧是 `set_http3_enabled()`(`src/web/uvcpp_http_client.h:363-363`)。 + 客户端那一侧是 `set_http3_enabled()`(`src/web/uvcpp_http_client.h:365-365`)。 **整个 h3 公开面都套在 `#if UVCPP_HTTP3_ENABLE` 里** —— 没编进来时它不存在, 这是**编译期**的"没有",不是运行时返回错误。 - **`"h3"` 绝不进 TCP 的 ALPN 列表。** h3 只跑在 QUIC(UDP)上;在 TCP 的 @@ -410,7 +410,7 @@ void connect_over_http3(uvcpp_http_client& cli, int udp_port) { 模块",所以 §3.3 那条明明只用 web 层 API 的片段也列了 `` —— 不列的话它会**被编**,而它调的 `set_http3_enabled()` / `listen_quic()` 整段在 `#if UVCPP_HTTP3_ENABLE` 里 - (`src/web/uvcpp_http_client.h:363-363`、`uvcpp_http_server.h:220`),于是报的是"未声明的 + (`src/web/uvcpp_http_client.h:365-365`、`uvcpp_http_server.h:220`),于是报的是"未声明的 成员":那是**假红**(文档没腐烂,是那个包没编 h3)。 登记之后,`config-contract` job 手里那个包会把这三条判成 `[跳·默认关]` 而不是 `[跳]`,**退出码不会抬到 3**。两个宏(`UVCPP_QUIC_ENABLE`、`UVCPP_HTTP3_ENABLE`) @@ -463,7 +463,7 @@ h1 写泵、TCP 的 ALPN 列表与分支 —— **全部不改**。唯一的例 (`std::map quic_ctxs_`, `src/web/uvcpp_http_server.h:1666-1666`),只在 QUIC 回调里碰;`conn_ctx` **不加任何 字段** —— 那个结构体在 h1 的**每请求**热路上,往里加一个字都是每个 h1 请求都要付 -的。客户端加了四个成员(两个 `bool`、两个指针,`src/web/uvcpp_http_client.h:536-542`) +的。客户端加了四个成员(两个 `bool`、两个指针,`src/web/uvcpp_http_client.h:538-544`) —— 那是每**连接**一个的对象,不是每请求一个;`send()` 热路上多的是一个可预测为假 的分支。不新增每连接或每请求的 `std::function`,不新增锁、不新增分配、不新增虚调用。 diff --git a/doc/net-guide.md b/doc/net-guide.md index 7bd406a..7b9ec0d 100644 --- a/doc/net-guide.md +++ b/doc/net-guide.md @@ -240,7 +240,7 @@ Nagle 最多压住一个 RTT(回环上几十 µs):它是低并发请求/ > 规矩是**回调里不要用同步的 `write_wait`/`read_wait`**(`src/net/uvcpp_tcp_server.h:92-97`): > 它们会把 loop 线程按住,一个慢对端能拖住这条 loop 上的**所有**连接。坑在于这两个函数 -> 的完成回调是可选参数,**省略它就等于同步** —— 按 `src/net/uvcpp_tcp_client.h:429-429`, +> 的完成回调是可选参数,**省略它就等于同步** —— 按 `src/net/uvcpp_tcp_client.h:438-438`, > `cb == nullptr` **正是** `write_wait(data, len, 30000)`。同文件 `:99` 的官方示例回显 > 现在传的是显式回调(异步)。本页的例子同样传显式回调来避开它。 @@ -526,7 +526,7 @@ n−1 条工作线程起好了(见 §4),而接着的 `bind()` / `listen()` | `set_read_callback(cb)` | 服务端 `:265` | 同上 | 一份回调覆盖所有连接 | **`read_start` 与 `read_start_events` 互斥。** 用过其中一个再用另一个,拿到 -`UV_EALREADY`。挡在前面的理由写在实现里(`src/net/uvcpp_tcp_client.cpp:2870-2878`): +`UV_EALREADY`。挡在前面的理由写在实现里(`src/net/uvcpp_tcp_client.cpp:2885-2893`): 两条路共用同一个底层 stream,同时注册的话底层 `read_start` 会把先注册的那个 **静默覆盖**掉——用户以为两个回调都在收数据,实际只有一个。 @@ -594,7 +594,7 @@ void doc_dispatch(uvcpp::uvcpp_tcp_client& client, `#define ERROR 0`,`EOF` 是 `` 的宏。所以是 `READ_ERROR` / `PEER_CLOSED`。 收到 `PEER_CLOSED` / `READ_ERROR` 之后**这个回调不会再被调用**,框架随后的收尾 -(关闭回调)照常发生,所以**不要在回调里做释放**(`src/net/uvcpp_tcp_client.h:590-591`)。 +(关闭回调)照常发生,所以**不要在回调里做释放**(`src/net/uvcpp_tcp_client.h:599-600`)。 --- @@ -607,7 +607,7 @@ void doc_dispatch(uvcpp::uvcpp_tcp_client& client, 所以: - **不要在关闭回调里 `delete` 客户端**,除非你已经用 `take_client()` 把所有权取走。 - 删一个仍归框架管的客户端会让框架随后二次释放(`src/net/uvcpp_tcp_client.h:671-673`)。 + 删一个仍归框架管的客户端会让框架随后二次释放(`src/net/uvcpp_tcp_client.h:680-682`)。 - 想自己管,用 `take_client()` 取走、`return_client()` 交回。两个都**必须在 loop 线程调用**;交回之后**不要再持有那个指针**。 - 关掉一条连接之后也别再留指针:框架可能在完成回调里把它删掉。 @@ -616,7 +616,7 @@ void doc_dispatch(uvcpp::uvcpp_tcp_client& client, (`src/net/uvcpp_tcp_server.h:59-65`)——不读的连接,即使设了 `set_on_close()` 也 **永远不会被触发**,而且这条连接不会被释放,等于每条一个静默泄漏。只想知道死活、 不要数据的话,就用 `read_start_events()` 注册一个忽略数据的回调 -(`src/net/uvcpp_tcp_client.h:659-663`)。 +(`src/net/uvcpp_tcp_client.h:668-672`)。 服务端有个 `set_auto_read`(默认 **true**)。关掉它只在"你要完全接管读路径、并且 自己负责发现断开"时有意义;关掉又没设回调时,服务端会给每条连接往 stderr 打一行警告 @@ -666,7 +666,7 @@ int write_wait(uvcpp_buf* buf, int timeout_ms = 30000); | 已经有异步写在飞,再调异步 `write` | 返回 `UV_EALREADY` | | 没连上就写 | 返回 `UV_ENOTCONN` | -**异步/同步其实是由 `cb` 是不是空决定的**(`src/net/uvcpp_tcp_client.cpp:1870-1873`): +**异步/同步其实是由 `cb` 是不是空决定的**(`src/net/uvcpp_tcp_client.cpp:1885-1888`): `cb` 非空 ⇒ 立即返回 0,完成时回调;`cb` 为空 ⇒ 退化成 `write_wait(data, len, 30000)`。 **这就是为什么回调里必须传 cb。** 事件循环回调里禁止同步等待,而三参数的 @@ -766,7 +766,7 @@ socket 之间流动(`src/net/uvcpp_tcp_client.h:193-205`)。所以 `web/` 三个例外: - **`uvcpp_buf*` 的零拷贝在 TLS 上不成立**——加密要求明文过 `SSL_write`,它会拷一份, - 所以调用之后那个 `uvcpp_buf` **仍然是满的**,数据仍归它(`src/net/uvcpp_tcp_client.h:459-462`)。 + 所以调用之后那个 `uvcpp_buf` **仍然是满的**,数据仍归它(`src/net/uvcpp_tcp_client.h:468-471`)。 - **握手完成前 `write()` 必然失败**,返回 `UV_ENOTCONN`。 - **握手失败的连接根本不会被交出来**:`on_connection` 一次都不调,只记在 `last_error_code_` 里(`src/net/uvcpp_tcp_server.h:693-701`)。所以明文直连 TLS 端口时, diff --git a/doc/ssl-guide.md b/doc/ssl-guide.md index 4543fd9..ee4fd09 100644 --- a/doc/ssl-guide.md +++ b/doc/ssl-guide.md @@ -320,7 +320,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:3164-3164`)——所以**必须看各 `load_*` 的返回值**, +(`src/net/uvcpp_tcp_client.cpp:3179-3179`)——所以**必须看各 `load_*` 的返回值**, 不能只看 `is_ready()`。 **先装私钥后装证书会返回 `false`**,见 §4。 @@ -328,12 +328,12 @@ read/write(): > 0 = 处理的字节数,0 = 需要更多 I/O,< 0 = 真出错 **服务端上 `PEER_STRICT` 不校验主机名**(客户端上会),见 §5。 **TLS 上 `uvcpp_buf*` 的零拷贝不成立**:调用之后那个 buf **仍然是满的**, -(`src/net/uvcpp_tcp_client.h:459-462`)。 +(`src/net/uvcpp_tcp_client.h:468-471`)。 **握手完成前写必然失败**(`UV_ENOTCONN`),`SSL_write` 要求握手已完成。 **同步/异步混用返回 `UV_ENOTSUP`**,而不是把明文写进一条已加密的连接 -(`src/web/uvcpp_http_client.h:270-279`)。 +(`src/web/uvcpp_http_client.h:272-281`)。 **怎么确认过滤器真的接上了:** 在连接回调里断言 `is_tls_handshake_done()`,并对收到的 字节按预期**明文**内容比对。测试就是这么做的——`tests/functional/web_ssl_server_func.cpp:131-135` diff --git a/doc/web-http-guide.md b/doc/web-http-guide.md index a402003..c07a09d 100644 --- a/doc/web-http-guide.md +++ b/doc/web-http-guide.md @@ -67,7 +67,7 @@ **三、`web/` 有 h2,但入口有三条。** 服务端 `set_http2_enabled(true)` (`src/web/uvcpp_http_server.h:357-357`)加 TLS+ALPN,服务端 `set_h2c_enabled(true)` (明文先验知识,RFC 9113 §3.4),客户端 `set_http2_enabled(true)` -(`src/web/uvcpp_http_client.h:508-508`)。 +(`src/web/uvcpp_http_client.h:510-510`)。 前两条都**默认关**;只有 `webapp/` 框架层默认开、零配置自动协商。 **不做 `Upgrade: h2c` 协商**(RFC 9113 已删除该机制),**也不做嗅探** —— 明文这条腿 只认那 24 个字节。 @@ -470,8 +470,8 @@ int doc_client_async() { ``` `send` / `get` / `post` 的回调签名逐字是 -`std::function`(`src/web/uvcpp_http_client.h:204-219`)。 -交付的 `resp` 是成员 `pending_resp_`(`src/web/uvcpp_http_client.h:483-483`)的 const 引用 —— **存到下次 `send()` +`std::function`(`src/web/uvcpp_http_client.h:206-221`)。 +交付的 `resp` 是成员 `pending_resp_`(`src/web/uvcpp_http_client.h:485-485`)的 const 引用 —— **存到下次 `send()` 就被覆写**。 **异步路径没有超时,`connect()` 也不设**(`src/web/uvcpp_http_client.cpp:64-68`)。 @@ -481,7 +481,7 @@ int doc_client_async() { "对端在响应收完前断开"时 `send()` 的回调永远不来。 **在响应回调里 `delete` 客户端是预期的用法。** 所有闭包都捕一个 `alive_token_` -弱引用(`src/web/uvcpp_http_client.h:466-477`),析构第一件事就 reset +弱引用(`src/web/uvcpp_http_client.h:468-479`),析构第一件事就 reset (`src/web/uvcpp_http_client.cpp:84-84`)—— 你不需要为它做任何额外的事。 ### 同步 @@ -511,8 +511,8 @@ int doc_client_sync() { ### 三个容易写错的地方 **一、keep-alive 有两个方向:请求那头听调用方的,响应那头听对端的。** -`keep_alive_` 是**调用方的偏好**(`src/web/uvcpp_http_client.h:461-461` 初值 `true`、 -`src/web/uvcpp_http_client.h:242-242` 声明 `set_keep_alive`、`src/web/uvcpp_http_client.cpp:889-889` setter)。`set_keep_alive(false)` +`keep_alive_` 是**调用方的偏好**(`src/web/uvcpp_http_client.h:463-463` 初值 `true`、 +`src/web/uvcpp_http_client.h:244-244` 声明 `set_keep_alive`、`src/web/uvcpp_http_client.cpp:889-889` setter)。`set_keep_alive(false)` 会让请求报文带上 `connection: close`(`src/web/uvcpp_http_client.cpp:545-564`); 默认的 `true` 则不补头,报文里那条 `connection: keep-alive` 是 `uvcpp_http_request::to_string()` 补的(`src/web/uvcpp_http_request.cpp:156-159`)。 @@ -534,8 +534,8 @@ int doc_client_sync() { `struct timeval`;`connect_wait()` 的 TLS 握手也用同一个选项限时 (`src/web/uvcpp_http_client.cpp:419-419`)。注意它是**每次 I/O** 的超时,不是 整次请求的总 deadline —— 对端每隔略小于 `timeout_ms` 发一个字节仍能把请求拖长。 -头文件的两处参数说明同步改了(`src/web/uvcpp_http_client.h:171-174`、 -`src/web/uvcpp_http_client.h:188-192`)。 +头文件的两处参数说明同步改了(`src/web/uvcpp_http_client.h:173-176`、 +`src/web/uvcpp_http_client.h:190-194`)。 **四、阻塞路径失败时不再交默认的 200 OK。** `read_one_message()` 把读错误、超时和"半条报文"作为负的 libuv 错误码往上传 @@ -552,7 +552,7 @@ int doc_client_sync() { `redirect|30[1238]` 零命中 —— 3xx 只是一个状态码交给你。 **压缩默认关,且门槛改不了。** `set_compression_enabled(true)` -(`src/web/uvcpp_http_client.h:248-260`)之后请求会补 +(`src/web/uvcpp_http_client.h:250-262`)之后请求会补 `accept-encoding: gzip, deflate`、响应收全后自动解压 —— **异步与阻塞两条路径都算** (`send()` 与 `send_wait()` / `send_wait_plain()` / `send_wait_ssl()`)。顺带记一笔 **曾经不是这样**:`1.6.32` 之前这两件事只写在异步那条路上,阻塞那两条静默地什么都不做 diff --git a/doc/worker-process-design.md b/doc/worker-process-design.md index e4acd8f..3b1c61f 100644 --- a/doc/worker-process-design.md +++ b/doc/worker-process-design.md @@ -196,7 +196,7 @@ Windows 要真正能用,得走 **master 自己 `accept()` + 每连接 `WSADupl 要写进文档的是另一件事:**n 个 worker × 每进程 4 条池线程 = 4n 条**,调 `UV_THREADPOOL_SIZE` 时按每进程算。 - **统计要跨进程聚合。** `uvcpp_tcp_client` 的 `reclaim_stats()` / `try_write_stats()` - (`src/net/uvcpp_tcp_client.h:772-772` / `src/net/uvcpp_tcp_client.h:800-800`)本来就是 `static` 的进程级聚合, + (`src/net/uvcpp_tcp_client.h:781-781` / `src/net/uvcpp_tcp_client.h:809-809`)本来就是 `static` 的进程级聚合, 多进程下变成"每进程一份",要看总数得自己聚合。 - **那个突发探针不进仓库、不进 CI。** 它要断言的失败模式("队列满了会拒连接")在这台机器上 **基本不出现**(§8),而且"满了之后是被拒还是挂住"**本身随平台变**(POSIX 给 RST、 diff --git a/tests/tools/doc_line_refs.lock b/tests/tools/doc_line_refs.lock index dedcaac..2ff53f4 100644 --- a/tests/tools/doc_line_refs.lock +++ b/tests/tools/doc_line_refs.lock @@ -307,11 +307,11 @@ 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:1870-1873 1e036d71316a81c6 48d033810df113d5 795893c2adb7c920 +src/net/uvcpp_tcp_client.cpp:1885-1888 1e036d71316a81c6 48d033810df113d5 795893c2adb7c920 src/net/uvcpp_tcp_client.cpp:236-238 b9d221d83892b526 06638547ede78bae 9d56ef46118aa3bf src/net/uvcpp_tcp_client.cpp:252-255 368c836c79756789 a30cd62faf3fc446 da95b372e94e3078 -src/net/uvcpp_tcp_client.cpp:2870-2878 5df26d1bf4b7dbca 48d033810df113d5 7b437c7951bf385f -src/net/uvcpp_tcp_client.cpp:3164-3164 16b916cd1ad9897f 16b916cd1ad9897f - +src/net/uvcpp_tcp_client.cpp:2885-2893 5df26d1bf4b7dbca 48d033810df113d5 7b437c7951bf385f +src/net/uvcpp_tcp_client.cpp:3179-3179 16b916cd1ad9897f 16b916cd1ad9897f - src/net/uvcpp_tcp_client.cpp:325-325 f1be8ca66f4149b5 f1be8ca66f4149b5 - src/net/uvcpp_tcp_client.cpp:864-868 85543241fdfec789 79dc8849065080ef da39a3ee5e6b4b0d src/net/uvcpp_tcp_client.h:193-205 b82385b59a1dcbd5 431c7b51a40a3c3a c4d564fd38050022 @@ -320,14 +320,14 @@ src/net/uvcpp_tcp_client.h:221-221 760ad7b5012e9f1f 760ad7b5012e9f1f - src/net/uvcpp_tcp_client.h:224-224 0d90308366291574 0d90308366291574 - src/net/uvcpp_tcp_client.h:236-236 24b1bada628bd0f9 24b1bada628bd0f9 - src/net/uvcpp_tcp_client.h:258-258 a485157291a76f2f a485157291a76f2f - -src/net/uvcpp_tcp_client.h:429-429 1c112210bf1a52e0 1c112210bf1a52e0 - -src/net/uvcpp_tcp_client.h:459-462 3ecca8837032620a c416998babbc0116 ed889d6391c46dc5 -src/net/uvcpp_tcp_client.h:590-591 a71ccc516d23687f 027fc8d2e6e5bfd1 b009f8e9f7f8f73c -src/net/uvcpp_tcp_client.h:659-663 831c0dc2353aa96d b83a8347852edc55 8c9d1f321368a059 -src/net/uvcpp_tcp_client.h:671-673 fbec72d28d6476ca 0861c5ac31328da0 058df2ba7b7b9f23 +src/net/uvcpp_tcp_client.h:438-438 1c112210bf1a52e0 1c112210bf1a52e0 - +src/net/uvcpp_tcp_client.h:468-471 3ecca8837032620a c416998babbc0116 ed889d6391c46dc5 +src/net/uvcpp_tcp_client.h:599-600 a71ccc516d23687f 027fc8d2e6e5bfd1 b009f8e9f7f8f73c +src/net/uvcpp_tcp_client.h:668-672 831c0dc2353aa96d b83a8347852edc55 8c9d1f321368a059 +src/net/uvcpp_tcp_client.h:680-682 fbec72d28d6476ca 0861c5ac31328da0 058df2ba7b7b9f23 src/net/uvcpp_tcp_client.h:76-78 da34d40f8fc445a3 5a968462ecbd0233 a9c130ca78315009 -src/net/uvcpp_tcp_client.h:772-772 3e6f5b37f4101e31 3e6f5b37f4101e31 - -src/net/uvcpp_tcp_client.h:800-800 5932eb983f34e0d6 5932eb983f34e0d6 - +src/net/uvcpp_tcp_client.h:781-781 3e6f5b37f4101e31 3e6f5b37f4101e31 - +src/net/uvcpp_tcp_client.h:809-809 5932eb983f34e0d6 5932eb983f34e0d6 - src/net/uvcpp_tcp_client.h:99-99 d11ace6cfc85bba0 d11ace6cfc85bba0 - src/net/uvcpp_tcp_server.cpp:1061-1063 656e275ae77689ca c2b7df6201fdd336 ae94d03aa8390799 src/net/uvcpp_tcp_server.cpp:110-126 3c7db8374b12817d f978787221d2171d 55f3444afa03a8fa @@ -493,20 +493,20 @@ src/web/uvcpp_http_client.cpp:758-778 37da23829c80fb45 48d033810df113d5 dadefa29 src/web/uvcpp_http_client.cpp:84-84 5168ca2b15819499 5168ca2b15819499 - src/web/uvcpp_http_client.cpp:889-889 643136f9dd3e1790 643136f9dd3e1790 - src/web/uvcpp_http_client.cpp:923-938 b118434eac1d8491 c2b7df6201fdd336 236b2c747eac9d35 -src/web/uvcpp_http_client.h:171-174 1bbf90ae9c77b8ac 6667a70333d874e5 95dd672e465d5736 -src/web/uvcpp_http_client.h:188-192 92e0d71ac9fbb7f8 b42b7860dd1720d6 bd74d8cce7db1b66 -src/web/uvcpp_http_client.h:204-219 85c571f34b36812d 859ac3537b2a51dc f75806f32b5bb95d -src/web/uvcpp_http_client.h:242-242 ac2839442940b98f ac2839442940b98f - -src/web/uvcpp_http_client.h:248-260 098567cc32d36071 236b2c747eac9d35 d531adb4a9ee06a0 -src/web/uvcpp_http_client.h:270-279 0e4969770f315a3c 96423943c5eb9538 15de869105d2e276 -src/web/uvcpp_http_client.h:307-307 5ec5be8e44ec8d13 5ec5be8e44ec8d13 - -src/web/uvcpp_http_client.h:363-363 1a03c4409bf0b24d 1a03c4409bf0b24d - +src/web/uvcpp_http_client.h:173-176 1bbf90ae9c77b8ac 6667a70333d874e5 95dd672e465d5736 +src/web/uvcpp_http_client.h:190-194 92e0d71ac9fbb7f8 b42b7860dd1720d6 bd74d8cce7db1b66 +src/web/uvcpp_http_client.h:206-221 85c571f34b36812d 859ac3537b2a51dc f75806f32b5bb95d +src/web/uvcpp_http_client.h:244-244 ac2839442940b98f ac2839442940b98f - +src/web/uvcpp_http_client.h:250-262 098567cc32d36071 236b2c747eac9d35 d531adb4a9ee06a0 +src/web/uvcpp_http_client.h:272-281 0e4969770f315a3c 96423943c5eb9538 15de869105d2e276 +src/web/uvcpp_http_client.h:309-309 5ec5be8e44ec8d13 5ec5be8e44ec8d13 - +src/web/uvcpp_http_client.h:365-365 1a03c4409bf0b24d 1a03c4409bf0b24d - src/web/uvcpp_http_client.h:43-45 22f2d8d7b1cbef95 24a0ea5ff3bfa408 da39a3ee5e6b4b0d -src/web/uvcpp_http_client.h:461-461 77940e08340681ed 77940e08340681ed - -src/web/uvcpp_http_client.h:466-477 5e1bd587480aa0cd 41c5717b5d15e16b 0b2b8a8ee10f67b6 -src/web/uvcpp_http_client.h:483-483 ffdce0a14f48d09e ffdce0a14f48d09e - -src/web/uvcpp_http_client.h:508-508 87d70f054eaf6247 87d70f054eaf6247 - -src/web/uvcpp_http_client.h:536-542 340e9dc8f77e5300 af9e4cd7c13c830c ddd6cd5f5eec36db +src/web/uvcpp_http_client.h:463-463 77940e08340681ed 77940e08340681ed - +src/web/uvcpp_http_client.h:468-479 5e1bd587480aa0cd 41c5717b5d15e16b 0b2b8a8ee10f67b6 +src/web/uvcpp_http_client.h:485-485 ffdce0a14f48d09e ffdce0a14f48d09e - +src/web/uvcpp_http_client.h:510-510 87d70f054eaf6247 87d70f054eaf6247 - +src/web/uvcpp_http_client.h:538-544 340e9dc8f77e5300 af9e4cd7c13c830c ddd6cd5f5eec36db src/web/uvcpp_http_client.h:70-70 b28db9b27491a2ec b28db9b27491a2ec - src/web/uvcpp_http_common.h:246-247 5d5591c858a59089 abf28ca15bd6320a 2c6c767a330c50df src/web/uvcpp_http_common.h:679-679 8440f66064369963 8440f66064369963 -