c++boostboost-beast

How to convert a Boost.Beast request<string_body> to a request<file_body>?


I'm writing a boost::beast server, and I'm having trouble figuring out how to use the different request flavours.

When a connection is accepted, I do this to read the request:

  1. I call async_read(), which populates my request<string_body>
  2. I look at the request's method and target to dispatch it to whoever can handle that request
using namespace boost::beast::http;
using namespace boost::beast;

class Session : public enable_shared_from_this<Session>
{

    tcp_steam            stream;
    flat_buffer          buf;
    request<string_body> req;
    
public:
    Session(boost::asio::ip::tcp::socket&& socket) 
        : stream(std::move(socket) ) {}
    
    void Read() {
    
        req = request<string_body>{}

        async_read(stream, buf, req, 
            bind_front_handler(&Session::OnRead, shared_from_this() ) 
        );
    }
    
    void OnRead(error_code ec, size_t)
    {
        auto method = GetRoute( req.target(), req.method() );
        method( std::move(req) );
    }
};

However, some of those methods (like when a user wants to upload/POST a binary file) I suspect would work better if they received a request<file_body>

Can I convert the request<string_body> to a request<file_body> in those methods?

If not, how can I know the request's method/target before creating the request object? It doesn't seem like there is a way to call async_read without knowing the Body in request<Body> ahead of time.


Solution

  • The idea here is to read the headers first, then decide on the body type, and switch.

    There is a body-type-switching conversion constructor specifically for this purpose, and it is documented here Change Body Type. It comes with an example, relevant excerpt:

    // Start with an empty_body parser
    request_parser<empty_body> req0;
    
    // Read just the header. Otherwise, the empty_body
    // would generate an error if body octets were received.
    read_header(stream, buffer, req0);
    
    // Choose a body depending on the method verb
    switch(req0.get().method())
    {
    case verb::post:
    {
        // If this is not a form upload then use a string_body
        if( req0.get()[field::content_type] != "application/x-www-form-urlencoded" &&
            req0.get()[field::content_type] != "multipart/form-data")
            goto do_dynamic_body;
    
        // Commit to string_body as the body type.
        // As long as there are no body octets in the parser
        // we are constructing from, no exception is thrown.
        request_parser<string_body> req{std::move(req0)};
    
        // Finish reading the message
        read(stream, buffer, req);
    
        // Call the handler. It can take ownership
        // if desired, since we are calling release()
        handler(req.release());
        break;
    }
    
    do_dynamic_body:
    default:
    {
        // Commit to dynamic_body as the body type.
        // As long as there are no body octets in the parser
        // we are constructing from, no exception is thrown.
        request_parser<dynamic_body> req{std::move(req0)};
    
        // Finish reading the message
        read(stream, buffer, req);
    
        // Call the handler. It can take ownership
        // if desired, since we are calling release()
        handler(req.release());
        break;
    }