You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Boost.Beast http::read()返回bad version错误,寻求技术帮助

问题分析与解决方案

你遇到的bad version错误,核心原因是在TLS模式下直接操作了底层的TCP流,而没有使用SSL流进行加密读写。

当你设置encryption::tls时,你的client_transport_tls::stream()返回的是m_stream.next_layer()(也就是原始的tcp_stream),然后你用这个流来调用http::write和http::read。这就相当于把明文的HTTP请求直接发送到了HTTPS端口(443),而服务器期望的是TLS握手请求,所以服务器会返回TLS握手的响应报文。HTTP解析器把这个TLS报文当成HTTP协议来解析,自然识别不了正确的HTTP版本,就抛出了bad version错误。

官方的sync-ssl示例之所以能正常工作,是因为它全程使用ssl_stream来进行读写,所有数据都经过TLS加密/解密,而你的代码绕过了SSL层直接操作底层TCP流,这就是问题所在。


具体修改方案

我们需要重构传输类的设计,把HTTP读写操作封装到传输类内部,让不同的传输类型(明文/TLS)自己处理对应的流操作:

1. 修改client_transport.hpp,封装流操作

#pragma once
#include <boost/beast/http.hpp>
#include <boost/beast/core/tcp_stream.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/asio/ssl/context.hpp>

namespace http = boost::beast::http;
namespace elx::http {

class client_transport {
public:
    virtual ~client_transport() = default;

    // 连接到指定端点
    virtual void connect(const boost::beast::tcp_stream::endpoint_type& endpoint) = 0;
    // 执行握手(TLS需要,明文不需要)
    virtual void handshake() = 0;
    // 发送HTTP请求
    virtual void write(const http::request<http::string_body>& req) = 0;
    // 读取HTTP响应
    virtual void read(boost::beast::flat_buffer& buffer, http::response<http::dynamic_body>& res) = 0;
    // 关闭连接
    virtual void shutdown(boost::beast::error_code& ec) = 0;
};

class client_transport_plain : public client_transport {
public:
    explicit client_transport_plain(boost::asio::io_context& io_ctx)
        : m_stream(io_ctx) {}

    void connect(const boost::beast::tcp_stream::endpoint_type& endpoint) override {
        m_stream.connect(endpoint);
    }

    void handshake() override {
        // 明文传输无需握手
    }

    void write(const http::request<http::string_body>& req) override {
        http::write(m_stream, req);
    }

    void read(boost::beast::flat_buffer& buffer, http::response<http::dynamic_body>& res) override {
        http::read(m_stream, buffer, res);
    }

    void shutdown(boost::beast::error_code& ec) override {
        m_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
    }

private:
    boost::beast::tcp_stream m_stream;
};

class client_transport_tls : public client_transport {
public:
    // 改用移动语义传递ssl_ctx,避免引用生命周期问题
    client_transport_tls(boost::asio::io_context& io_ctx, boost::asio::ssl::context ssl_ctx)
        : m_stream(io_ctx, std::move(ssl_ctx)) {}

    // 设置SNI主机名
    void set_hostname(const std::string& hostname) {
        if (!SSL_set_tlsext_host_name(m_stream.native_handle(), hostname.c_str())) {
            throw boost::beast::system_error{
                boost::beast::error_code{static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category()}
            };
        }
    }

    void connect(const boost::beast::tcp_stream::endpoint_type& endpoint) override {
        // TLS连接需要先连接底层TCP流
        m_stream.next_layer().connect(endpoint);
    }

    void handshake() override {
        // 执行TLS客户端握手
        m_stream.handshake(boost::asio::ssl::stream_base::client);
    }

    void write(const http::request<http::string_body>& req) override {
        // 使用SSL流发送加密后的HTTP请求
        http::write(m_stream, req);
    }

    void read(boost::beast::flat_buffer& buffer, http::response<http::dynamic_body>& res) override {
        // 使用SSL流读取解密后的HTTP响应
        http::read(m_stream, buffer, res);
    }

    void shutdown(boost::beast::error_code& ec) override {
        // TLS连接需要先关闭SSL层
        m_stream.shutdown(ec);
    }

private:
    boost::beast::ssl_stream<boost::beast::tcp_stream> m_stream;
};

} // namespace elx::http

2. 修改client.cpp中的synchronous_get方法,使用封装的传输操作

client::response client::synchronous_get(const request& req) {
    namespace http = boost::beast::http;

    // Setup SSL context (in case we need that)
    boost::asio::ssl::context ssl_ctx(boost::asio::ssl::context::tlsv12_client);

    // Setup encryption
    switch (req.encryption) {
        case request::encryption::none:
            m_transport = std::make_unique<client_transport_plain>(m_io_ctx);
            break;
        case request::encryption::tls:
            ssl_ctx.set_default_verify_paths();
#warning "ToDo: We definitely want to verify the peer"
            ssl_ctx.set_verify_mode(boost::asio::ssl::verify_none);
            // 创建TLS传输对象并设置SNI
            auto tls_transport = std::make_unique<client_transport_tls>(m_io_ctx, std::move(ssl_ctx));
            tls_transport->set_hostname(req.host);
            m_transport = std::move(tls_transport);
            break;
    }

    // Sanity check
    if (!m_transport) {
        throw std::runtime_error("Failed to initialize transport layer");
    }

    // Look up the domain name
    auto const results = m_resolver.resolve(req.host, std::to_string(req.port));

    // Make the connection on the IP address we get from a lookup
    m_transport->connect(results->endpoint());

    // Perform handshake
    m_transport->handshake();

    // Set up an HTTP GET request message
    http::request<boost::beast::http::string_body> beast_req;
    beast_req.set(http::field::host, req.host);
    beast_req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
    if (!req.auth.is_empty())
        beast_req.set(http::field::authorization, req.auth.to_string());
    beast_req.method(http::verb::get);
    beast_req.version(req.http_version);
    beast_req.target(req.target);

    // Send the HTTP request via transport layer
    m_transport->write(beast_req);

    // This buffer is used for reading and must be persisted
    boost::beast::flat_buffer buffer;

    // Declare a container to hold the response
    response res;

    // Receive the HTTP response via transport layer
    try {
        m_transport->read(buffer, res.raw);
    } catch (const boost::beast::system_error& e) {
        throw;
    }

    // Gracefully close the connection
    boost::beast::error_code ec;
    m_transport->shutdown(ec);

    // Ignore not_connected error (common when server closes connection first)
    if (ec && ec != boost::beast::errc::not_connected) {
        throw boost::beast::system_error{ec};
    }

    return res;
}

关键修改点说明

  1. 封装流操作:不再暴露底层流对象给上层,而是让传输类自己处理HTTP读写,确保TLS模式下使用ssl_stream进行加密传输。
  2. 修复SSL上下文生命周期:将ssl_ctx通过移动语义传递给client_transport_tls,避免引用局部变量带来的潜在生命周期问题。
  3. 正确的TLS关闭流程:TLS连接需要先调用ssl_stream::shutdown,而不是直接关闭底层TCP socket。

另外,建议你后续把resolver.resolve的结果遍历连接(参考官方示例的net::connect),而不是只取第一个端点,这样可以提高连接成功率,但这不是当前错误的原因。

内容的提问来源于stack exchange,提问作者Joel Bodenmann

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.06 16:57:32