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!
CodePudding user response:
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.