4

C++20 introduced language support for coroutines. My understanding is that that this consists of syntactic sugar, such as co_await and co_return, to which semantic meaning is attached via special "hooks".

The language support allows for idiomatic expression of asynchronous logic; in effect, allowing for code that reads sequentially, even where it uses callback mechanisms under the hood.

Does Boost.Beast have any support for C++20 coroutines, and if so, how can the two be used together? In particular, is it possible to read and write messages asynchronously over an unencrypted or SSL-encrypted websocket?

2 Answers 2

4

Like Richard just said. Here's the websocket_client_coro_ssl.cpp example reworked:

//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

//------------------------------------------------------------------------------
//
// Example: WebSocket SSL client, c++20 coroutines
//
//------------------------------------------------------------------------------

#include "/home/sehe/custom/superboost/libs/beast/example/common/root_certificates.hpp"

#include <boost/asio/co_spawn.hpp>
#include <boost/asio/experimental/as_tuple.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/use_future.hpp>

#include <boost/beast.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/beast/websocket/ssl.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace websocket = beast::websocket; // from <boost/beast/websocket.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl;       // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

//------------------------------------------------------------------------------

// Sends a WebSocket message and prints the response
net::awaitable<void>
do_session(
    std::string host,
    std::string const& port,
    std::string const& text,
    ssl::context& ctx)
{
    using net::use_awaitable;
    using net::experimental::as_tuple;

    auto ex = co_await net::this_coro::executor;
    // These objects perform our I/O
    tcp::resolver resolver(ex);
    websocket::stream<
        beast::ssl_stream<beast::tcp_stream>> ws(ex, ctx);

    try
    {
        // Look up the domain name
        auto const results = co_await resolver.async_resolve(
            host, port, use_awaitable);

        // Set a timeout on the operation
        beast::get_lowest_layer(ws).expires_after(
            std::chrono::seconds(30));

        // Make the connection on the IP address we get from a lookup
        auto ep = co_await beast::get_lowest_layer(ws).async_connect(
            results, use_awaitable);

        // Set SNI Hostname (many hosts need this to handshake
        // successfully)
        if(! SSL_set_tlsext_host_name(
               ws.next_layer().native_handle(), host.c_str()))
        {
            throw beast::system_error(
                static_cast<int>(::ERR_get_error()),
                net::error::get_ssl_category());
        }

        // Update the host string. This will provide the value of the
        // Host HTTP header during the WebSocket handshake.
        // See https://tools.ietf.org/html/rfc7230#section-5.4
        host += ':' + std::to_string(ep.port());

        // Set a timeout on the operation
        beast::get_lowest_layer(ws).expires_after(
            std::chrono::seconds(30));

        // Set a decorator to change the User-Agent of the handshake
        ws.set_option(websocket::stream_base::decorator(
            [](websocket::request_type& req)
            {
                req.set(
                    http::field::user_agent,
                    std::string(BOOST_BEAST_VERSION_STRING) +
                        " websocket-client-coro");
            }));

        // Perform the SSL handshake
        co_await ws.next_layer().async_handshake(
            ssl::stream_base::client, use_awaitable);

        // Turn off the timeout on the tcp_stream, because
        // the websocket stream has its own timeout system.
        beast::get_lowest_layer(ws).expires_never();

        // Set suggested timeout settings for the websocket
        ws.set_option(websocket::stream_base::timeout::suggested(
            beast::role_type::client));

        // Perform the websocket handshake
        co_await ws.async_handshake(host, "/", use_awaitable);

        // Send the message
        co_await ws.async_write(
            net::buffer(std::string(text)), use_awaitable);

        // This buffer will hold the incoming message
        beast::flat_buffer buffer;

        // Read a message into our buffer
        auto [ec, bytes] =
            co_await ws.async_read(buffer, as_tuple(use_awaitable));

        if(ec)
        {
            // eof is to be expected for some services
            if(ec != net::error::eof)
                throw beast::system_error(ec);
        } else
        {
            // The make_printable() function helps print a
            // ConstBufferSequence
            std::cout << beast::make_printable(buffer.data())
                      << std::endl;

            // Close the WebSocket connection
            co_await ws.async_close(
                websocket::close_code::normal, use_awaitable);
        }

    } catch(beast::system_error const& se)
    {
        //std::cerr << "Handled: " << se.code().message() << "\n";
        throw; // handled at the spawn site instead
    }
}

//------------------------------------------------------------------------------

int main(int argc, char** argv)
{
    // Check command line arguments.
    if(argc != 4)
    {
        std::cerr <<
            "Usage: websocket-client-coro-ssl <host> <port> <text>\n" <<
            "Example:\n" <<
            "    websocket-client-coro-ssl echo.websocket.org 443 \"Hello, world!\"\n";
        return EXIT_FAILURE;
    }
    auto const host = argv[1];
    auto const port = argv[2];
    auto const text = argv[3];

    // The io_context is required for all I/O
    net::io_context ioc;

    // The SSL context is required, and holds certificates
    ssl::context ctx{ssl::context::tlsv12_client};

    // This holds the root certificate used for verification
    load_root_certificates(ctx);

    // Launch the asynchronous operation
    net::co_spawn(
        ioc.get_executor(),
        do_session(host, port, text, ctx),
        [&](std::exception_ptr e)
        {
            try
            {
                std::rethrow_exception(e);
            } catch(std::exception const& e)
            {
                std::cerr << "Err: " << e.what() << "\n";
            }
        });

    // Run the I/O service. The call will return when
    // the socket is closed.
    ioc.run();

    return EXIT_SUCCESS;
}
Sign up to request clarification or add additional context in comments.

Comments

1

Yes. You should pass asio::use_awaitable as the completion token type, then co_await the value returned by the asynchronous initiation function.

If you want an error code rather than exception on failure, pass asio::experimental::as_tuple(asio::use_awaitable) as the completion token.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.