Search code examples
c++boostboost-beast

BOOST : terminate called after throwing an instance of 'boost::wrapexcept<std::out_of_range>


binance-gateway.hpp:

#include <boost/asio.hpp>
#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/json/src.hpp>
#include <boost/json.hpp>
#include <iostream>
#include <string>


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

using executor = net::any_io_executor; 
using namespace boost::json;


namespace binapi
{
    
    namespace rest
    {

        boost::url make_url(boost::url_view base_api, boost::url_view method) {
            assert(!method.is_path_absolute());
            assert(base_api.data()[base_api.size() - 1] == '/');

            boost::urls::error_code ec;
            boost::url url;
            resolve(base_api, method, url, ec);
            if (ec)
                throw boost::system::system_error(ec);

            std::cout << "URL : "<< url << std::endl;    
            return url;
        }
        // Report a failure
        void fail_http(beast::error_code ec, char const* what)
        {
            std::cerr << what << ": " << ec.message() << "\n";
        }

        httpClient::httpClient(executor ex, ssl::context& ctx)
            : resolver_(ex)
            , stream_(ex, ctx) {}


        http::response<http::string_body> httpClient::sync_run(boost::url url,http::verb action)
        {
            std::string const host(url.host());
            std::string const service = url.has_port() //
                ? url.port()
                : (url.scheme_id() == boost::urls::scheme::https) //
                    ? "https"
                    : "http";
            url.remove_origin(); // becomes req_.target()

            // Set SNI Hostname (many hosts need this to handshake successfully)
            if(! SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()))
            {
                beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
                throw beast::system_error{ec};
            }

            // Set up an HTTP GET/POST/DELETE/PUT request message
            // req_.version(version);
            req_.method(action);
            req_.target(url.c_str());
            req_.set(http::field::host, host);
            req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
            req_.set("X-MBX-APIKEY", "Acz85W54qihZsUFVL0NtOw5szKysHSpx80c217qBrEYZJhUb15QRJBzMvUp1tFII");
            //req_.body() = serialize(json::object {{"symbol", "btcusdt"}});
            req_.prepare_payload(); // make HTTP 1.1 compliant

            auto const results = resolver_.resolve(host, service);
            beast::get_lowest_layer(stream_).connect(results);

            // Perform the SSL handshake
            stream_.handshake(ssl::stream_base::client);
            http::write(stream_, req_);

            // Receive the HTTP response
            http::read(stream_, buffer_, res_);

            return res_;

        }    

        // Start the asynchronous operation
        void httpClient::async_run(boost::url url, http::verb action) 
        {

            std::string const host(url.host());
            std::string const service = url.has_port() //
                ? url.port()
                : (url.scheme_id() == boost::urls::scheme::https) //
                    ? "https"
                    : "http";
            url.remove_origin(); // becomes req_.target()

            // Set SNI Hostname (many hosts need this to handshake successfully)
            if(! SSL_set_tlsext_host_name(stream_.native_handle(), host.c_str()))
            {
                beast::error_code ec{static_cast<int>(::ERR_get_error()), net::error::get_ssl_category()};
                std::cerr << ec.message() << "\n";
                return;
            }

            // Set up an HTTP GET/POST/DELETE/PUT request message
            // req_.version(version);
            req_.method(action);
            req_.target(url.c_str());
            req_.set(http::field::host, host);
            req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
            req_.set("X-MBX-APIKEY", "Acz85W54qihZsUFVL0NtOw5szKysHSpx80c217qBrEYZJhUb15QRJBzMvUp1tFII");
            //req_.body() = serialize(json::object {{"symbol", "btcusdt"}});
            req_.prepare_payload(); // make HTTP 1.1 compliant

            // Look up the domain name

            resolver_.async_resolve(host, service,beast::bind_front_handler(&httpClient::on_resolve,shared_from_this()));

        }

        void httpClient::on_resolve(beast::error_code ec, tcp::resolver::results_type results)
        {
            if(ec)
                return fail_http(ec, "resolve");

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Make the connection on the IP address we get from a lookup
            beast::get_lowest_layer(stream_).async_connect(results,beast::bind_front_handler(&httpClient::on_connect,shared_from_this()));
        }

        void httpClient::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
        {
            if(ec)
                return fail_http(ec, "connect");

            // Perform the SSL handshake
            stream_.async_handshake(ssl::stream_base::client,beast::bind_front_handler(&httpClient::on_handshake,shared_from_this()));
        }

        void httpClient::on_handshake(beast::error_code ec)
        {
            if(ec)
                return fail_http(ec, "handshake");

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Send the HTTP request to the remote host
            std::cout << "Sending " << req_ << std::endl;
            http::async_write(stream_, req_, beast::bind_front_handler(&httpClient::on_write, shared_from_this()));
        }

        void httpClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
        {
            boost::ignore_unused(bytes_transferred);

            if(ec)
                return fail_http(ec, "write");

            // Receive the HTTP response
            http::async_read(stream_, buffer_, res_, beast::bind_front_handler(&httpClient::on_read,shared_from_this()));

        }

        void httpClient::on_read(beast::error_code ec, std::size_t bytes_transferred)
        {
            boost::ignore_unused(bytes_transferred);

            if(ec)
                return fail_http(ec, "read");

            // Write the message to standard out
            // std::cout << res_.body() << std::endl;

            json = parse(res_.body()).at("serverTime").as_int64();

            std::cout << json << std::endl;

            // Set a timeout on the operation
            beast::get_lowest_layer(stream_).expires_after(std::chrono::seconds(30));

            // Gracefully close the stream
            stream_.async_shutdown(beast::bind_front_handler(&httpClient::on_shutdown,shared_from_this()));
        }

        void httpClient::on_shutdown(beast::error_code ec)
        {
            if(ec == net::error::eof)
            {
                ec = {};
            }
            if(ec)
                return fail_http(ec, "shutdown");

        }
        
        static void async_latest_price(std::string symbol, net::io_context &ioc, ssl::context &ctx)
        {
            static boost::url_view const base_api{"https://api.binance.com/api/v3/ticker/"};
            boost::url method{"price"};
            method.params().emplace_back("symbol",symbol);
            std::make_shared<httpClient>(net::make_strand(ioc),ctx)->async_run(make_url(base_api,method),http::verb::get);
        }
        
        static http::response<http::string_body> sync_latest_price(std::string symbol, net::io_context &ioc, ssl::context &ctx)
        {
            static boost::url_view const base_api{"https://api.binance.com/api/v3/ticker/"};
            boost::url method{"price"};
            method.params().emplace_back("symbol",symbol);
            http::response<http::string_body> res = std::make_shared<httpClient>(net::make_strand(ioc),ctx)->sync_run(make_url(base_api,method),http::verb::get);
            return res;
        }


    }
}

main.cpp :

#include <iostream>
#include <ctime>
#include "boost/url/src.hpp" // can only be included in one source file
#include "binance-ws.hpp"
#include "binance-gateway.hpp" 

int main()
{
    net::io_context ioc;
    binapi::rest::httpClient* client;

    // The SSL context is required, and holds certificates
    ssl::context ctx{ssl::context::tlsv12_client};

    // Verify the remote server's certificate
    ctx.set_verify_mode(ssl::verify_peer);
    ctx.set_default_verify_paths();

    // sync_latest_price() works fine
    // http::response<http::string_body> res = binapi::rest::sync_latest_price("BTCUSDT",ioc,ctx);
    // std::cout << res << std::endl;

    binapi::rest::async_latest_price("BTCUSDT",ioc,ctx); 
    ioc.run();
}

Build goes fine ,and when i run it :

URL : https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT
Sending GET /api/v3/ticker/price?symbol=BTCUSDT HTTP/1.1
Host: api.binance.com
User-Agent: Boost.Beast/330
X-MBX-APIKEY: Acz85W54qihZsUFVL0NtOw5szKysHSpx80c217qBrEYZJhUb15QRJBzMvUp1tFII


terminate called after throwing an instance of 'boost::wrapexcept<std::out_of_range>'
  what():  out of range
Aborted

sync_latest_price() works fine, but not async_latest_price() I wonder whats wrong going here ? advance thanks!


Solution

  • out_of_range is thrown when you request an element (by key) that doesn't exist.

    I - again - spent 10 minutes to piece together all the missing code (WHY). And then I saw:

        json = parse(res_.body()).at("serverTime").as_int64();
    
        std::cout << json << std::endl;
    

    It seems pretty obvious that since you're not using that code to (only) read server time, that's wrong. And it completely explains out_of_range error (after all serverTime is out of range).

    You probably wanted

        json = parse(res_.body());
    

    Other notes

    You still have the spurious

    binapi::rest::httpClient* client;
    

    in main.

    Your async_latest_price seems to have no way to complete the operation.