boost::asio::streambuf 通过 https 检索 xml 数据

Posted

技术标签:

【中文标题】boost::asio::streambuf 通过 https 检索 xml 数据【英文标题】:boost::asio::streambuf retrieve xml data though https 【发布时间】:2016-06-26 14:14:38 【问题描述】:

我在 Asio 中的 streambuf 管理方面遇到了困难。我在 ubuntu 上使用 boost 1.58。首先,这里是代码:

#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;


这是我的问题:如果结果数据不太长(几千个字符),该示例就可以完美运行。 但是,一旦 async_read 返回奇数个字符(默认 bytes_transferred 为 512 个字符),streambuf 就会损坏,并且下一次 async_read 调用将包含一些额外的字符。

我尝试了上述代码的许多变体均未成功:使用 transfer_exactly()、调用 streambuf.consume() 来清除缓冲区、在检测到返回的字符数不均匀时立即传递另一个缓冲区等。这些解决方案都不是工作。

我在这里缺少什么?谢谢

【问题讨论】:

您的代码对我有用。我的怀疑是你不熟悉chunked transfer encoding 并且你的流中那些“几个额外的字符”实际上是块头。 嗨,当然,我错过了这一点。感谢您的评论,我开始更改代码以跟踪块分隔符,但后来我意识到 asio 消息与服务器块无关(分隔符可能位于缓冲区的中间)。所以我改变了策略,首先将整个消息加载到缓冲区中,然后从那里填充流字符串。 【参考方案1】:

在评论交换中确定,服务器正在使用chunked transfer encoding:

分块传输编码是 1.1 版本中的一种数据传输机制 超文本传输​​协议 (HTTP),其中数据以 一系列“块”。它使用了 Transfer-Encoding HTTP 标头 的 Content-Length 标头,...

每个块都以十六进制块长度和 CRLF 开头。如果您对分块传输不熟悉,确实会出现一些奇怪的字符破坏您的数据流。

分块传输编码一般用于在发送前不方便确定响应正文的确切长度时。因此,接收者在处理最终的零长度块之前不知道正文长度(请注意,尾随的“headers”,也称为“trailers”,可能会跟随最终块)。

使用boost::asio可以使用async_read_until()通过CRLF分隔符读取chunk header,解析长度,然后使用async_read()transfer_exactly获取chunk数据。请注意,一旦您开始使用streambuf 进行读取,您应该继续使用相同的streambuf 实例,因为它可能会缓冲额外的数据(从streambuf 中提取特定数量的数据在here 中进行了讨论)。另请注意,块数据以您应该丢弃的 CRLF(不包含在长度中)结尾。

使用 boost::asio 编写自己的 HTTP 客户端可能很有启发性(如果您有时间和好奇心,甚至会很有趣),但要涵盖所有选项(例如压缩、预告片、重定向)并不容易) 在 HTTP 标准中。您可能需要考虑像 libcurl 这样的成熟客户端库是否适合您的需求。

【讨论】:

以上是关于boost::asio::streambuf 通过 https 检索 xml 数据的主要内容,如果未能解决你的问题,请参考以下文章

当 streambuf 由先前的 async_read 填充时, boost::asio::async_read 进入 boost::asio::streambuf 块

使用 boost::asio::async_wait_until 和 boost::asio::streambuf

Boost asio - 从标准输入异步读取已建立的字符数

使用 boost::log 输出用户定义的结构

将缓冲区转换为字符串 C++ boost

绑定: 通过 Binding 绑定对象, 通过 x:Bind 绑定对象, 通过 Binding 绑定集合, 通过 x:Bind 绑定集合