Tim Clemons
Tim Clemons

Reputation: 6441

Asynchronous processing of streaming HTTP with boost::beast

I'm implementing a client which accesses a REST endpoint and then begins processing an SSE stream and monitoring events as they occur. To this end, I'm using Boost::Beast version 124 with Boost 1.63 and attempting to use async_read_some to incrementally read the body of the response.

Here's my code thus far:

namespace http = boost::beast::http;

http::response_parser<http::string_body> sse_client::m_parser;
http::response<http::string_body> sse_client::m_response;
boost::beast::flat_buffer m_buffer;

void sse_client::monitor_sse()
{
    http::request<http::empty_body> req{http::verb::get, m_target, 11};
    req.set(http::field::host, m_host);
    req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
    req.set(http::field::accept, "text/event-stream");
    http::async_write(m_socket, req,
            std::bind(
                &sse_client::process_sse,
                shared_from_this(),
                std::placeholders::_1,
                std::placeholders::_2));
}

void sse_client::process_sse(boost::system::error_code ec, std::size_t byte_count)
{
    http::read_header(m_socket, m_buffer, m_parser);
    http::async_read_some(m_socket, m_buffer, m_parser,
            std::bind(
                &sse_client::read_event,
                shared_from_this(),
                std::placeholders::_1));
}

void sse_client::read_event(boost::system::error_code ec)
{
    // TODO: process event
    http::async_read_some(m_socket, m_buffer, m_parser,
            std::bind(
                &sse_client::read_event,
                shared_from_this(),
                std::placeholders::_1));
}

My questions are:

  1. Is this the right approach for this particular use case?
  2. Is there a more appropriate type to use with response_parser and response than http::string_body?
  3. When the read_event handler is invoked, how does it access the content retrieved by async_read_some? Should it be pulled from the buffer?

Upvotes: 3

Views: 3321

Answers (1)

Vinnie Falco
Vinnie Falco

Reputation: 5353

I'll answer your questions first and then provide explanation.

  1. Yes, you want to read the header and then call read_some (or read, see below) until the parser returns true from is_complete(). However, in your code I notice you are mixing synchronous and asynchronous calls (read_header followed by async_read_some). It would be best to stick to just one model instead of mixing them.

  2. For your purposes you probably want buffer_body instead of string_body. There is an example in the documentation which shows how to do this (http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/parser_stream_operations/incremental_read.html)

  3. The "buffer" you refer to is the dynamic buffer argument passed to the HTTP stream operation. While this buffer will hold the message data, it is not for the application to inspect. This buffer is used to hold additional data past the end of the current message that the stream algorithm can read (this is explained in http://www.boost.org/doc/libs/1_66_0/libs/beast/doc/html/beast/using_http/message_stream_operations.html#beast.using_http.message_stream_operations.reading). You will access the content by inspecting the body of the message when using buffer_body

http::response_parser::get() will provide you with access to the message being read in.

The best solution for you is to use buffer_body as in the example, provide an area of memory to point it to and then call read or async_read in a loop. Every time the buffer is full, the read will return with the error beast::http::error::need_buffer, indicating that further calls are required.

Hope this helps!

Upvotes: 8

Related Questions