2016-06-26 6 views
1

Ich habe Probleme mit streambuf Management in Asio. Ich benutze Boost 1.58 auf Ubuntu. Als erstes ist hier der Code:boost :: asio :: streambuf abrufen xml Daten obwohl https

#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.1\r\n"; 
     request_stream << "Host: " << "www.quandl.com" << "\r\n"; 
     request_stream << "Accept: */*\r\n"; 
     request_stream << "Connection: close\r\n\r\n"; 

     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; 

} 

Hier ist mein Problem: Das Beispiel funktioniert einwandfrei, wenn die resultierenden Daten nicht zu lang sind (ein paar tausend Zeichen). Sobald async_read jedoch eine ungerade Anzahl von Zeichen zurückgibt (standardmäßig ist bytes_transfer 512 Zeichen), wird der Streambuf beschädigt und der nächste async_read-Aufruf enthält einige zusätzliche Zeichen.

Ich habe erfolglos viele Variationen des obigen Codes ausprobiert: mit transfer_exactly(), streambuf.consume() aufrufen, um den Puffer zu löschen, einen weiteren Puffer passieren, sobald ich eine ungerade Anzahl von zurückgegebenen Zeichen, etc. Keine von diesen feststellen Lösungen funktionierten.

Was fehlt mir hier? Thx

+1

Ihr Code funktioniert für mich. Mein Verdacht ist, dass Sie mit [chunked transfer encoding] (https://en.wikipedia.org/wiki/Chunked_transfer_encoding) nicht vertraut sind und diese "paar zusätzlichen Zeichen" in Ihrem Stream sind eigentlich die Chunk-Header. – rhashimoto

+0

Hallo, definitiv, ich habe diesen Punkt vermisst. Dank Ihrer Bemerkung habe ich begonnen, meinen Code zu ändern, um die Chunck-Trennzeichen zu verfolgen, aber dann habe ich gemerkt, dass Asio-Nachrichten nicht mit den Server-Chuncks in Verbindung stehen (die Trennzeichen könnten sich in der Mitte des Puffers befinden). Also habe ich meine Strategie geändert, zuerst die gesamte Nachricht in den Puffer geladen und dann den Streamstring von dort aufgefüllt. –

Antwort

2

Wie im Kommentar Austausch bestimmt, wird der Server mit chunked transfer encoding:

Chunked Transfer-Codierung ist eine Datentransfermechanismus in der Version 1.1 des Hypertext Transfer Protocol (HTTP), in dem Daten in gesendet wird eine Reihe von "Brocken". Es nutzt die Transfer-Encoding HTTP-Header anstelle des Content-Length-Header, ...

Jeder Brocken beginnt mit einer Hexadezimal-Blocklänge und einem CRLF. Wenn Sie mit der Chunked-Übertragung nicht vertraut sind, werden in der Tat seltsame Zeichen erscheinen, die Ihren Datenstrom beschädigen.

Chunked-Transfer-Codierung wird im Allgemeinen verwendet, wenn es nicht zweckmäßig ist, die genaue Länge des Antwortkörpers vor dem Senden zu bestimmen. Daraus folgt, dass der Empfänger die Körperlänge nicht kennt, bevor er den endgültigen Abschnitt mit der Länge null verarbeitet (beachte, dass nachlaufende "Header", auch "Trailer" genannt, dem letzten Abschnitt folgen können).

Mit boost :: asio, können Sie async_read_until() verwenden, um die Chunk-Header durch die CRLF Trennzeichen zu lesen, analysieren die Länge, und dann async_read() verwenden, um mit einem transfer_exactly die chunk Daten zu erhalten. Beachten Sie, dass Sie nach der Verwendung von streambuf für Ihre Lesevorgänge dieselbe streambuf-Instanz weiter verwenden sollten, da sie zusätzliche Daten puffern kann (das Extrahieren einer bestimmten Datenmenge aus einer streambuf wird here besprochen). Beachten Sie auch, dass Chunk-Daten mit einer CRLF (nicht in der Länge enthalten) enden, die Sie verwerfen sollten.

Es kann aufschlussreich sein (und sogar Spaß, wenn Sie die Zeit und Neugier haben), Ihren eigenen HTTP-Client mit boost :: asio zu schreiben, aber es ist nicht einfach, alle Optionen abzudecken (zB Komprimierung, Trailer, Umleitung) im HTTP-Standard. Sie sollten überlegen, ob eine ausgereifte Client-Bibliothek wie libcurl Ihren Anforderungen entspricht.