c++boostboost-beastbeast-websockets

How to read the header of a bad request in beast::websocket?


I have a websocket server(listening the loopback interface) that should respond to a GET request. Here's how I do that

ws_.async_accept_ex(
    [self = shared_from_this()](websocket::response_type& res) //
    {
        if (res.result_int() == 400) { // bad request, assume HTTP GET
            osstream response;
            /* invoke request handlers */
            for (auto& do_handle : self->handlers) {
                do_handle({ "GET" }, response);
                /* if responded, assing response to the body*/
                if (response.tellp()) {
                    res.content_length(response.str().size());
                    res.body() = std::move(response.str());
                    break;
                }
            }
        }
    },
    net::bind_executor(strand_,
        [self = shared_from_this()](beast::error_code ec) {
            self->on_accept(ec);
}));

But I also need to be able to handle locations(like localhost:2019/some_location).

Is there a way?

Solution: read the header manually, check if it's an upgrade

beast::flat_buffer buffer_;
websocket::request_type req_;

/*
  ...
  ...
*/
        http::async_read(ws_.next_layer(), buffer_, req_,
        [self=shared_from_this()](beast::error_code ec, size_t)
        {
            if(ec){
                fail(ec, "[http] read");
                return;
            }
            if(websocket::is_upgrade(self->req_)){
                self->ws_.accept(self->req_,ec);
                self->on_accept(ec);
            }
            else{
                osstream response;
                websocket::response_type res;
                vector<string_view> req_args
                    { "GET", self->req_.base().target().begin() };
                /* invoke request handlers */ 
                for(auto &do_handle : self->handlers){
                    do_handle(req_args, response);
                    /* if responded, assing response to the body*/
                    if(response.tellp()){
                        res.content_length(response.str().size());
                        res.body() = std::move(response.str());
                        break;
                }}
                http::write(self->ws_.next_layer(), res);
            }
        });

Solution

  • The request-target for a WebSocket Upgrade request must be in origin-form. "localhost:2019/some_location" is in absolute-form and therefore illegal. It sounds to me like what you want is to be able to specially handle HTTP requests which are not WebSocket upgrades, and for the actual WebSocket upgrades let the websocket stream handle it by performing the handshake.

    This is covered in the documentation: https://www.boost.org/doc/libs/1_69_0/libs/beast/doc/html/beast/using_websocket/handshaking_servers.html#beast.using_websocket.handshaking_servers.passing_http_requests

    And also in the examples: https://github.com/boostorg/beast/blob/06efddd8b851610b5b3a5832ac87f1c52b838d9b/example/advanced/server/advanced_server.cpp#L665

    tl;dr: Read the request yourself using beast::http::async_read, see if it is a websocket upgrade using beast::websocket::is_upgrade. If it is an upgrade, construct the beast::websocket::stream and call async_accept on it with the request, otherwise process the HTTP request the way you want and send back the response yourself using beast::http::async_write.