Beast 读完代理的 CONNECT 响应头之后,要同时检查响应状态和动态缓冲区。读取操作可能取到头结束符之后的字节,并把它们留在传入的缓冲区里。如果用一个只读 socket 的重载启动 TLS,这些被保留的字节就落在 TLS 输入路径之外。

对于下一层是 SSL 流的 WebSocket 流,应使用带缓冲的握手重载。在已有的 yield_context 会话中,让 buffer 在整个操作期间保持存活:

http::response_parser<http::empty_body> response;
http::async_read_header(
    beast::get_lowest_layer(ws), buffer, response, yield[ec]);
if (ec) return;

auto status = response.get().result_int();
if (status < 200 || status >= 300) return;

std::size_t used = ws.next_layer().async_handshake(
    ssl::stream_base::client, buffer.data(), yield[ec]);
if (ec) return;
buffer.consume(used);

提前返回代表该会话的失败出口;把常规的错误上报和清理逻辑接在那里。只有成功的 CONNECT 响应才会建立隧道。拒绝页面不能变成 TLS 输入。

带缓冲的重载以错误码和字节数完成。该字节数表示它从传入缓冲区中消费了多少字节。完成之后,用 consume(used) 精确移除这段前缀。不要在操作开始前清空缓冲区,不要在握手挂起时改动它的存储,也不要假设操作消费了传入的每一个字节。

如果客户端在 CONNECT 之后立刻发起常规 TLS,缓冲区可能是空的。该重载也处理这种情况。它的目的是保留流边界,而不是把任意多余的代理数据变成合法的 TLS。握手之前,把目标主机名校验和 SNI 配置保留在 SSL 层;只有在 TLS 成功之后,才启动 WebSocket 升级。

相关文章