Search code examples
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.

  • Is it possible to resume parsing with a_parser_with_different_body without having an instance of a_parser to move from?
  • If not, how should I move around the instance?

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:

    • it just obscures the intent
    • it is likely less efficient than passing around a unique resource
    • it already highlights an issue with "spreading concerns" (the opposite of Separation Of Concerns): the parser really inherently belongs with a stream. The stream state is tied to the parser progress, as evidenced by the fact that the buf instance would really be required to be passed in addition to the partial request if you were to read more from the same stream.

    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.