diff --git a/example/websocket/client/CMakeLists.txt b/example/websocket/client/CMakeLists.txt index 5955ca18..5d1300e2 100644 --- a/example/websocket/client/CMakeLists.txt +++ b/example/websocket/client/CMakeLists.txt @@ -13,6 +13,7 @@ add_subdirectory (sync) if (OPENSSL_FOUND) add_subdirectory (async-ssl) + add_subdirectory (async-ssl-system-executor) add_subdirectory (coro-ssl) add_subdirectory (sync-ssl) endif() diff --git a/example/websocket/client/Jamfile b/example/websocket/client/Jamfile index bfdb89e8..94a45811 100644 --- a/example/websocket/client/Jamfile +++ b/example/websocket/client/Jamfile @@ -13,5 +13,6 @@ build-project sync ; # SSL build-project async-ssl ; +build-project async-ssl-system-executor ; build-project coro-ssl ; build-project sync-ssl ; diff --git a/example/websocket/client/async-ssl-system-executor/CMakeLists.txt b/example/websocket/client/async-ssl-system-executor/CMakeLists.txt new file mode 100644 index 00000000..63577367 --- /dev/null +++ b/example/websocket/client/async-ssl-system-executor/CMakeLists.txt @@ -0,0 +1,31 @@ +# +# Copyright (c) 2016-2017 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 +# + +if (OPENSSL_FOUND) + GroupSources(include/boost/beast beast) + GroupSources(example/common common) + GroupSources(example/websocket/client/async-ssl "/") + + add_executable (websocket-client-async-ssl-system-executor + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_async_ssl_system_executor.cpp + ) + + set_property(TARGET websocket-client-async-ssl-system-executor PROPERTY FOLDER "example-websocket-client-system-executor") + + target_link_libraries (websocket-client-async-ssl-system-executor + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/example/websocket/client/async-ssl-system-executor/Jamfile b/example/websocket/client/async-ssl-system-executor/Jamfile new file mode 100644 index 00000000..770a93c7 --- /dev/null +++ b/example/websocket/client/async-ssl-system-executor/Jamfile @@ -0,0 +1,22 @@ +# +# Copyright (c) 2016-2017 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 +# + +import ac ; + +project + : requirements + [ ac.check-library /boost/beast//lib-asio-ssl : /boost/beast//lib-asio-ssl/static : no ] + ; + +exe websocket-client-async-ssl-system-executor : + websocket_client_async_ssl_system_executor.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp b/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp new file mode 100644 index 00000000..a87ea430 --- /dev/null +++ b/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp @@ -0,0 +1,253 @@ +// +// 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, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace beast = boost::beast; // from +namespace http = beast::http; // from +namespace websocket = beast::websocket; // from +namespace net = boost::asio; // from +namespace ssl = boost::asio::ssl; // from +using tcp = boost::asio::ip::tcp; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Sends a WebSocket message and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + websocket::stream< + beast::ssl_stream> ws_; + beast::flat_buffer buffer_; + std::string host_; + std::string text_; + + // Objects are constructed with a strand to + // ensure that handlers do not execute concurrently. + session(net::strand ex, ssl::context& ctx) + : resolver_(ex) + , ws_(ex, ctx) + { + } +public: + // Delegate construction to a prive constructor to be able to use + // the same strand for both I/O objects. + explicit + session(ssl::context& ctx) + : session(net::make_strand(net::system_executor{}), ctx) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* text) + { + // Save these for later + host_ = host; + text_ = text; + + // Look up the domain name + resolver_.async_resolve( + host, + port, + beast::bind_front_handler( + &session::on_resolve, + shared_from_this())); + } + + void + on_resolve( + beast::error_code ec, + tcp::resolver::results_type results) + { + if(ec) + return fail(ec, "resolve"); + + // 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 + beast::get_lowest_layer(ws_).async_connect( + results, + beast::bind_front_handler( + &session::on_connect, + shared_from_this())); + } + + void + on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type) + { + if(ec) + return fail(ec, "connect"); + + // Set a timeout on the operation + beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); + + // Perform the SSL handshake + ws_.next_layer().async_handshake( + ssl::stream_base::client, + beast::bind_front_handler( + &session::on_ssl_handshake, + shared_from_this())); + } + + void + on_ssl_handshake(beast::error_code ec) + { + if(ec) + return fail(ec, "ssl_handshake"); + + // 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)); + + // 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-async-ssl"); + })); + + // Perform the websocket handshake + ws_.async_handshake(host_, "/", + beast::bind_front_handler( + &session::on_handshake, + shared_from_this())); + } + + void + on_handshake(beast::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws_.async_write( + net::buffer(text_), + beast::bind_front_handler( + &session::on_write, + shared_from_this())); + } + + void + on_write( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if(ec) + return fail(ec, "write"); + + // Read a message into our buffer + ws_.async_read( + buffer_, + beast::bind_front_handler( + &session::on_read, + shared_from_this())); + } + + void + on_read( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws_.async_close(websocket::close_code::normal, + beast::bind_front_handler( + &session::on_close, + shared_from_this())); + } + + void + on_close(beast::error_code ec) + { + if(ec) + return fail(ec, "close"); + + // If we get here then the connection is closed gracefully + + // The make_printable() function helps print a ConstBufferSequence + std::cout << beast::make_printable(buffer_.data()) << std::endl; + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-async-ssl \n" << + "Example:\n" << + " websocket-client-async-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 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 + std::make_shared(ctx)->run(host, port, text); + + // The async operations will run on the system_executor. + // Because the main thread has nothing to do in this example, we just wait + // for the system_executor to run out of work. + net::system_executor().context().join(); + + return EXIT_SUCCESS; +}