1

I am relatively new to C and C++ programming and I am trying to connect to a server using Websockets using the boost and beast libraries in C++. I followed the tutorial here but I get the following error

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  The WebSocket handshake was declined by the remote peer [boost.beast.websocket:20]

This is my code so far. I need help figuring out the problem and also if you guys can include resources where I can become better at C++ and networking in general I would really appreciate it.

#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <rapidjson/allocators.h>
#include <rapidjson/document.h>
#include <rapidjson/prettywriter.h>
#include <ta_libc.h>
#include <cstdlib>
#include <iostream>
#include <string>

using namespace std;
using namespace rapidjson;

namespace beast = boost::beast;
namespace http = beast::http;
namespace websocket = beast::websocket;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;

int main() {

    string host = "ws-feed.exchange.coinbase.com";
    auto const port = "443";
    const char doc[] =
        "{ \'type\': \'subscribe\', \'product_ids\': [\'ETH-USD\'], \'channels\': [\'matches\'] }";

    Document document;
    document.Parse(doc);

    net::io_context ioc;
    tcp::resolver resolver{ioc};
    websocket::stream<tcp::socket> ws{ioc};

    auto const results = resolver.resolve(host, port);

    auto ep = net::connect(ws.next_layer(), results);

    host += ":" + to_string(ep.port());

    ws.set_option(websocket::stream_base::decorator(
        [](websocket::request_type& req)
            {
                req.set(http::field::user_agent,
                string(BOOST_BEAST_VERSION_STRING) +
                " websocket-client-coro");
            }));

    ws.handshake(host, "/");

    ws.write(net::buffer(string(doc)));

    beast::flat_buffer buffer;

    ws.read(buffer);

    ws.close(websocket::close_code::normal);

    cout << beast::make_printable(buffer.data()) << endl;

} // main
7
  • Do the Boost Beast library have built-in support for HTTPS and encrypted connections? Commented May 25, 2022 at 5:01
  • @Someprogrammerdude it does, but this code isn't using it Commented May 25, 2022 at 6:28
  • 2
    Please don't delete and repost your questions, you can edit your question Israel l instead Commented May 25, 2022 at 6:30
  • @AlanBirtles Port 443 is the HTTPS port. Which might be the problem, the OP is trying to connect to the HTTPS port but without the SSL/TLS parts that's needed for it. Commented May 25, 2022 at 6:30
  • Yes, and that handshake was declined by the remote peer says it. If you would like an example of using SSL, I've done it here. Commented May 26, 2022 at 0:13

1 Answer 1

2

Like others said, you don't use SSL where the server requires it.

Here's a demo. The subtler point is that the server required SNI.

#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/json.hpp>
#include <boost/json/src.hpp> // for header-only lib
#include <iostream>

namespace beast     = boost::beast;
namespace http      = beast::http;
namespace websocket = beast::websocket;
namespace net       = boost::asio;
namespace ssl       = net::ssl;
namespace json      = boost::json;

using tcp = boost::asio::ip::tcp;

int main() {
    std::string host = "ws-feed.exchange.coinbase.com";
    auto const  port = "443";

    json::value doc = {
        {"type", "subscribe"},
        {"product_ids", {"ETH-USD"}},
        {"channels", {"matches"}},
    };

    net::io_context ioc;
    tcp::resolver   resolver{ioc};
    ssl::context    ctx(ssl::context::sslv23_client);
    ctx.set_default_verify_paths();
    websocket::stream<ssl::stream<tcp::socket>> ws{ioc, ctx};

    auto const results = resolver.resolve(host, port);

    // connect raw socket
    auto& raw = beast::get_lowest_layer(ws);
    auto ep = net::connect(raw, results);

    // Set SNI Hostname (many hosts need this to handshake successfully)
    if (!SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) {
        throw boost::system::system_error(
            ::ERR_get_error(), boost::asio::error::get_ssl_category());
    }
    ws.next_layer().handshake(ssl::stream_base::client);

    // ssl handshake
    host += ":" + std::to_string(ep.port());

    ws.set_option(
        websocket::stream_base::decorator([](websocket::request_type& req) {
            req.set(http::field::user_agent,
                    std::string(BOOST_BEAST_VERSION_STRING) +
                        " websocket-client-coro");
        }));

    // websocket handshake
    ws.handshake(host, "/");

    std::cout << doc << std::endl;
    ws.write(net::buffer(serialize(doc)));

    beast::flat_buffer buffer;

    ws.read(buffer);

    std::cout << beast::make_printable(buffer.data()) << std::endl;

    ws.close(websocket::close_code::normal);
}

Results in:

{"type":"subscribe","product_ids":["ETH-USD"],"channels":["matches"]}
{"type":"subscriptions","channels":[{"name":"matches","product_ids":["ETH-USD"]}]}
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  stream truncated [asio.ssl.stream:1]

The error indicates that the server doesn't gracefully shutdown the connection. That's probably by design.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks @sehe I used flags -lcrypto -lssl for compiling btw
How did you know the server required SNI? Can some servers requires SSL but not SNI?
Because it rejected the handshake. Not all servers require SNI, in fact for a large period of time SNI didn't exist. However in the world of shared hosting SNI made a lot of sense, so it has quietly taken over, and in general it's a good fist line of defense against rogue traffic.
@user997112 Indeed. The SSL was documented, the SNI requires an educated guess. The Beast examples show this consistently, I think, so it's possible to "accidentally" get it even without knowledge of SNI
@user997112 Good point, it's the call to SSL_set_tlsext_host_name I will add a comment accordingly. The beast examples already do this, see e.g. boost.org/doc/libs/1_67_0/libs/beast/example/http/client/…
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.