Search code examples
c++boostboost-beast

Boost.beast : how to return json response only


code : https://gist.github.com/Naseefabu/173c928603e564879683ccdf10d9d0f8

When i run this and print the response to the console:

Sending GET /api/v3/time HTTP/1.1
Host: api.binance.com
content-type: application/json
User-Agent: Boost.Beast/330


HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Content-Length: 28
Connection: keep-alive
Date: Fri, 13 May 2022 17:18:47 GMT
Server: nginx
x-mbx-uuid: 7e7465db-011a-4308-aa1e-6603d72c8c9a
x-mbx-used-weight: 1
x-mbx-used-weight-1m: 1
Strict-Transport-Security: max-age=31536000; includeSubdomains
X-Frame-Options: SAMEORIGIN
X-Xss-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Security-Policy: default-src 'self'
X-Content-Security-Policy: default-src 'self'
X-WebKit-CSP: default-src 'self'
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, OPTIONS
X-Cache: Miss from cloudfront
Via: 1.1 dfccb338f8c0489ab09835ea7dbad1a8.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: MAA51-P1
X-Amz-Cf-Id: waTqtDHSDpk74QB7zkF5Ya0CdRVWuJuC-M4TZqSuMd2bfXawkq6o6g==

{"serverTime":1652462327804}
shutdown: stream truncated

what if i just want to get the json response : {"serverTime":1652462327804} ? and store in the json variable so i could use it for my needs, other informations is not that important for me, advance thanks!


Solution

  •  binapi::AsyncRest::httpClient* client;
    

    That's extremely suspect, since the class is using enable_shared_from_this(). Pretty sure that should be

    auto client =
        std::make_shared<binapi::AsyncRest::httpClient>(ioc.get_executor(), ctx);
    

    Next up, I assume get_server_time is a static function. I don't see why it is a member of httpClient.

    So adding all the missing code back in (using lots of experience):

    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    #include <boost/beast.hpp>
    #include <boost/beast/ssl.hpp>
    #include <boost/url.hpp>
    #include <boost/url/src.hpp> // for header-only
    #include <fstream>
    #include <iomanip>
    #include <iostream>
    namespace net   = boost::asio;
    namespace beast = boost::beast;
    namespace http  = boost::beast::http;
    namespace ssl   = boost::asio::ssl;
    using net::ip::tcp;
    
    namespace binapi { namespace AsyncRest {
    
        // Report a failure
        void fail_http(beast::error_code ec, char const* what) {
            std::cerr << what << ": " << ec.message() << "\n";
        }
    
        struct httpClient : std::enable_shared_from_this<httpClient> {
            using executor = net::any_io_executor;
            using Stream   = beast::ssl_stream<beast::tcp_stream>;
    
            tcp::resolver                     resolver_;
            Stream                            stream_;
            beast::flat_buffer                buffer_;
            http::request<http::empty_body>   req_;
            http::response<http::string_body> res_;
    
            httpClient(executor ex, ssl::context& ctx);
    
            // Start the asynchronous operation
            void run(boost::url, http::verb);
            void on_resolve(beast::error_code, tcp::resolver::results_type);
            void on_connect(beast::error_code, tcp::resolver::results_type::endpoint_type);
            void on_handshake(beast::error_code);
            void on_write(beast::error_code, size_t bytes_transferred);
            void on_read(beast::error_code, size_t bytes_transferred);
            void on_shutdown(beast::error_code);
        };
    
        httpClient::httpClient(executor ex, ssl::context& ctx)
            : resolver_(ex)
            , stream_(ex, ctx) {}
    
        // Start the asynchronous operation
        void httpClient::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_.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, 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, 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_ << 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 get_server_time(net::io_context& ioc, ssl::context& ctx) {
            static boost::url_view const uri{"https://api.binance.com/api/v3/time"};
    
            std::make_shared<httpClient>(net::make_strand(ioc), ctx)
                ->run(uri, http::verb::get);
        }
    }} // namespace binapi::AsyncRest
    
    int main() {
        net::io_context                ioc;
    
        // 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();
    
        binapi::AsyncRest::get_server_time(ioc, ctx);
    
        ioc.run();
    }
    

    Now we know that res_ is beast::http::response<beast::http::string_body>. So, if you only want to print the body, print that:

        std::cout << res_.body() << std::endl;
    

    Prints

    Sending GET /api/v3/time HTTP/1.1
    Host: api.binance.com
    User-Agent: Boost.Beast/330
    
    
    {"serverTime":1652476115413}
    shutdown: stream truncated
    

    To only print the time:

        static constexpr boost::gregorian::date s_epoch{1970, 1, 1};
    
        auto epoch_seconds = json::parse(res_.body()).at("serverTime").as_int64();
        ptime serverTime(s_epoch, boost::posix_time::milliseconds(epoch_seconds));
    
        std::cout << serverTime << std::endl;
    

    Prints

    2022-May-13 21:38:55.982000
    

    Summary/Notes

    What it really looks like you're after is a function like

     ptime server_time();
    

    Or

     void async_server_time(auto completionToken);
    

    And you'd probably want to share the client class instance instead of reconnecting for each call.