dimanche 26 juin 2016

boost::asio::streambuf retrieve xml data though https


I'm struggling with streambuf management in Asio. I'm using boost 1.58 on ubuntu. First, here is the code:

#include <iostream>

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/completion_condition.hpp>

class example
{
private:
    // asio components
    boost::asio::io_service service;
    boost::asio::ssl::context context;
    boost::asio::ip::tcp::resolver::query query;
    boost::asio::ip::tcp::resolver resolver;
    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket;
    boost::asio::streambuf requestBuf, responseBuf;

    // callbacks
    void handle_resolve(const boost::system::error_code& err,
                            boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
    {
        if (!err)
        {
            boost::asio::async_connect(socket.lowest_layer(), endpoint_iterator,
                boost::bind(&example::handle_connect, this,
                    boost::asio::placeholders::error));
        }
    }
    void handle_connect(const boost::system::error_code& err)
    {
        if (!err)
        {
          socket.async_handshake(boost::asio::ssl::stream_base::client,
              boost::bind(&example::handle_handshake, this,
                boost::asio::placeholders::error));
        }
    }
    void handle_handshake(const boost::system::error_code& err)
    {
        if (!err)
        {
            boost::asio::async_write(socket, requestBuf,
                boost::bind(&example::handle_write_request, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
        }
    }

    void handle_write_request(const boost::system::error_code& err, size_t bytes_transferred)
        {
            if (!err)
            {
                boost::asio::async_read(socket, responseBuf,
                    boost::asio::transfer_at_least(1),
                    boost::bind(&example::handle_read, this,
                        boost::asio::placeholders::error,
                        boost::asio::placeholders::bytes_transferred));
            }
        }

    void handle_read(const boost::system::error_code& err,
                             size_t bytes_transferred)
    {
        if (!err)
        {
            boost::asio::async_read(socket, responseBuf,
                boost::asio::transfer_at_least(1),
                boost::bind(&example::handle_read, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
        }
    }
public:
    example() : context(boost::asio::ssl::context::sslv23),
                resolver(service),
                socket(service, context),
                query("www.quandl.com", "443") {}

    void work()
    {
        // set security
        context.set_default_verify_paths();
        socket.set_verify_mode(boost::asio::ssl::verify_peer);

        // in case this no longer works, generate a new key from https://www.quandl.com/
        std::string api_key = "4jufXHL8S4XxyM6gzbA_";

        // build the query
        std::stringstream ss;

        ss << "api/v3/datasets/";
        ss << "RBA" << "/" << "FXRUKPS" << ".";
        ss << "xml" << "?sort_order=asc";
        ss << "?api_key=" << api_key;
        ss << "&start_date=" << "2000-01-01";
        ss << "&end_date=" << "2003-01-01";

        std::ostream request_stream(&requestBuf);
        request_stream << "GET /";
        request_stream << ss.str();
        request_stream << " HTTP/1.1rn";
        request_stream << "Host: " << "www.quandl.com" << "rn";
        request_stream << "Accept: */*rn";
        request_stream << "Connection: closernrn";

        resolver.async_resolve(query,
            boost::bind(&example::handle_resolve, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::iterator));

        service.run();

        std::cout << &responseBuf;
    }
};

int main(int argc, char * argv[])
{
    // this is a test
    int retVal; try
    {
        example f; f.work();
        retVal = 0;

    }
    catch (std::exception & ex)
    {
        std::cout << "an error occured:" << ex.what() << std::endl;
        retVal = 1;
    }

    return retVal;

}

Here is my problem: the example works perfectly if the resulting data are not too long (a few thousands characters). However, as soon async_read returns an uneven number of characters (default bytes_transferred is 512 chars), the streambuf get corrupted and the next async_read call will contain a few extra characters.

I unsuccessfully tried many variations of the code above: using transfer_exactly(), calling streambuf.consume() to clear the buffer, passing another buffer as soon as I detect an uneven number of chars returned, etc. None of these solutions worked.

What am I missing here ? Thx


Aucun commentaire:

Enregistrer un commentaire