title: Boost.beast http::read() returns `bad version`
body: <p>Based on the two official examples <a href="https://github.com/boostorg/beast/blob/develop/example/http/client/sync/http_client_sync.cpp" rel="nofollow noreferrer"><code>/example/http/client/sync</code></a> and <a href="https://github.com/boostorg/beast/blob/develop/example/http/client/sync-ssl/http_client_sync_ssl.cpp" rel="nofollow noreferrer"><code>/example/http/client/sync-ssl1</code></a> I am trying to build an HTTP client class that is able to handle both HTTP and HTTPS. As I am fairly new to <code>boost.beast</code> I <a href="https://github.com/boostorg/beast/issues/1849" rel="nofollow noreferrer">reached out</a> to figure out what the best/intended way of doing this is. It turned out that using a polymorphic approach with an abstract transport class should work well:</p>
  
  <p><a href="https://i.stack.imgur.com/S6mru.png" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/S6mru.png" alt="My polymorphic transport class design"></a></p>
  
  <p>I've implemented my <code>client</code> and the <code>client_transport</code> class based on the two examples.
  Making play text requests (HTTP) works well. However, when I set my <code>encryption</code> mode to <code>encryption::tls</code> the <code>http::read()</code> function returns a <code>bad version</code> error.
  I can successfully run the official <code>sync-ssl</code> example on my machine which indicates to me that dependencies such as OpenSSL shouldn't play a role here but that instead I simply overlooked something. Unfortunately, I didn't make much progress in figuring this out so I'd appreciate any kind of help.</p>
  
  <p><strong><code>main.cpp</code>:</strong></p>
  
  <pre><code>#include &lt;boost/asio/connect.hpp&gt;
  #include &lt;boost/asio/ip/tcp.hpp&gt;
  #include &lt;boost/asio/ssl/error.hpp&gt;
  #include &lt;cstdlib&gt;
  #include &lt;iostream&gt;
  #include &lt;string&gt;
  #include "http/client.hpp"
  
  namespace beast = boost::beast; // from &lt;boost/beast.hpp&gt;
  namespace http = beast::http;   // from &lt;boost/beast/http.hpp&gt;
  namespace net = boost::asio;    // from &lt;boost/asio.hpp&gt;
  namespace ssl = net::ssl;       // from &lt;boost/asio/ssl.hpp&gt;
  using tcp = net::ip::tcp;       // from &lt;boost/asio/ip/tcp.hpp&gt;
  
  // Performs an HTTP GET and prints the response
  int main(int argc, char** argv)
  {
      // Check command line arguments.
      if(argc != 4 &amp;&amp; argc != 5)
      {
          std::cerr &lt;&lt;
                    "Usage: http-client-sync-ssl &lt;host&gt; &lt;port&gt; &lt;target&gt; [&lt;HTTP version: 1.0 or 1.1(default)&gt;]\n" &lt;&lt;
                    "Example:\n" &lt;&lt;
                    "    http-client-sync-ssl www.example.com 443 /\n" &lt;&lt;
                    "    http-client-sync-ssl www.example.com 443 / 1.0\n";
          return EXIT_FAILURE;
      }
      auto const host = argv[1];
      auto const port = argv[2];
      auto const target = argv[3];
      int version = argc == 5 &amp;&amp; !std::strcmp("1.0", argv[4]) ? 10 : 11;
  
      try {
  
          // IO context
          boost::asio::io_context io_ctx;
  
          // Build request
          elx::http::client::request req = {
              .host = host,
              .port = static_cast&lt;uint16_t&gt;(std::stoi(port)),
              .target = target,
              .encryption = elx::http::client::request::encryption::tls
          };
  
          // Perform synchronous GET request
          elx::http::client client(io_ctx);
          auto response = client.synchronous_get(req);
  
          // Show the response body
          std::cout &lt;&lt; "response = " &lt;&lt; response.body_string() &lt;&lt; std::endl;
  
      }
      catch(std::exception const&amp; e) {
          std::cerr &lt;&lt; "Error: " &lt;&lt; e.what() &lt;&lt; std::endl;
          return EXIT_FAILURE;
      }
      return EXIT_SUCCESS;
  }
  </code></pre>
  
  <p><strong><code>client.hpp</code>:</strong></p>
  
  <pre><code>#pragma once
  
  #include &lt;boost/beast/http.hpp&gt;
  #include &lt;boost/beast/core/buffers_to_string.hpp&gt;
  #include &lt;boost/beast/core/tcp_stream.hpp&gt;
  #include "authorization.hpp"
  #include "client_transport.hpp"
  
  namespace http = boost::beast::http;
  
  namespace elx::http
  {
      class client
      {
      public:
          struct request
          {
              enum class encryption {
                  none,
                  tls
              };
  
              std::string host;
              uint16_t port;
              std::string target;
              uint8_t http_version = 11;
              authorization::auth auth;
              encryption encryption = encryption::none;
          };
  
          struct response
          {
              boost::beast::http::response&lt;boost::beast::http::dynamic_body&gt; raw;
  
              std::string body_string() const
              {
                  return boost::beast::buffers_to_string(raw.body().data());
              }
          };
  
          // Construction
          explicit client(boost::asio::io_context&amp; ioc);
          client(const client&amp; other) = delete;
          client(client&amp;&amp; other) = delete;
          virtual ~client() = default;
  
          [[nodiscard]] static enum request::encryption determine_encryption(const std::string&amp; host);
          [[nodiscard]] response synchronous_get(const request&amp; req);
  
      private:
          boost::asio::io_context&amp; m_io_ctx;
          boost::asio::ip::tcp::resolver m_resolver;
          std::unique_ptr&lt;client_transport&gt; m_transport;
  
      };
  }
  </code></pre>
  
  <p><strong><code>client.cpp</code>:</strong></p>
  
  <pre><code>#include &lt;boost/beast/core.hpp&gt;
  #include &lt;boost/beast/http.hpp&gt;
  #include &lt;boost/beast/version.hpp&gt;
  #include &lt;boost/asio/connect.hpp&gt;
  #include &lt;boost/asio/ip/tcp.hpp&gt;
  #include &lt;boost/asio/strand.hpp&gt;
  #include "client.hpp"
  #include "client_transport.hpp"
  
  using namespace elx::http;
  
  client::client(boost::asio::io_context&amp; ioc) :
      m_io_ctx(ioc),
      m_resolver(boost::asio::make_strand(ioc))
  {
  }
  
  client::response client::synchronous_get(const request&amp; 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&lt;client_transport_plain&gt;(m_io_ctx);
              break;
  tls
          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);
              m_transport = std::make_unique&lt;client_transport_tls&gt;(m_io_ctx, ssl_ctx);
              m_transport-&gt;set_hostname(req.host);
              break;
      }
  
      // Sanity check
      if (not m_transport)
          return { };
  
      // 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-&gt;connect(results-&gt;endpoint());
  
      // Perform handshake
      m_transport-&gt;handshake();
  
      // Set up an HTTP GET request message
      http::request&lt;boost::beast::http::string_body&gt; beast_req;
      beast_req.set(http::field::host, req.host);
      beast_req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
      if (not 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 beast_request to the remote host
      http::write(m_transport-&gt;stream(), 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
      boost::beast::error_code ec;
      http::read(m_transport-&gt;stream(), buffer, res.raw, ec);
      if (ec) {
          throw boost::beast::system_error{ec};
      }
  
      // Gracefully close the socket
      m_transport-&gt;stream().socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
  
      // not_connected happens sometimes
      // so don't bother reporting it.
      //
      if(ec &amp;&amp; ec != boost::beast::errc::not_connected)
          throw boost::beast::system_error{ec};
  
      // If we get here then the connection is closed gracefully
  
      return res;
  }
  </code></pre>
  
  <p><strong><code>client_transport.hpp</code>:</strong></p>
  
  <pre><code>#pragma once
  
  #include &lt;boost/asio/ip/resolver_base.hpp&gt;
  #include &lt;boost/asio/ssl/stream.hpp&gt;
  #include &lt;boost/beast/core/tcp_stream.hpp&gt;
  #include &lt;boost/beast/ssl.hpp&gt;
  
  namespace elx::http
  {
      class client_transport
      {
      public:
          [[nodiscard]] virtual boost::beast::tcp_stream&amp; stream() = 0;
  
          virtual boost::beast::error_code set_hostname(const std::string&amp; hostname) { return { }; }
          virtual void handshake() { }
  
          void connect(const boost::beast::tcp_stream::endpoint_type&amp; endpoint)
          {
              stream().connect(endpoint);
          }
      };
  
      class client_transport_plain :
          public client_transport
      {
      public:
          client_transport_plain(boost::asio::io_context&amp; io_ctx) :
              m_stream(io_ctx)
          {
          }
  
          [[nodiscard]] boost::beast::tcp_stream&amp; stream() override
          {
              return m_stream;
          }
  
      private:
          boost::beast::tcp_stream m_stream;
      };
  
      class client_transport_tls :
          public client_transport
      {
      public:
          client_transport_tls(boost::asio::io_context&amp; io_ctx, boost::asio::ssl::context&amp; ssl_ctx) :
              m_io_ctx(io_ctx),
              m_ssl_ctx(ssl_ctx),
              m_stream(io_ctx, ssl_ctx)
          {
          }
  
          [[nodiscard]] boost::beast::tcp_stream&amp; stream() override
          {
              return m_stream.next_layer();
          }
  
          boost::beast::error_code set_hostname(const std::string&amp; hostname) override
          {
              // Set SNI Hostname (many hosts need this to handshake successfully)
              if (not SSL_set_tlsext_host_name(m_stream.native_handle(), hostname.c_str())) {
                  return boost::beast::error_code{static_cast&lt;int&gt;(::ERR_get_error()), boost::asio::error::get_ssl_category()};
              }
  
              return { };
          }
  
          void handshake() override
          {
              m_stream.handshake(boost::asio::ssl::stream_base::client);
          }
  
      private:
          boost::asio::io_context&amp; m_io_ctx;
          boost::asio::ssl::context&amp; m_ssl_ctx;
          boost::beast::ssl_stream&lt;boost::beast::tcp_stream&gt; m_stream;
      };
  }
  </code></pre>