如何使用boost::asio通过HTTP代理连接HTTPS服务器?
错误根因
你遇到的write: uninitialized报错是因为你直接将还未完成SSL握手的ssl::stream<tcp::socket>对象当成普通TCP套接字使用:ssl::stream会在读写操作前自动校验SSL层状态,未握手时所有直接读写ssl::stream的操作都会被拦截报错。你无法跳过SSL层直接通过ssl::stream发送原始TCP数据,这是设计上的限制。
正确的实现步骤
整个流程需要拆分为完全独立的两个阶段,不能一开始就把套接字包装为SSL流:
- 阶段1(和代理交互):使用裸tcp::socket连接代理服务器,发送
CONNECT请求,读取并校验代理返回的响应,确认收到HTTP/1.x 200 Connection established响应后,代理隧道已建立完成,后续所有流量都会被代理透传给目标HTTPS服务器。 - 阶段2(和目标HTTPS服务器交互):将已经连通的裸
tcp::socket移动到ssl::stream中,再执行SSL握手,后续所有HTTPS读写操作都通过这个SSL流完成即可。
修改后的示例代码
using boost::asio::ip::tcp; namespace ssl = boost::asio::ssl; typedef ssl::stream<tcp::socket> ssl_socket; // 1. 初始化SSL上下文,这部分可以提前创建复用 ssl::context ctx(ssl::context::sslv23); ctx.set_default_verify_paths(); ctx.set_verify_mode(ssl::verify_peer); // 正式环境建议开启证书校验 boost::asio::io_context io_context; // 2. 阶段1:用裸TCP socket和代理交互 tcp::socket raw_sock(io_context); // 这里resolver查询要填代理的IP和端口,不是目标HTTPS服务器的 boost::asio::connect(raw_sock, resolver.resolve(proxy_host, proxy_port)); raw_sock.set_option(tcp::no_delay(true)); // 构造并发送CONNECT请求 boost::asio::streambuf request; std::ostream request_stream(&request); request_stream << "CONNECT " << target_host << ":443 HTTP/1.1\r\n"; request_stream << "Host: " << target_host << ":443\r\n"; AddBasicUserAuthHeader(request_stream, testUrl); // 代理需要认证的话保留这行 request_stream << "Proxy-Connection: keep-alive\r\n\r\n"; boost::asio::write(raw_sock, request); // 读取代理的响应,校验隧道是否建立成功 boost::asio::streambuf response; boost::asio::read_until(raw_sock, response, "\r\n\r\n"); std::istream response_stream(&response); std::string http_version; unsigned int status_code; response_stream >> http_version >> status_code; // 校验状态码必须是200,否则代理隧道建立失败 if (status_code != 200) { // 自行处理错误逻辑,比如代理拒绝连接、需要认证等 raw_sock.close(); return; } // 把响应里剩余的未读数据清空,避免残留数据干扰后续SSL握手 response.consume(response.size()); // 3. 阶段2:将裸socket转为SSL流,和目标服务器做SSL握手 ssl_socket ssl_sock(std::move(raw_sock), ctx); ssl_sock.set_verify_callback(ssl::host_name_verification(target_host.c_str())); boost::system::error_code ec; ssl_sock.handshake(ssl_socket::client, ec); if (ec) { // 自行处理SSL握手错误逻辑 return; } // 后续就可以正常通过ssl_sock读写HTTPS请求了
常见坑点说明
- 如果你用的Boost版本低于1.66,不支持
ssl::stream的移动构造,可以先创建ssl_socket实例,再调用ssl_sock.lowest_layer() = std::move(raw_sock)传入裸套接字。 - 一定要完整读完代理返回的CONNECT响应(包括最后的空行),残留的HTTP响应数据会被SSL层当成握手包,直接导致握手失败。
- SSL握手的域名校验对象是目标HTTPS服务器的域名,不是代理的域名,不要填错。
内容的提问来源于stack exchange,提问作者RED SOFT ADAIR
相关产品推荐
相关产品推荐

