1

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.

Progman
  • 16,827
  • 6
  • 33
  • 48
Stewart
  • 4,356
  • 2
  • 27
  • 59
  • 1
    I think the most flexible option would be a raw buffer as the request body, preferably `vector_body` or `dynamic_body`. You can accommodate both raw binary data and strings. – guard3 Jun 08 '23 at 12:58
  • Thanks, I'll look into that. More generic at that level is better. – Stewart Jun 08 '23 at 13:02

1 Answers1

2

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;
}
sehe
  • 374,641
  • 47
  • 450
  • 633