使用 boost::beast 处理大型 http 响应

Posted

技术标签:

【中文标题】使用 boost::beast 处理大型 http 响应【英文标题】:Handling large http response using boost::beast 【发布时间】:2021-09-01 10:09:09 【问题描述】:

以下代码用于获取http响应消息:

  boost::beast::tcp_stream stream_;

  boost::beast::flat_buffer buffer;
  boost::beast::http::response<boost::beast::http::dynamic_body> res;
  boost::beast::http::read(stream_, buffer, res);

但是,在某些情况下,根据前面的请求,我可以预期响应消息正文将包含大型二进制文件。

因此,我想直接将其读取到文件系统,而不是通过buffer 变量,以避免过度使用进程内存。怎么办?

在 Objective-c 框架 NSUrlSession 中,有一个简单的方法可以使用 NSURLSessionDownloadTask 而不是 NSURLSessionDataTask,所以我想知道它是否也存在于 boost 中。

谢谢!

【问题讨论】:

【参考方案1】:

一般情况下,您可以使用http::buffer_body 处理任意大的请求/响应消息。

如果您特别想从文件系统文件中读取/写入,则可以使用 http::file_body

完整演示buffer_body

buffer_body 的文档示例在此处https://www.boost.org/doc/libs/1_77_0/libs/beast/doc/html/beast/using_http/parser_stream_operations/incremental_read.html。

使用它写入 std::cout:Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>

namespace net       = boost::asio;
namespace beast     = boost::beast;
namespace http      = beast::http;
using tcp           = net::ip::tcp;
using socket_t      = tcp::socket;

/*  This function reads a message using a fixed size buffer to hold
    portions of the body, and prints the body contents to a `std::ostream`.
*/

template<
    bool isRequest,
    class SyncReadStream,
    class DynamicBuffer>
void
read_and_print_body(
    std::ostream& os,
    SyncReadStream& stream,
    DynamicBuffer& buffer,
    beast::error_code& ec)

    http::parser<isRequest, http::buffer_body> p;
    http::read_header(stream, buffer, p, ec);
    if(ec)
        return;
    while(! p.is_done())
    
        char buf[512];
        p.get().body().data = buf;
        p.get().body().size = sizeof(buf);
        http::read(stream, buffer, p, ec);
        if(ec == http::error::need_buffer)
            ec = ;
        if(ec)
            return;
        os.write(buf, sizeof(buf) - p.get().body().size);
    


int main() 
    std::string host = "173.203.57.63"; // COLIRU 20210901
    auto const port  = "80";

    net::io_context ioc;
    tcp::resolver   resolverioc;

    socket_t sioc;
    net::connect(s, resolver.resolve(host, port));

    write(s, http::request<http::empty_body>http::verb::get, "/", 11);

    beast::error_code  ec;
    beast::flat_buffer buf;

    read_and_print_body<false>(std::cout, s, buf, ec);

完整的file_body 示例

这要短得多,写信给body.html

Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <iostream>

namespace net       = boost::asio;
namespace beast     = boost::beast;
namespace http      = beast::http;
using tcp           = net::ip::tcp;
using socket_t      = tcp::socket;

int main() 
    std::string host = "173.203.57.63"; // COLIRU 20210901
    auto const port  = "80";

    net::io_context ioc;
    tcp::resolver   resolverioc;

    socket_t sioc;
    net::connect(s, resolver.resolve(host, port));

    write(s, http::request<http::empty_body>http::verb::get, "/", 11);

    beast::error_code  ec;
    beast::flat_buffer buf;
    http::response<http::file_body> res;
    res.body().open("body.html", beast::file_mode::write_new, ec);
    if (!ec.failed())
    
        read(s, buf, res, ec);
    

    std::cout << "Wrote 'body.html' (" << ec.message() << ")\n";
    std::cout << "Headers " << res.base() << "\n";

打印

Wrote 'body.html' (Success)
Headers HTTP/1.1 200 OK 
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Wed, 01 Sep 2021 19:52:20 GMT
Connection: Keep-Alive

file body.html; wc body.html 显示:

body.html: HTML document, ASCII text, with very long lines
 185  644 8616 body.html

超越:流式传输到子进程和流式处理

我在这里有一个高级示例:How to read data from Internet using muli-threading with connecting only once?。

【讨论】:

谢谢,效果很好。我只是希望还有一个选项可以自动将响应定向到文件而无需用户交互。 你说得对。我忘记了file_body,在心里假设它只是为了从静态文件中序列化正文。我重写了simple example of buffer_body to ostream 以使用file_body instead,确实更容易。将其添加到答案文本中。 很多更新了答案。感谢您让我注意到我的错误假设 :) 我只是忘记了我曾经在源代码中看到过。 感谢您的详细解答。但是,由于某种原因,在我使用file_mode 方法后,它以某种方式断开了套接字,所以我不得不重新连接才能重新使用它。我在使用基于块的技术时没有遇到过这样的行为......也许你知道为什么吗? 这是带有 Connection: close 标头的正常 HTTP 行为(假设不存在)。替代方法需要使用 Content-Length 和/或分块编码(因此您不要将 eof 视为响应结束)

以上是关于使用 boost::beast 处理大型 http 响应的主要内容,如果未能解决你的问题,请参考以下文章

C++ Boost 1.66 使用 Beast http request Parser 来解析字符串

Boost Beast服务器响应延迟1秒

Boost.Beast 高级服务器示例中的 HTTP Pipelining vs. WebSocket

试图用 Boost::Beast 替换我的 libwebsocket 代码

如何从另一个线程中断 websocket(使用 boost beast)?

如何使用 boost beast websocket 客户端收听 websocket 提要?