c++boostboost-beast

How to move a Boost.Beast parser object?


I am building a library on top of Boost.Beast and I am facing an issue. I do not understand why boost::beast::http::parser is not movable. The only way to move it, is to use the constructor designed for changing the body type, but this is defined only when the bodies are different.

template<bool isRequest, class Body, class Allocator = std::allocator<char>>
class parser : public basic_parser<isRequest>
{
public:
    ...
    parser(parser&& other) = delete;

    template<class OtherBody, class... Args,
        class = typename std::enable_if<
            ! std::is_same<Body, OtherBody>::value>::type>
    explicit
    parser(parser<isRequest, OtherBody,
        Allocator>&& parser, Args&&... args);

While I am sure there is a design choice behind the above limitation, I would like to be able to achieve the following:

  1. parse the request header with a request_parser<empty_body>
  2. move this parser into an object that will represent the HTTP request
  3. user code receives everything and, based on some logic, resumes the parsing by building a parser with the appropriate Body type.

Example

void a_function(request_parser<empty_body>&&);

request_parser<empty_body> a_parser;
// ... do stuff with 'a_parser', like reading the HTTP header

// This line does not compile
a_function(std::move(a_parser));
// while the following line compiles as it invokes a special move constructor
// that is defined only when the body type is different.
request_parser<string_body> a_parser_with_different_body { std::move(a_parser) };

I wonder if any of you has faced the same issue before and what is the right way to proceed.


Solution

  • I think movability is left for simplicity. With all the general parts to messages, it would seem unnecessarily restrictive to require every extension to allow moving and, much more subtly, avoid taking internal references at any time.

    The simplest way to bridge those requirements would be to wrap the entire message implementation into a smart pointer. That's a cost not everybody would be willing to pay. What's more, there's nothing that keeps you from doing it if you want it.

    Hack The System

    Your use case sketch is pretty sympathetic, and let's just see whether we can add the move-constructor without actually modifying the library:

    Live On Compiler Explorer

    #include <boost/beast.hpp>
    
    namespace asio  = boost::asio;
    namespace beast = boost::beast;
    namespace http  = beast::http;
    using asio::ip::tcp;
    
    struct temp_body : http::empty_body {};
    
    http::message_generator middleware(http::request_parser<temp_body> just_temp) {
        if (rand() % 2) {
            http::request_parser<http::string_body> r2(std::move(just_temp));
            // stuffs
            return std::move(r2.get());
        } else {
            http::request_parser<http::file_body> r3 (std::move(just_temp));
            // stuffs
            return std::move(r3.get());
        }
    }
    
    int main() {
        beast::flat_buffer buf;
        tcp::socket        s(asio::system_executor{}, {{}, 7878});
    
        http::request_parser<http::empty_body> r1;
    
        // do stuff like read headers
        r1.eager(false);
        read_header(s, buf, r1);
    
        // pass it off
        auto r2 = middleware(http::request_parser<temp_body>(std::move(r1)));
    }
    

    While this does compile I don't believe in the approach:

    SUMMARIZING

    Just don't do it: keep the parser with the stream owner. If you need to switch body types, that's fair.

    Future: I think the Beast author is on record saying that they somewhat regret some design choices surrounding the message class hierarchy and would probably not "merge" the body type into the message object again.

    Keep an eye out for [Non-Boost] http_proto by the same author.