Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/http2-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/` 框架层是零配置自动协商。

Expand Down
2 changes: 1 addition & 1 deletion doc/http2-status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 在前
Expand Down
6 changes: 3 additions & 3 deletions doc/http3-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 的
Expand Down Expand Up @@ -410,7 +410,7 @@ void connect_over_http3(uvcpp_http_client& cli, int udp_port) {
模块",所以 §3.3 那条明明只用 web 层 API 的片段也列了
`<http3/uvcpp_h3_connection.h>` —— 不列的话它会**被编**,而它调的
`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`)
Expand Down Expand Up @@ -463,7 +463,7 @@ h1 写泵、TCP 的 ALPN 列表与分支 —— **全部不改**。唯一的例
(`std::map<uvcpp_quic_connection*, quic_ctx> 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`,不新增锁、不新增分配、不新增虚调用。

Expand Down
14 changes: 7 additions & 7 deletions doc/net-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 的官方示例回显
> 现在传的是显式回调(异步)。本页的例子同样传显式回调来避开它。

Expand Down Expand Up @@ -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` 会把先注册的那个
**静默覆盖**掉——用户以为两个回调都在收数据,实际只有一个。

Expand Down Expand Up @@ -594,7 +594,7 @@ void doc_dispatch(uvcpp::uvcpp_tcp_client& client,
`#define ERROR 0`,`EOF` 是 `<cstdio>` 的宏。所以是 `READ_ERROR` / `PEER_CLOSED`。

收到 `PEER_CLOSED` / `READ_ERROR` 之后**这个回调不会再被调用**,框架随后的收尾
(关闭回调)照常发生,所以**不要在回调里做释放**(`src/net/uvcpp_tcp_client.h:590-591`)。
(关闭回调)照常发生,所以**不要在回调里做释放**(`src/net/uvcpp_tcp_client.h:599-600`)。

---

Expand All @@ -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
线程调用**;交回之后**不要再持有那个指针**。
- 关掉一条连接之后也别再留指针:框架可能在完成回调里把它删掉。
Expand All @@ -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 打一行警告
Expand Down Expand Up @@ -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。** 事件循环回调里禁止同步等待,而三参数的
Expand Down Expand Up @@ -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 端口时,
Expand Down
6 changes: 3 additions & 3 deletions doc/ssl-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,20 +320,20 @@ 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。

**服务端上 `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`
Expand Down
18 changes: 9 additions & 9 deletions doc/web-http-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 个字节。
Expand Down Expand Up @@ -470,8 +470,8 @@ int doc_client_async() {
```

`send` / `get` / `post` 的回调签名逐字是
`std::function<void(const uvcpp_http_response&, int)>`(`src/web/uvcpp_http_client.h:204-219`)。
交付的 `resp` 是成员 `pending_resp_`(`src/web/uvcpp_http_client.h:483-483`)的 const 引用 —— **存到下次 `send()`
`std::function<void(const uvcpp_http_response&, int)>`(`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`)。
Expand All @@ -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`)—— 你不需要为它做任何额外的事。

### 同步
Expand Down Expand Up @@ -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`)。
Expand All @@ -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 错误码往上传
Expand All @@ -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` 之前这两件事只写在异步那条路上,阻塞那两条静默地什么都不做
Expand Down
2 changes: 1 addition & 1 deletion doc/worker-process-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -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、
Expand Down
17 changes: 16 additions & 1 deletion src/net/uvcpp_tcp_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -1004,13 +1007,15 @@ 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;
return UV_ETIMEDOUT;
}

if (sync_connect_result_ != 0) {
if (via_socks5) socks5_proxy_connect_failed_ = true;
socks5_pending_ = false;
return sync_connect_result_;
}
Expand Down Expand Up @@ -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()` 负责写。
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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<void(int)> cb) {
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down
12 changes: 12 additions & 0 deletions src/net/uvcpp_tcp_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
// -----------------------------------------------------------------
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/web/uvcpp_http_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/web/uvcpp_http_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading