beast icon indicating copy to clipboard operation
beast copied to clipboard

Sending message right before closing websocket

Open rdzienis opened this issue 2 years ago • 15 comments

I have an issue with letting the client know about disconnection purpose. I wanted to use deadline timer to have a timeout on async_read operation. If the client is inactive for 30 seconds, we send him message with such info and THEN disconnect.

Part of my code:

boost::asio::yield_context yield boost::beast::flat_buffer buffer; boost::beast::websocket::stream<boost::beast::ssl_stream<boost::beast::tcp_stream>>* ws boost::asio::deadline_timer t(ioc, boost::posix_time::seconds(session.timeout)); t.async_wait(boost::bind(&Connection::setInactivity, this, boost::asio::placeholders::error())); (*ws).async_read(buffer, yield[ec]);

setInactivity method is just setting session.active to false. then I perform : boost::beast::multi_buffer resp; boost::system::error_code ec;

`(*ws).text((*ws).got_text());
size_t n = buffer_copy(resp.prepare(response.size()), boost::asio::buffer(response));
resp.commit(n);

(*ws).async_write(resp.data(), yield[ec]);
if (ec)
{

    return showErrorMessage(ec, "method: async_write");
}
else
{
    PRINT_INFO("Request processed - sent response");
}`

It looked just fine, but when I called async_write, I got the following error: Operation canceled

Is it even possible to send a message before disconnecting?

Note: I found it impossible with built-in timeout which disconnects automatically.

rdzienis avatar Sep 12 '22 10:09 rdzienis

definitely don't use beast::tcp_stream's internal timeout with websocket.

As I read it, your t.async_wait(...) code won't execute until after the async_read has completed, because you are yielding the coroutine.

It might be a good idea if you can link a github repo containing a complete, small, self-contained program that demonstrates what you are trying to do. I can then offer suggestions on how to correct it.

madmongo1 avatar Sep 12 '22 11:09 madmongo1

I guess it will be hard to link a github repo as it is confidential. I pasted the code in wrong order, sorry. Firsty I start the timer, then I call async_read.

As far as I understand, I'm not "closing" the async_read, so I can't perform async_write.. but I don't know how to close/end/stop async_read.

rdzienis avatar Sep 12 '22 11:09 rdzienis

Don't copy your company's code. Write a new, very small, program which demonstrates the principle of what you want to achieve.

This is a websocket read with a timeout, yes?

madmongo1 avatar Sep 12 '22 11:09 madmongo1

Start reading from readMessageToByte.

void Connection::setInactivity(const boost::system::error_code& e)
{
    if (e == boost::asio::error::operation_aborted)
    {
        // Timer was stopped due to an incoming message - it's fine
        return;
    }
    else if (e)
    {
        PRINT_INFO("Timer failed - ", e.what());
    }
    else
    {
        PRINT_INFO("Disconnecting client due to timeout - was ", session.timeout);
        session.active = false;
    }
}


void Connection::sendResponse(std::vector<uint8_t> response, boost::asio::yield_context yield)
{
    boost::beast::multi_buffer resp;
    boost::system::error_code ec;

    (*ws).text((*ws).got_text());
    size_t n = buffer_copy(resp.prepare(response.size()), boost::asio::buffer(response));
    resp.commit(n);

    (*ws).async_write(resp.data(), yield[ec]);
    if (ec)
    {

        return showErrorMessage(ec);
    }
    else
    {
        PRINT_INFO("Request processed - sent response");
    }
}


std::vector<uint8_t> Connection::readMessageToByte(boost::asio::yield_context yield, boost::asio::io_context& ioc)
{
    boost::beast::flat_buffer buffer;
    std::vector<uint8_t> data;
    boost::system::error_code ec;

    session.active = true;
    boost::asio::deadline_timer t(ioc, boost::posix_time::seconds(session.timeout));
    t.async_wait(boost::bind(&Connection::setInactivity, this, boost::asio::placeholders::error()));

    (*ws).async_read(buffer, yield[ec]);

    if (!session.active)
    {
        std::vector<uint8_t> messageBeforeDisconnect = {1,2,3,4}

        sendResponse(messageBeforeDisconnect, yield);
        return data; //empty
    }
    else if (ec == boost::beast::websocket::error::closed || ec)
    {
        PRINT_INFO("Connection terminated - ", ec.message());
        return data; //empty
    }

    data.resize(buffer.data().size());
    boost::asio::buffer_copy(boost::asio::buffer(data, data.size()), buffer.data(), buffer.data().size());

    return data;
}

rdzienis avatar Sep 12 '22 11:09 rdzienis

The Websocket CLOSE message has a place to put both a numeric close code and a textual close reason, why don't you use that? Beast lets you set them both: https://www.boost.org/doc/libs/1_80_0/libs/beast/doc/html/beast/ref/boost__beast__websocket__stream/async_close.html

vinniefalco avatar Sep 12 '22 14:09 vinniefalco

@vinniefalco How Can I call this async_close method then? I would like to send a full frame before disconnecting. Is it possible to put a vector<uint8_t> in this "textual close reason"?

rdzienis avatar Sep 13 '22 09:09 rdzienis

I think there's a limit of 128 bytes

vinniefalco avatar Sep 13 '22 13:09 vinniefalco

/// The type representing the reason string in a close frame.
using reason_string = static_string<123, char>;

/// The type representing the payload of ping and pong messages.
using ping_data = static_string<125, char>;

https://github.com/boostorg/beast/blob/6ada618c120b50a3a394e4427d99d14ae78b3066/include/boost/beast/websocket/rfc6455.hpp#L158

madmongo1 avatar Sep 13 '22 15:09 madmongo1

123, close enough :)

vinniefalco avatar Sep 13 '22 15:09 vinniefalco

I think it will work out for me, I just don't know how to call async_close..

template<
    class CloseHandler = net::default_completion_token_t<[executor_type]>>
[DEDUCED]
async_close(
    close_reason const& cr,
    CloseHandler&& handler = net::default_completion_token_t< [executor_type] >{});

what is CloseHandler?

rdzienis avatar Sep 14 '22 10:09 rdzienis

Is that possible to get some help with that? @vinniefalco or @madmongo1

rdzienis avatar Sep 15 '22 07:09 rdzienis

CloseHandler is an asio-style completion handler or completion token. Just like any other asio async operation.

madmongo1 avatar Sep 15 '22 09:09 madmongo1

Could you provide me any example of its usage? I can't find anything that would help me with completing that task.

rdzienis avatar Sep 15 '22 10:09 rdzienis

Did you have a look at the documentation? https://www.boost.org/doc/libs/1_80_0/libs/beast/doc/html/beast/using_websocket/control_frames.html#beast.using_websocket.control_frames.close_frames

vinniefalco avatar Sep 16 '22 15:09 vinniefalco

I've just read in the documentation that for the async operations we have to use a handler. As you mentioned above, CloseHandler may be good, but as @rdzienis said, we can't find any examples of its usage. Should we implement it from the beginning just like any other handler or is there a way we could use an already written one?

We want to use the "ws.close(close_code::normal);" function that @vinniefalco recommended, but it is impossible without the handler. Could you explain in more detail how exactly should we manage with the handlers in our situation @madmongo1?

khernet avatar Sep 21 '22 14:09 khernet

I've just read in the documentation that for the async operations we have to use a handler. As you mentioned above, CloseHandler may be good, but as @rdzienis said, we can't find any examples of its usage. Should we implement it from the beginning just like any other handler or is there a way we could use an already written one?

We want to use the "ws.close(close_code::normal);" function that @vinniefalco recommended, but it is impossible without the handler. Could you explain in more detail how exactly should we manage with the handlers in our situation @madmongo1?

I recommend reading about Completion Tokens in the Asio documentation.

ashtum avatar Jan 03 '24 08:01 ashtum