Boost Beast Websocket: Application Data After Close Notify

399 Views Asked by At

I am using boost::beast as well as boost::asio to implement a websocket client with SSL support. My WebsocketClient class has the following members:

    boost::asio::io_context& io_context;
    boost::asio::ssl::context& ssl_context;
    std::optional<tcp::resolver> resolver;
    std::optional<websocket::stream<beast::ssl_stream<beast::tcp_stream>>> ws;
    std::promise<void> promise_;

The io_context and ssl_context are constructed and passed by reference from main. The resolver and ws members are initialized via:

resolver.emplace(boost::asio::make_strand(io_context));
ws.emplace(boost::asio::make_strand(io_context), ssl_context);

After calling a WebsocketClient method "run" which triggers a sequence which calls connect, handshake, etc. we enter an async loop. Also of note, we set the value of promise before entering the "on_read" loop. This promise is returned to the caller of run in main which allows the application to progress once the initial WebsocketClient::run call is made.

void WebsocketClient::on_read(
  beast::error_code ec, 
  std::size_t bytes_transferred) {
  
  boost::ignore_unused(bytes_transferred);
  
  if (ec) {
    fail(ec, "read");
    reconnect(ec);
  } else {
    // business logic
    ws_->async_read(
      buffer_,
      beast::bind_front_handler(
        &WebsocketClient::on_read,
        this)
    );
  }
}

The WebsocketClient works well for several minutes until I reach an exception:

read: application data after close notify

Upon reaching this error, the WebsocketClient proceeds to:

void WebsocketClient::reconnect(beast::error_code ec) {
  ws.emplace(boost::asio::make_strand(io_context), ssl_context);
  promise_ = std::promise<void>();
  resolver_.emplace(boost::asio::make_strand(io_context));  
  on_disconnect_cb(ec);
}

The function "on_disconnect_cb" is passed in during WebsocketClient initialization as a lambda function. This function simply calls the WebsocketClient::run function again in attempt to reconnect.

To summarize my questions:

  1. Why am I receiving this "read: application data after close notify" error?

  2. Why does the application fail to reconnect after calling WebsocketClient::reconnect?

In the process of debugging I have concluded that the application makes no additional progress after calling:

  resolver->async_resolve(
    host, 
    port,
    beast::bind_front_handler(
      &WebsocketClient::on_resolve, 
      this)
  );

in the WebsocketClient::run function. This fails only in the case of a reconnect and run works as expected on first call. Therefore I expect the issue to be related to incorrectly resetting some of the components of the WebsocketClient.

Edit:

As requested in the comments, I'd like to provide a sketch of the "main" routine. This is indeed a sketch but should cover how the websocket is being used during the program lifecycle:


class App {
public:
  App(boost::asio::io_context& io_context,
      boost::asio::ssl::context& ssl_context) : {
    
    ws.emplace(boost::asio::io_context& io_context,
               boost::asio::ssl::context& ssl_context,
               [this]() {
                 // on_read callback for ws
                 logic();
               },
               [this]() {
                 // on_disconnect callback for ws
                 on_disconnect();
               }
               );
  }
  
  void run() {
    // call underlying websocket connect
  }

  void logic() {
    // do stuff with inbound message
  }

  void on_disconnect() {
    // destroy existing websocket
    
    // If *this contains a value, destroy that value as if by value().T::~T()
    ws.reset();  

    // Initialize new WebsocketClient
    ws.emplace(io_context, 
               ssl_context,
               [this](){
                 logic();
               },
               [this](){
                 on_disconnect();
               };
               );

    // call App::run again to restart
    run();
  }

private:
  boost::asio::io_context& io_context;
  boost::asio::ssl::context& ssl_context;
  std::optional<WebsocketClient> ws;
};

int main() {
  // spinup io_context and detach thread that polls

  App app(io_context, ssl_contex);
  
  // triggers WebsocketClient::connect
  app.run();
}
0

There are 0 best solutions below