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:
request_parser<empty_body>
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.
a_parser_with_different_body
without having an instance
of a_parser
to move from?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.
Your use case sketch is pretty sympathetic, and let's just see whether we can add the move-constructor without actually modifying the library:
#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:
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.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.