diff options
Diffstat (limited to 'src/boost/libs/beast/example/websocket')
68 files changed, 5567 insertions, 0 deletions
diff --git a/src/boost/libs/beast/example/websocket/CMakeLists.txt b/src/boost/libs/beast/example/websocket/CMakeLists.txt new file mode 100644 index 000000000..d38604dd0 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (c) 2013-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 +# + +add_subdirectory (client) +add_subdirectory (server) diff --git a/src/boost/libs/beast/example/websocket/Jamfile b/src/boost/libs/beast/example/websocket/Jamfile new file mode 100644 index 000000000..32f2c5144 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/Jamfile @@ -0,0 +1,11 @@ +# +# Copyright (c) 2013-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 +# + +build-project client ; +build-project server ; diff --git a/src/boost/libs/beast/example/websocket/client/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/CMakeLists.txt new file mode 100644 index 000000000..5d1300e2a --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2013-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 +# + +add_subdirectory (async) +add_subdirectory (coro) +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/src/boost/libs/beast/example/websocket/client/Jamfile b/src/boost/libs/beast/example/websocket/client/Jamfile new file mode 100644 index 000000000..94a458110 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/Jamfile @@ -0,0 +1,18 @@ +# +# Copyright (c) 2013-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 +# + +build-project async ; +build-project coro ; +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/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/CMakeLists.txt new file mode 100644 index 000000000..3a22aade8 --- /dev/null +++ b/src/boost/libs/beast/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-system-executor "/") + + 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") + + target_link_libraries (websocket-client-async-ssl-system-executor + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/Jamfile b/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/Jamfile new file mode 100644 index 000000000..770a93c7d --- /dev/null +++ b/src/boost/libs/beast/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-client-async-ssl-system-executor : + websocket_client_async_ssl_system_executor.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp b/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp new file mode 100644 index 000000000..40d19fd00 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async-ssl-system-executor/websocket_client_async_ssl_system_executor.cpp @@ -0,0 +1,259 @@ +// +// 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, using system_executor +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/strand.hpp> +#include <boost/asio/system_executor.hpp> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#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> + +//------------------------------------------------------------------------------ + +// 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<session> +{ + tcp::resolver resolver_; + websocket::stream< + beast::ssl_stream<beast::tcp_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<net::system_executor> 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 ep) + { + if(ec) + return fail(ec, "connect"); + + // 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)); + + // 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 <host> <port> <text>\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<session>(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; +} diff --git a/src/boost/libs/beast/example/websocket/client/async-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/async-ssl/CMakeLists.txt new file mode 100644 index 000000000..a21982806 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async-ssl/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 + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_async_ssl.cpp + ) + + set_property(TARGET websocket-client-async-ssl PROPERTY FOLDER "example-websocket-client") + + target_link_libraries (websocket-client-async-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/client/async-ssl/Jamfile b/src/boost/libs/beast/example/websocket/client/async-ssl/Jamfile new file mode 100644 index 000000000..1f46a94d9 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async-ssl/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-client-async-ssl : + websocket_client_async_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp b/src/boost/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp new file mode 100644 index 000000000..4d413aa78 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp @@ -0,0 +1,252 @@ +// +// 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 <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/strand.hpp> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#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> + +//------------------------------------------------------------------------------ + +// 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<session> +{ + tcp::resolver resolver_; + websocket::stream< + beast::ssl_stream<beast::tcp_stream>> ws_; + beast::flat_buffer buffer_; + std::string host_; + std::string text_; + +public: + // Resolver and socket require an io_context + explicit + session(net::io_context& ioc, ssl::context& ctx) + : resolver_(net::make_strand(ioc)) + , ws_(net::make_strand(ioc), 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 ep) + { + if(ec) + return fail(ec, "connect"); + + // 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)); + + // 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 <host> <port> <text>\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 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 + std::make_shared<session>(ioc, ctx)->run(host, port, text); + + // Run the I/O service. The call will return when + // the socket is closed. + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/client/async/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/async/CMakeLists.txt new file mode 100644 index 000000000..cc6434071 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/async "/") + +add_executable (websocket-client-async + ${BOOST_BEAST_FILES} + Jamfile + websocket_client_async.cpp +) + +target_link_libraries(websocket-client-async + lib-asio + lib-beast) + +set_property(TARGET websocket-client-async PROPERTY FOLDER "example-websocket-client") diff --git a/src/boost/libs/beast/example/websocket/client/async/Jamfile b/src/boost/libs/beast/example/websocket/client/async/Jamfile new file mode 100644 index 000000000..e5359db3b --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async/Jamfile @@ -0,0 +1,15 @@ +# +# 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 +# + +exe websocket-client-async : + websocket_client_async.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/client/async/websocket_client_async.cpp b/src/boost/libs/beast/example/websocket/client/async/websocket_client_async.cpp new file mode 100644 index 000000000..5fcf721b8 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/async/websocket_client_async.cpp @@ -0,0 +1,223 @@ +// +// 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 client, asynchronous +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/strand.hpp> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// 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<session> +{ + tcp::resolver resolver_; + websocket::stream<beast::tcp_stream> ws_; + beast::flat_buffer buffer_; + std::string host_; + std::string text_; + +public: + // Resolver and socket require an io_context + explicit + session(net::io_context& ioc) + : resolver_(net::make_strand(ioc)) + , ws_(net::make_strand(ioc)) + { + } + + // 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 the timeout for 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 ep) + { + if(ec) + return fail(ec, "connect"); + + // 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"); + })); + + // 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()); + + // 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 <host> <port> <text>\n" << + "Example:\n" << + " websocket-client-async echo.websocket.org 80 \"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; + + // Launch the asynchronous operation + std::make_shared<session>(ioc)->run(host, port, text); + + // Run the I/O service. The call will return when + // the socket is closed. + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/client/coro-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/coro-ssl/CMakeLists.txt new file mode 100644 index 000000000..fe9db75c4 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro-ssl/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/coro-ssl "/") + + add_executable (websocket-client-coro-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_coro_ssl.cpp + ) + + set_property(TARGET websocket-client-coro-ssl PROPERTY FOLDER "example-websocket-client") + + target_link_libraries (websocket-client-coro-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/client/coro-ssl/Jamfile b/src/boost/libs/beast/example/websocket/client/coro-ssl/Jamfile new file mode 100644 index 000000000..f6f1bb922 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro-ssl/Jamfile @@ -0,0 +1,23 @@ +# +# 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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-client-coro-ssl : + websocket_client_coro_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + <library>/boost/coroutine//boost_coroutine + ; diff --git a/src/boost/libs/beast/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp b/src/boost/libs/beast/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp new file mode 100644 index 000000000..5b4d9e9b4 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp @@ -0,0 +1,175 @@ +// +// 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, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/spawn.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> + +//------------------------------------------------------------------------------ + +// 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 +void +do_session( + std::string host, + std::string const& port, + std::string const& text, + net::io_context& ioc, + ssl::context& ctx, + net::yield_context yield) +{ + beast::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver(ioc); + websocket::stream< + beast::ssl_stream<beast::tcp_stream>> ws(ioc, ctx); + + // Look up the domain name + auto const results = resolver.async_resolve(host, port, yield[ec]); + 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 + auto ep = beast::get_lowest_layer(ws).async_connect(results, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // 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 + ws.next_layer().async_handshake(ssl::stream_base::client, yield[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)); + + // Perform the websocket handshake + ws.async_handshake(host, "/", yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws.async_write(net::buffer(std::string(text)), yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message into our buffer + ws.async_read(buffer, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws.async_close(websocket::close_code::normal, yield[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-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 + boost::asio::spawn(ioc, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(text), + std::ref(ioc), + std::ref(ctx), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the socket is closed. + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/client/coro/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/coro/CMakeLists.txt new file mode 100644 index 000000000..2bfeef282 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/coro "/") + +add_executable (websocket-client-coro + ${BOOST_BEAST_FILES} + Jamfile + websocket_client_coro.cpp +) + +target_link_libraries(websocket-client-coro + lib-asio + lib-beast) + +set_property(TARGET websocket-client-coro PROPERTY FOLDER "example-websocket-client") diff --git a/src/boost/libs/beast/example/websocket/client/coro/Jamfile b/src/boost/libs/beast/example/websocket/client/coro/Jamfile new file mode 100644 index 000000000..761dfe8ff --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro/Jamfile @@ -0,0 +1,16 @@ +# +# 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 +# + +exe websocket-client-coro : + websocket_client_coro.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + <library>/boost/coroutine//boost_coroutine + ; diff --git a/src/boost/libs/beast/example/websocket/client/coro/websocket_client_coro.cpp b/src/boost/libs/beast/example/websocket/client/coro/websocket_client_coro.cpp new file mode 100644 index 000000000..7a4b0616d --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/coro/websocket_client_coro.cpp @@ -0,0 +1,153 @@ +// +// 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 client, coroutine +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/spawn.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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// 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 +void +do_session( + std::string host, + std::string const& port, + std::string const& text, + net::io_context& ioc, + net::yield_context yield) +{ + beast::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver(ioc); + websocket::stream<beast::tcp_stream> ws(ioc); + + // Look up the domain name + auto const results = resolver.async_resolve(host, port, yield[ec]); + 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 + auto ep = beast::get_lowest_layer(ws).async_connect(results, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // 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()); + + // 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-coro"); + })); + + // Perform the websocket handshake + ws.async_handshake(host, "/", yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws.async_write(net::buffer(std::string(text)), yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message into our buffer + ws.async_read(buffer, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws.async_close(websocket::close_code::normal, yield[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-coro <host> <port> <text>\n" << + "Example:\n" << + " websocket-client-coro echo.websocket.org 80 \"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; + + // Launch the asynchronous operation + boost::asio::spawn(ioc, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(text), + std::ref(ioc), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the socket is closed. + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/client/sync-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/sync-ssl/CMakeLists.txt new file mode 100644 index 000000000..b84495702 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync-ssl/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/sync-ssl "/") + + add_executable (websocket-client-sync-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_sync_ssl.cpp + ) + + set_property(TARGET websocket-client-sync-ssl PROPERTY FOLDER "example-websocket-client") + + target_link_libraries (websocket-client-sync-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/client/sync-ssl/Jamfile b/src/boost/libs/beast/example/websocket/client/sync-ssl/Jamfile new file mode 100644 index 000000000..0cfec0f68 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync-ssl/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-client-sync-ssl : + websocket_client_sync_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp b/src/boost/libs/beast/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp new file mode 100644 index 000000000..1a51f6b6b --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp @@ -0,0 +1,116 @@ +// +// 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, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/connect.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/ssl/stream.hpp> +#include <cstdlib> +#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 +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-sync-ssl <host> <port> <text>\n" << + "Example:\n" << + " websocket-client-sync-ssl echo.websocket.org 443 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + std::string 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); + + // These objects perform our I/O + tcp::resolver resolver{ioc}; + websocket::stream<beast::ssl_stream<tcp::socket>> ws{ioc, ctx}; + + // Look up the domain name + auto const results = resolver.resolve(host, port); + + // Make the connection on the IP address we get from a lookup + auto ep = net::connect(get_lowest_layer(ws), results); + + // 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()); + + // Perform the SSL handshake + ws.next_layer().handshake(ssl::stream_base::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-coro"); + })); + + // Perform the websocket handshake + ws.handshake(host, "/"); + + // Send the message + ws.write(net::buffer(std::string(text))); + + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message into our buffer + ws.read(buffer); + + // Close the WebSocket connection + ws.close(websocket::close_code::normal); + + // 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; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/client/sync/CMakeLists.txt b/src/boost/libs/beast/example/websocket/client/sync/CMakeLists.txt new file mode 100644 index 000000000..d8606ab55 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/sync "/") + +add_executable (websocket-client-sync + ${BOOST_BEAST_FILES} + Jamfile + websocket_client_sync.cpp +) + +target_link_libraries(websocket-client-sync + lib-asio + lib-beast) + +set_property(TARGET websocket-client-sync PROPERTY FOLDER "example-websocket-client") diff --git a/src/boost/libs/beast/example/websocket/client/sync/Jamfile b/src/boost/libs/beast/example/websocket/client/sync/Jamfile new file mode 100644 index 000000000..ea71a0ce6 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync/Jamfile @@ -0,0 +1,15 @@ +# +# 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 +# + +exe websocket-client-sync : + websocket_client_sync.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/client/sync/websocket_client_sync.cpp b/src/boost/libs/beast/example/websocket/client/sync/websocket_client_sync.cpp new file mode 100644 index 000000000..07409b70e --- /dev/null +++ b/src/boost/libs/beast/example/websocket/client/sync/websocket_client_sync.cpp @@ -0,0 +1,105 @@ +// +// 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 client, synchronous +// +//------------------------------------------------------------------------------ + +//[example_websocket_client + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/connect.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <cstdlib> +#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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +// Sends a WebSocket message and prints the response +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-sync <host> <port> <text>\n" << + "Example:\n" << + " websocket-client-sync echo.websocket.org 80 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + std::string 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; + + // These objects perform our I/O + tcp::resolver resolver{ioc}; + websocket::stream<tcp::socket> ws{ioc}; + + // Look up the domain name + auto const results = resolver.resolve(host, port); + + // Make the connection on the IP address we get from a lookup + auto ep = net::connect(ws.next_layer(), results); + + // 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 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 websocket handshake + ws.handshake(host, "/"); + + // Send the message + ws.write(net::buffer(std::string(text))); + + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message into our buffer + ws.read(buffer); + + // Close the WebSocket connection + ws.close(websocket::close_code::normal); + + // 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; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +//] diff --git a/src/boost/libs/beast/example/websocket/server/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/CMakeLists.txt new file mode 100644 index 000000000..c5b8da5d6 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright (c) 2013-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 +# + +add_subdirectory (async) +add_subdirectory (chat-multi) +add_subdirectory (coro) +add_subdirectory (fast) +add_subdirectory (stackless) +add_subdirectory (sync) + +if (OPENSSL_FOUND) + add_subdirectory (async-ssl) + add_subdirectory (coro-ssl) + add_subdirectory (stackless-ssl) + add_subdirectory (sync-ssl) +endif() diff --git a/src/boost/libs/beast/example/websocket/server/Jamfile b/src/boost/libs/beast/example/websocket/server/Jamfile new file mode 100644 index 000000000..ccfd63fa4 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2013-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 +# + +build-project async ; +build-project chat-multi ; +build-project coro ; +build-project fast ; +build-project stackless ; +build-project sync ; + +# SSL +build-project async-ssl ; +build-project coro-ssl ; +build-project stackless-ssl ; +build-project sync-ssl ; diff --git a/src/boost/libs/beast/example/websocket/server/async-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/async-ssl/CMakeLists.txt new file mode 100644 index 000000000..8f1055d2f --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async-ssl/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/server/async-ssl "/") + + add_executable (websocket-server-async-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_async_ssl.cpp + ) + + set_property(TARGET websocket-server-async-ssl PROPERTY FOLDER "example-websocket-server") + + target_link_libraries (websocket-server-async-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/server/async-ssl/Jamfile b/src/boost/libs/beast/example/websocket/server/async-ssl/Jamfile new file mode 100644 index 000000000..f9b253a0c --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async-ssl/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-server-async-ssl : + websocket_server_async_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp b/src/boost/libs/beast/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp new file mode 100644 index 000000000..b79abbbfb --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp @@ -0,0 +1,317 @@ +// +// 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 server, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/strand.hpp> +#include <boost/asio/dispatch.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session : public std::enable_shared_from_this<session> +{ + websocket::stream< + beast::ssl_stream<beast::tcp_stream>> ws_; + beast::flat_buffer buffer_; + +public: + // Take ownership of the socket + session(tcp::socket&& socket, ssl::context& ctx) + : ws_(std::move(socket), ctx) + { + } + + // Get on the correct executor + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch(ws_.get_executor(), + beast::bind_front_handler( + &session::on_run, + shared_from_this())); + } + + // Start the asynchronous operation + void + on_run() + { + // Set the timeout. + beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); + + // Perform the SSL handshake + ws_.next_layer().async_handshake( + ssl::stream_base::server, + beast::bind_front_handler( + &session::on_handshake, + shared_from_this())); + } + + void + on_handshake(beast::error_code ec) + { + if(ec) + return fail(ec, "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::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-async-ssl"); + })); + + // Accept the websocket handshake + ws_.async_accept( + beast::bind_front_handler( + &session::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // 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); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + 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"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this<listener> +{ + net::io_context& ioc_; + ssl::context& ctx_; + tcp::acceptor acceptor_; + +public: + listener( + net::io_context& ioc, + ssl::context& ctx, + tcp::endpoint endpoint) + : ioc_(ioc) + , ctx_(ctx) + , acceptor_(net::make_strand(ioc)) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + do_accept(); + } + +private: + void + do_accept() + { + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &listener::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared<session>(std::move(socket), ctx_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async-ssl <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-async-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::tlsv12}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared<listener>(ioc, ctx, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/async/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/async/CMakeLists.txt new file mode 100644 index 000000000..0e1ceb8b6 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/async "/") + +add_executable (websocket-server-async + ${BOOST_BEAST_FILES} + Jamfile + websocket_server_async.cpp +) + +target_link_libraries(websocket-server-async + lib-asio + lib-beast) + +set_property(TARGET websocket-server-async PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/async/Jamfile b/src/boost/libs/beast/example/websocket/server/async/Jamfile new file mode 100644 index 000000000..e1e63e14d --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async/Jamfile @@ -0,0 +1,15 @@ +# +# 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 +# + +exe websocket-server-async : + websocket_server_async.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/async/websocket_server_async.cpp b/src/boost/libs/beast/example/websocket/server/async/websocket_server_async.cpp new file mode 100644 index 000000000..4b9570868 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/async/websocket_server_async.cpp @@ -0,0 +1,281 @@ +// +// 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 server, asynchronous +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/dispatch.hpp> +#include <boost/asio/strand.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session : public std::enable_shared_from_this<session> +{ + websocket::stream<beast::tcp_stream> ws_; + beast::flat_buffer buffer_; + +public: + // Take ownership of the socket + explicit + session(tcp::socket&& socket) + : ws_(std::move(socket)) + { + } + + // Get on the correct executor + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch(ws_.get_executor(), + beast::bind_front_handler( + &session::on_run, + shared_from_this())); + } + + // Start the asynchronous operation + void + on_run() + { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-async"); + })); + // Accept the websocket handshake + ws_.async_accept( + beast::bind_front_handler( + &session::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // 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); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + 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"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this<listener> +{ + net::io_context& ioc_; + tcp::acceptor acceptor_; + +public: + listener( + net::io_context& ioc, + tcp::endpoint endpoint) + : ioc_(ioc) + , acceptor_(ioc) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + do_accept(); + } + +private: + void + do_accept() + { + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &listener::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared<session>(std::move(socket))->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-async 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // Create and launch a listening port + std::make_shared<listener>(ioc, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/chat-multi/CMakeLists.txt new file mode 100644 index 000000000..a256b000f --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/CMakeLists.txt @@ -0,0 +1,41 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/chat-multi "/") + +file (GLOB APP_FILES + beast.hpp + http_session.cpp + http_session.hpp + Jamfile + listener.cpp + listener.hpp + main.cpp + net.hpp + shared_state.cpp + shared_state.hpp + websocket_session.cpp + websocket_session.hpp + chat_client.html + README.md +) + +source_group ("" FILES ${APP_FILES}) + +add_executable (websocket-chat-multi + ${APP_FILES} + ${BOOST_BEAST_FILES} +) + +target_link_libraries(websocket-chat-multi + lib-asio + lib-beast) + +set_property(TARGET websocket-chat-multi PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/Jamfile b/src/boost/libs/beast/example/websocket/server/chat-multi/Jamfile new file mode 100644 index 000000000..a8d49bfdd --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/Jamfile @@ -0,0 +1,19 @@ +# +# 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 +# + +exe websocket-chat-multi : + http_session.cpp + listener.cpp + main.cpp + shared_state.cpp + websocket_session.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/beast.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/beast.hpp new file mode 100644 index 000000000..0b7a9c61a --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/beast.hpp @@ -0,0 +1,19 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_BEAST_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_BEAST_HPP + +#include <boost/beast.hpp> + +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> + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/chat_client.html b/src/boost/libs/beast/example/websocket/server/chat-multi/chat_client.html new file mode 100644 index 000000000..2be69cdf0 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/chat_client.html @@ -0,0 +1,60 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8" /> + <title>Boost.Beast WebSocket Chat Client</title> +</head> +<body> + <h1>Boost.Beast WebSocket Chat Client</h1> + <p> + <a href="http://www.boost.org/libs/beast">Boost.Beast</a> + <a href="http://www.boost.org/libs/beast/example/websocket/server/chat-multi">Source Code</a> + </p> + Server URI: <input class="draw-border" id="uri" size="47" value="ws://localhost:8080" style="margin-bottom: 5px;"> + <button class="echo-button" id="connect">Connect</button> + <button class="echo-button" id="disconnect">Disconnect</button><br> + Your Name: <input class="draw-border" id="userName" size=47 style="margin-bottom: 5px;"><br> + <pre id="messages" style="width: 600px; height: 400px; white-space: normal; overflow: auto; border: solid 1px #cccccc; margin-bottom: 5px;"></pre> + <div style="margin-bottom: 5px;"> + Message<br> + <input class="draw-border" id="sendMessage" size="74" value=""> + <button class="echo-button" id="send">Send</button> + </div> + <script> + var ws = null; + function showMessage(msg) { + messages.innerText += msg + "\n"; + messages.scrollTop = messages.scrollHeight - messages.clientHeight; + }; + connect.onclick = function() { + ws = new WebSocket(uri.value); + ws.onopen = function(ev) { + showMessage("[connection opened]"); + }; + ws.onclose = function(ev) { + showMessage("[connection closed]"); + }; + ws.onmessage = function(ev) { + showMessage(ev.data); + }; + ws.onerror = function(ev) { + showMessage("[error]"); + console.log(ev); + }; + }; + disconnect.onclick = function() { + ws.close(); + }; + send.onclick = function() { + ws.send(userName.value + ": " + sendMessage.value); + sendMessage.value = ""; + }; + sendMessage.onkeyup = function(ev) { + ev.preventDefault(); + if (ev.keyCode === 13) { + send.click(); + } + } + </script> +</body> +</html> diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.cpp b/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.cpp new file mode 100644 index 000000000..9d4dee871 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.cpp @@ -0,0 +1,371 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#include "http_session.hpp" +#include "websocket_session.hpp" +#include <boost/config.hpp> +#include <iostream> + +#define BOOST_NO_CXX14_GENERIC_LAMBDAS + +//------------------------------------------------------------------------------ + +// Return a reasonable mime type based on the extension of a file. +beast::string_view +mime_type(beast::string_view path) +{ + using beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == beast::string_view::npos) + return beast::string_view{}; + return path.substr(pos); + }(); + if(iequals(ext, ".htm")) return "text/html"; + if(iequals(ext, ".html")) return "text/html"; + if(iequals(ext, ".php")) return "text/html"; + if(iequals(ext, ".css")) return "text/css"; + if(iequals(ext, ".txt")) return "text/plain"; + if(iequals(ext, ".js")) return "application/javascript"; + if(iequals(ext, ".json")) return "application/json"; + if(iequals(ext, ".xml")) return "application/xml"; + if(iequals(ext, ".swf")) return "application/x-shockwave-flash"; + if(iequals(ext, ".flv")) return "video/x-flv"; + if(iequals(ext, ".png")) return "image/png"; + if(iequals(ext, ".jpe")) return "image/jpeg"; + if(iequals(ext, ".jpeg")) return "image/jpeg"; + if(iequals(ext, ".jpg")) return "image/jpeg"; + if(iequals(ext, ".gif")) return "image/gif"; + if(iequals(ext, ".bmp")) return "image/bmp"; + if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon"; + if(iequals(ext, ".tiff")) return "image/tiff"; + if(iequals(ext, ".tif")) return "image/tiff"; + if(iequals(ext, ".svg")) return "image/svg+xml"; + if(iequals(ext, ".svgz")) return "image/svg+xml"; + return "application/text"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + beast::string_view base, + beast::string_view path) +{ + if(base.empty()) + return std::string(path); + std::string result(base); +#ifdef BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + beast::string_view doc_root, + http::request<Body, http::basic_fields<Allocator>>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](beast::string_view why) + { + http::response<http::string_body> res{http::status::bad_request, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = std::string(why); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](beast::string_view target) + { + http::response<http::string_body> res{http::status::not_found, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "The resource '" + std::string(target) + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](beast::string_view what) + { + http::response<http::string_body> res{http::status::internal_server_error, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body() = "An error occurred: '" + std::string(what) + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Cache the size since we need it after the move + auto const size = body.size(); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response<http::empty_body> res{http::status::ok, req.version()}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(size); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to GET request + http::response<http::file_body> res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version())}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(size); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +struct http_session::send_lambda +{ + http_session& self_; + + explicit + send_lambda(http_session& self) + : self_(self) + { + } + + template<bool isRequest, class Body, class Fields> + void + operator()(http::message<isRequest, Body, Fields>&& msg) const + { + // The lifetime of the message has to extend + // for the duration of the async operation so + // we use a shared_ptr to manage it. + auto sp = boost::make_shared< + http::message<isRequest, Body, Fields>>(std::move(msg)); + + // Write the response + auto self = self_.shared_from_this(); + http::async_write( + self_.stream_, + *sp, + [self, sp](beast::error_code ec, std::size_t bytes) + { + self->on_write(ec, bytes, sp->need_eof()); + }); + } +}; + +//------------------------------------------------------------------------------ + +http_session:: +http_session( + tcp::socket&& socket, + boost::shared_ptr<shared_state> const& state) + : stream_(std::move(socket)) + , state_(state) +{ +} + +void +http_session:: +run() +{ + do_read(); +} + +// Report a failure +void +http_session:: +fail(beast::error_code ec, char const* what) +{ + // Don't report on canceled operations + if(ec == net::error::operation_aborted) + return; + + std::cerr << what << ": " << ec.message() << "\n"; +} + +void +http_session:: +do_read() +{ + // Construct a new parser for each message + parser_.emplace(); + + // Apply a reasonable limit to the allowed size + // of the body in bytes to prevent abuse. + parser_->body_limit(10000); + + // Set the timeout. + stream_.expires_after(std::chrono::seconds(30)); + + // Read a request + http::async_read( + stream_, + buffer_, + parser_->get(), + beast::bind_front_handler( + &http_session::on_read, + shared_from_this())); +} + +void +http_session:: +on_read(beast::error_code ec, std::size_t) +{ + // This means they closed the connection + if(ec == http::error::end_of_stream) + { + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + return; + } + + // Handle the error, if any + if(ec) + return fail(ec, "read"); + + // See if it is a WebSocket Upgrade + if(websocket::is_upgrade(parser_->get())) + { + // Create a websocket session, transferring ownership + // of both the socket and the HTTP request. + boost::make_shared<websocket_session>( + stream_.release_socket(), + state_)->run(parser_->release()); + return; + } + + // Send the response +#ifndef BOOST_NO_CXX14_GENERIC_LAMBDAS + // + // The following code requires generic + // lambdas, available in C++14 and later. + // + handle_request( + state_->doc_root(), + std::move(req_), + [this](auto&& response) + { + // The lifetime of the message has to extend + // for the duration of the async operation so + // we use a shared_ptr to manage it. + using response_type = typename std::decay<decltype(response)>::type; + auto sp = boost::make_shared<response_type>(std::forward<decltype(response)>(response)); + + #if 0 + // NOTE This causes an ICE in gcc 7.3 + // Write the response + http::async_write(this->stream_, *sp, + [self = shared_from_this(), sp]( + beast::error_code ec, std::size_t bytes) + { + self->on_write(ec, bytes, sp->need_eof()); + }); + #else + // Write the response + auto self = shared_from_this(); + http::async_write(stream_, *sp, + [self, sp]( + beast::error_code ec, std::size_t bytes) + { + self->on_write(ec, bytes, sp->need_eof()); + }); + #endif + }); +#else + // + // This code uses the function object type send_lambda in + // place of a generic lambda which is not available in C++11 + // + handle_request( + state_->doc_root(), + parser_->release(), + send_lambda(*this)); + +#endif +} + +void +http_session:: +on_write(beast::error_code ec, std::size_t, bool close) +{ + // Handle the error, if any + if(ec) + return fail(ec, "write"); + + if(close) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + return; + } + + // Read another request + do_read(); +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.hpp new file mode 100644 index 000000000..637a5d898 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/http_session.hpp @@ -0,0 +1,48 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_HTTP_SESSION_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_HTTP_SESSION_HPP + +#include "net.hpp" +#include "beast.hpp" +#include "shared_state.hpp" +#include <boost/optional.hpp> +#include <boost/smart_ptr.hpp> +#include <cstdlib> +#include <memory> + +/** Represents an established HTTP connection +*/ +class http_session : public boost::enable_shared_from_this<http_session> +{ + beast::tcp_stream stream_; + beast::flat_buffer buffer_; + boost::shared_ptr<shared_state> state_; + + // The parser is stored in an optional container so we can + // construct it from scratch it at the beginning of each new message. + boost::optional<http::request_parser<http::string_body>> parser_; + + struct send_lambda; + + void fail(beast::error_code ec, char const* what); + void do_read(); + void on_read(beast::error_code ec, std::size_t); + void on_write(beast::error_code ec, std::size_t, bool close); + +public: + http_session( + tcp::socket&& socket, + boost::shared_ptr<shared_state> const& state); + + void run(); +}; + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/listener.cpp b/src/boost/libs/beast/example/websocket/server/chat-multi/listener.cpp new file mode 100644 index 000000000..8bbecb49b --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/listener.cpp @@ -0,0 +1,101 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#include "listener.hpp" +#include "http_session.hpp" +#include <iostream> + +listener:: +listener( + net::io_context& ioc, + tcp::endpoint endpoint, + boost::shared_ptr<shared_state> const& state) + : ioc_(ioc) + , acceptor_(ioc) + , state_(state) +{ + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } +} + +void +listener:: +run() +{ + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &listener::on_accept, + shared_from_this())); +} + +// Report a failure +void +listener:: +fail(beast::error_code ec, char const* what) +{ + // Don't report on canceled operations + if(ec == net::error::operation_aborted) + return; + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handle a connection +void +listener:: +on_accept(beast::error_code ec, tcp::socket socket) +{ + if(ec) + return fail(ec, "accept"); + else + // Launch a new session for this connection + boost::make_shared<http_session>( + std::move(socket), + state_)->run(); + + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &listener::on_accept, + shared_from_this())); +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/listener.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/listener.hpp new file mode 100644 index 000000000..a0c536e2e --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/listener.hpp @@ -0,0 +1,42 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_LISTENER_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_LISTENER_HPP + +#include "beast.hpp" +#include "net.hpp" +#include <boost/smart_ptr.hpp> +#include <memory> +#include <string> + +// Forward declaration +class shared_state; + +// Accepts incoming connections and launches the sessions +class listener : public boost::enable_shared_from_this<listener> +{ + net::io_context& ioc_; + tcp::acceptor acceptor_; + boost::shared_ptr<shared_state> state_; + + void fail(beast::error_code ec, char const* what); + void on_accept(beast::error_code ec, tcp::socket socket); + +public: + listener( + net::io_context& ioc, + tcp::endpoint endpoint, + boost::shared_ptr<shared_state> const& state); + + // Start accepting incoming connections + void run(); +}; + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/main.cpp b/src/boost/libs/beast/example/websocket/server/chat-multi/main.cpp new file mode 100644 index 000000000..8645f1ded --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/main.cpp @@ -0,0 +1,84 @@ +// +// 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/vinniefalco/CppCon2018 +// + +//------------------------------------------------------------------------------ +/* + WebSocket chat server, multi-threaded + + This implements a multi-user chat room using WebSocket. The + `io_context` runs on any number of threads, specified at + the command line. + +*/ +//------------------------------------------------------------------------------ + +#include "listener.hpp" +#include "shared_state.hpp" + +#include <boost/asio/signal_set.hpp> +#include <boost/smart_ptr.hpp> +#include <iostream> +#include <vector> + +int +main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: websocket-chat-multi <address> <port> <doc_root> <threads>\n" << + "Example:\n" << + " websocket-chat-server 0.0.0.0 8080 . 5\n"; + return EXIT_FAILURE; + } + auto address = net::ip::make_address(argv[1]); + auto port = static_cast<unsigned short>(std::atoi(argv[2])); + auto doc_root = argv[3]; + auto const threads = std::max<int>(1, std::atoi(argv[4])); + + // The io_context is required for all I/O + net::io_context ioc; + + // Create and launch a listening port + boost::make_shared<listener>( + ioc, + tcp::endpoint{address, port}, + boost::make_shared<shared_state>(doc_root))->run(); + + // Capture SIGINT and SIGTERM to perform a clean shutdown + net::signal_set signals(ioc, SIGINT, SIGTERM); + signals.async_wait( + [&ioc](boost::system::error_code const&, int) + { + // Stop the io_context. This will cause run() + // to return immediately, eventually destroying the + // io_context and any remaining handlers in it. + ioc.stop(); + }); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + // (If we get here, it means we got a SIGINT or SIGTERM) + + // Block until all the threads exit + for(auto& t : v) + t.join(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/net.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/net.hpp new file mode 100644 index 000000000..48a215355 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/net.hpp @@ -0,0 +1,18 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_NET_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_NET_HPP + +#include <boost/asio.hpp> + +namespace net = boost::asio; // from <boost/asio.hpp> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.cpp b/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.cpp new file mode 100644 index 000000000..9fceecc5d --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.cpp @@ -0,0 +1,59 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#include "shared_state.hpp" +#include "websocket_session.hpp" + +shared_state:: +shared_state(std::string doc_root) + : doc_root_(std::move(doc_root)) +{ +} + +void +shared_state:: +join(websocket_session* session) +{ + std::lock_guard<std::mutex> lock(mutex_); + sessions_.insert(session); +} + +void +shared_state:: +leave(websocket_session* session) +{ + std::lock_guard<std::mutex> lock(mutex_); + sessions_.erase(session); +} + +// Broadcast a message to all websocket client sessions +void +shared_state:: +send(std::string message) +{ + // Put the message in a shared pointer so we can re-use it for each client + auto const ss = boost::make_shared<std::string const>(std::move(message)); + + // Make a local list of all the weak pointers representing + // the sessions, so we can do the actual sending without + // holding the mutex: + std::vector<boost::weak_ptr<websocket_session>> v; + { + std::lock_guard<std::mutex> lock(mutex_); + v.reserve(sessions_.size()); + for(auto p : sessions_) + v.emplace_back(p->weak_from_this()); + } + + // For each session in our local list, try to acquire a strong + // pointer. If successful, then send the message on that session. + for(auto const& wp : v) + if(auto sp = wp.lock()) + sp->send(ss); +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.hpp new file mode 100644 index 000000000..08f4b2d34 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.hpp @@ -0,0 +1,48 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_SHARED_STATE_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_SHARED_STATE_HPP + +#include <boost/smart_ptr.hpp> +#include <memory> +#include <mutex> +#include <string> +#include <unordered_set> + +// Forward declaration +class websocket_session; + +// Represents the shared server state +class shared_state +{ + std::string const doc_root_; + + // This mutex synchronizes all access to sessions_ + std::mutex mutex_; + + // Keep a list of all the connected clients + std::unordered_set<websocket_session*> sessions_; + +public: + explicit + shared_state(std::string doc_root); + + std::string const& + doc_root() const noexcept + { + return doc_root_; + } + + void join (websocket_session* session); + void leave (websocket_session* session); + void send (std::string message); +}; + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.cpp b/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.cpp new file mode 100644 index 000000000..4807a5f1e --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.cpp @@ -0,0 +1,135 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#include "websocket_session.hpp" +#include <iostream> + +websocket_session:: +websocket_session( + tcp::socket&& socket, + boost::shared_ptr<shared_state> const& state) + : ws_(std::move(socket)) + , state_(state) +{ +} + +websocket_session:: +~websocket_session() +{ + // Remove this session from the list of active sessions + state_->leave(this); +} + +void +websocket_session:: +fail(beast::error_code ec, char const* what) +{ + // Don't report these + if( ec == net::error::operation_aborted || + ec == websocket::error::closed) + return; + + std::cerr << what << ": " << ec.message() << "\n"; +} + +void +websocket_session:: +on_accept(beast::error_code ec) +{ + // Handle the error, if any + if(ec) + return fail(ec, "accept"); + + // Add this session to the list of active sessions + state_->join(this); + + // Read a message + ws_.async_read( + buffer_, + beast::bind_front_handler( + &websocket_session::on_read, + shared_from_this())); +} + +void +websocket_session:: +on_read(beast::error_code ec, std::size_t) +{ + // Handle the error, if any + if(ec) + return fail(ec, "read"); + + // Send to all connections + state_->send(beast::buffers_to_string(buffer_.data())); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Read another message + ws_.async_read( + buffer_, + beast::bind_front_handler( + &websocket_session::on_read, + shared_from_this())); +} + +void +websocket_session:: +send(boost::shared_ptr<std::string const> const& ss) +{ + // Post our work to the strand, this ensures + // that the members of `this` will not be + // accessed concurrently. + + net::post( + ws_.get_executor(), + beast::bind_front_handler( + &websocket_session::on_send, + shared_from_this(), + ss)); +} + +void +websocket_session:: +on_send(boost::shared_ptr<std::string const> const& ss) +{ + // Always add to queue + queue_.push_back(ss); + + // Are we already writing? + if(queue_.size() > 1) + return; + + // We are not currently writing, so send this immediately + ws_.async_write( + net::buffer(*queue_.front()), + beast::bind_front_handler( + &websocket_session::on_write, + shared_from_this())); +} + +void +websocket_session:: +on_write(beast::error_code ec, std::size_t) +{ + // Handle the error, if any + if(ec) + return fail(ec, "write"); + + // Remove the string from the queue + queue_.erase(queue_.begin()); + + // Send the next message if any + if(! queue_.empty()) + ws_.async_write( + net::buffer(*queue_.front()), + beast::bind_front_handler( + &websocket_session::on_write, + shared_from_this())); +} diff --git a/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.hpp b/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.hpp new file mode 100644 index 000000000..c47d48ac9 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.hpp @@ -0,0 +1,86 @@ +// +// 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/vinniefalco/CppCon2018 +// + +#ifndef BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_WEBSOCKET_SESSION_HPP +#define BOOST_BEAST_EXAMPLE_WEBSOCKET_CHAT_MULTI_WEBSOCKET_SESSION_HPP + +#include "net.hpp" +#include "beast.hpp" +#include "shared_state.hpp" + +#include <cstdlib> +#include <memory> +#include <string> +#include <vector> + +// Forward declaration +class shared_state; + +/** Represents an active WebSocket connection to the server +*/ +class websocket_session : public boost::enable_shared_from_this<websocket_session> +{ + beast::flat_buffer buffer_; + websocket::stream<beast::tcp_stream> ws_; + boost::shared_ptr<shared_state> state_; + std::vector<boost::shared_ptr<std::string const>> queue_; + + void fail(beast::error_code ec, char const* what); + void on_accept(beast::error_code ec); + void on_read(beast::error_code ec, std::size_t bytes_transferred); + void on_write(beast::error_code ec, std::size_t bytes_transferred); + +public: + websocket_session( + tcp::socket&& socket, + boost::shared_ptr<shared_state> const& state); + + ~websocket_session(); + + template<class Body, class Allocator> + void + run(http::request<Body, http::basic_fields<Allocator>> req); + + // Send a message + void + send(boost::shared_ptr<std::string const> const& ss); + +private: + void + on_send(boost::shared_ptr<std::string const> const& ss); +}; + +template<class Body, class Allocator> +void +websocket_session:: +run(http::request<Body, http::basic_fields<Allocator>> req) +{ + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-chat-multi"); + })); + + // Accept the websocket handshake + ws_.async_accept( + req, + beast::bind_front_handler( + &websocket_session::on_accept, + shared_from_this())); +} + +#endif diff --git a/src/boost/libs/beast/example/websocket/server/coro-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/coro-ssl/CMakeLists.txt new file mode 100644 index 000000000..a302a2f56 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro-ssl/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/server/coro-ssl "/") + + add_executable (websocket-server-coro-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_coro_ssl.cpp + ) + + set_property(TARGET websocket-server-coro-ssl PROPERTY FOLDER "example-websocket-server") + + target_link_libraries (websocket-server-coro-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/server/coro-ssl/Jamfile b/src/boost/libs/beast/example/websocket/server/coro-ssl/Jamfile new file mode 100644 index 000000000..621b0ce58 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro-ssl/Jamfile @@ -0,0 +1,23 @@ +# +# 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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-server-coro-ssl : + websocket_server_coro_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + <library>/boost/coroutine//boost_coroutine + ; diff --git a/src/boost/libs/beast/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp b/src/boost/libs/beast/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp new file mode 100644 index 000000000..3e1785f00 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp @@ -0,0 +1,206 @@ +// +// 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 server, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/spawn.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +void +do_session( + websocket::stream< + beast::ssl_stream<beast::tcp_stream>>& ws, + net::yield_context yield) +{ + beast::error_code ec; + + // Set the timeout. + beast::get_lowest_layer(ws).expires_after(std::chrono::seconds(30)); + + // Perform the SSL handshake + ws.next_layer().async_handshake(ssl::stream_base::server, yield[ec]); + if(ec) + return fail(ec, "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::server)); + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-coro-ssl"); + })); + + // Accept the websocket handshake + ws.async_accept(yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message + ws.async_read(buffer, yield[ec]); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + break; + + if(ec) + return fail(ec, "read"); + + // Echo the message back + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + net::io_context& ioc, + ssl::context& ctx, + tcp::endpoint endpoint, + net::yield_context yield) +{ + beast::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ioc); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Allow address reuse + acceptor.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + return fail(ec, "set_option"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(net::socket_base::max_listen_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ioc); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_executor(), + std::bind( + &do_session, + websocket::stream<beast::ssl_stream< + beast::tcp_stream>>(std::move(socket), ctx), + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-coro-ssl <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-coro-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::tlsv12}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Spawn a listening port + boost::asio::spawn(ioc, + std::bind( + &do_listen, + std::ref(ioc), + std::ref(ctx), + tcp::endpoint{address, port}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/coro/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/coro/CMakeLists.txt new file mode 100644 index 000000000..c8e408d1c --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/coro "/") + +add_executable (websocket-server-coro + ${BOOST_BEAST_FILES} + Jamfile + websocket_server_coro.cpp +) + +target_link_libraries(websocket-server-coro + lib-asio + lib-beast) + +set_property(TARGET websocket-server-coro PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/coro/Jamfile b/src/boost/libs/beast/example/websocket/server/coro/Jamfile new file mode 100644 index 000000000..5e8cbf948 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro/Jamfile @@ -0,0 +1,16 @@ +# +# 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 +# + +exe websocket-server-coro : + websocket_server_coro.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + <library>/boost/coroutine//boost_coroutine + ; diff --git a/src/boost/libs/beast/example/websocket/server/coro/websocket_server_coro.cpp b/src/boost/libs/beast/example/websocket/server/coro/websocket_server_coro.cpp new file mode 100644 index 000000000..e15b01079 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/coro/websocket_server_coro.cpp @@ -0,0 +1,180 @@ +// +// 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 server, coroutine +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/spawn.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +void +do_session( + websocket::stream<beast::tcp_stream>& ws, + net::yield_context yield) +{ + beast::error_code ec; + + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-coro"); + })); + + // Accept the websocket handshake + ws.async_accept(yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message + ws.async_read(buffer, yield[ec]); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + break; + + if(ec) + return fail(ec, "read"); + + // Echo the message back + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + net::io_context& ioc, + tcp::endpoint endpoint, + net::yield_context yield) +{ + beast::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ioc); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Allow address reuse + acceptor.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + return fail(ec, "set_option"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(net::socket_base::max_listen_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ioc); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_executor(), + std::bind( + &do_session, + websocket::stream< + beast::tcp_stream>(std::move(socket)), + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-coro <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-coro 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc(threads); + + // Spawn a listening port + boost::asio::spawn(ioc, + std::bind( + &do_listen, + std::ref(ioc), + tcp::endpoint{address, port}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/fast/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/fast/CMakeLists.txt new file mode 100644 index 000000000..93d469e91 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/fast/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/fast "/") + +add_executable (websocket-server-fast + ${BOOST_BEAST_FILES} + Jamfile + websocket_server_fast.cpp +) + +target_link_libraries(websocket-server-fast + lib-asio + lib-beast) + +set_property(TARGET websocket-server-fast PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/fast/Jamfile b/src/boost/libs/beast/example/websocket/server/fast/Jamfile new file mode 100644 index 000000000..3e6cbf5b0 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/fast/Jamfile @@ -0,0 +1,16 @@ +# +# 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 +# + +exe websocket-server-fast : + websocket_server_fast.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + <library>/boost/coroutine//boost_coroutine + ; diff --git a/src/boost/libs/beast/example/websocket/server/fast/websocket_server_fast.cpp b/src/boost/libs/beast/example/websocket/server/fast/websocket_server_fast.cpp new file mode 100644 index 000000000..7f2a5e3cb --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/fast/websocket_server_fast.cpp @@ -0,0 +1,480 @@ +// +// 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 server, fast +// +//------------------------------------------------------------------------------ + +/* This server contains the following ports: + + Synchronous <base port + 0> + Asynchronous <base port + 1> + Coroutine <base port + 2> + + This program is optimized for the Autobahn|Testsuite + benchmarking and WebSocket compliants testing program. + + See: + https://github.com/crossbario/autobahn-testsuite +*/ + +#include <boost/beast/core.hpp> +#include <boost/beast/http.hpp> +#include <boost/beast/version.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/spawn.hpp> +#include <boost/asio/strand.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << (std::string(what) + ": " + ec.message() + "\n"); +} + +// Adjust settings on the stream +template<class NextLayer> +void +setup_stream(websocket::stream<NextLayer>& ws) +{ + // These values are tuned for Autobahn|Testsuite, and + // should also be generally helpful for increased performance. + + websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + ws.set_option(pmd); + + ws.auto_fragment(false); + + // Autobahn|Testsuite needs this + ws.read_message_max(64 * 1024 * 1024); +} + +//------------------------------------------------------------------------------ + +void +do_sync_session(websocket::stream<beast::tcp_stream>& ws) +{ + beast::error_code ec; + + setup_stream(ws); + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, std::string( + BOOST_BEAST_VERSION_STRING) + "-Sync"); + })); + + ws.accept(ec); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + beast::flat_buffer buffer; + + ws.read(buffer, ec); + if(ec == websocket::error::closed) + break; + if(ec) + return fail(ec, "read"); + ws.text(ws.got_text()); + ws.write(buffer.data(), ec); + if(ec) + return fail(ec, "write"); + } +} + +void +do_sync_listen( + net::io_context& ioc, + tcp::endpoint endpoint) +{ + beast::error_code ec; + tcp::acceptor acceptor{ioc, endpoint}; + for(;;) + { + tcp::socket socket{ioc}; + + acceptor.accept(socket, ec); + if(ec) + return fail(ec, "accept"); + + std::thread(std::bind( + &do_sync_session, + websocket::stream<beast::tcp_stream>( + std::move(socket)))).detach(); + } +} + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +class async_session : public std::enable_shared_from_this<async_session> +{ + websocket::stream<beast::tcp_stream> ws_; + beast::flat_buffer buffer_; + +public: + // Take ownership of the socket + explicit + async_session(tcp::socket&& socket) + : ws_(std::move(socket)) + { + setup_stream(ws_); + } + + // Start the asynchronous operation + void + run() + { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, std::string( + BOOST_BEAST_VERSION_STRING) + "-Async"); + })); + + // Accept the websocket handshake + ws_.async_accept( + beast::bind_front_handler( + &async_session::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Read a message into our buffer + ws_.async_read( + buffer_, + beast::bind_front_handler( + &async_session::on_read, + shared_from_this())); + } + + void + on_read( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + // This indicates that the async_session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + beast::bind_front_handler( + &async_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"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +// Accepts incoming connections and launches the sessions +class async_listener : public std::enable_shared_from_this<async_listener> +{ + net::io_context& ioc_; + tcp::acceptor acceptor_; + +public: + async_listener( + net::io_context& ioc, + tcp::endpoint endpoint) + : ioc_(ioc) + , acceptor_(net::make_strand(ioc)) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + do_accept(); + } + +private: + void + do_accept() + { + // The new connection gets its own strand + acceptor_.async_accept( + net::make_strand(ioc_), + beast::bind_front_handler( + &async_listener::on_accept, + shared_from_this())); + } + + void + on_accept(beast::error_code ec, tcp::socket socket) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the async_session and run it + std::make_shared<async_session>(std::move(socket))->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +void +do_coro_session( + websocket::stream<beast::tcp_stream>& ws, + net::yield_context yield) +{ + beast::error_code ec; + + setup_stream(ws); + + // Set suggested timeout settings for the websocket + ws.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, std::string( + BOOST_BEAST_VERSION_STRING) + "-Fiber"); + })); + + ws.async_accept(yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + beast::flat_buffer buffer; + + ws.async_read(buffer, yield[ec]); + if(ec == websocket::error::closed) + break; + if(ec) + return fail(ec, "read"); + + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +void +do_coro_listen( + net::io_context& ioc, + tcp::endpoint endpoint, + net::yield_context yield) +{ + beast::error_code ec; + + tcp::acceptor acceptor(ioc); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + acceptor.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + return fail(ec, "set_option"); + + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + acceptor.listen(net::socket_base::max_listen_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ioc); + + acceptor.async_accept(socket, yield[ec]); + if(ec) + { + fail(ec, "accept"); + continue; + } + + boost::asio::spawn( + acceptor.get_executor(), + std::bind( + &do_coro_session, + websocket::stream< + beast::tcp_stream>(std::move(socket)), + std::placeholders::_1)); + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-fast <address> <starting-port> <threads>\n" << + "Example:\n" + " websocket-server-fast 0.0.0.0 8080 1\n" + " Connect to:\n" + " starting-port+0 for synchronous,\n" + " starting-port+1 for asynchronous,\n" + " starting-port+2 for coroutine.\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // Create sync port + std::thread(beast::bind_front_handler( + &do_sync_listen, + std::ref(ioc), + tcp::endpoint{ + address, + static_cast<unsigned short>(port + 0u)} + )).detach(); + + // Create async port + std::make_shared<async_listener>( + ioc, + tcp::endpoint{ + address, + static_cast<unsigned short>(port + 1u)})->run(); + + // Create coro port + boost::asio::spawn(ioc, + std::bind( + &do_coro_listen, + std::ref(ioc), + tcp::endpoint{ + address, + static_cast<unsigned short>(port + 2u)}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/stackless-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/stackless-ssl/CMakeLists.txt new file mode 100644 index 000000000..7ae66a07b --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless-ssl/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/server/stackless-ssl "/") + + add_executable (websocket-server-stackless-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_stackless_ssl.cpp + ) + + set_property(TARGET websocket-server-stackless-ssl PROPERTY FOLDER "example-websocket-server") + + target_link_libraries (websocket-server-stackless-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/server/stackless-ssl/Jamfile b/src/boost/libs/beast/example/websocket/server/stackless-ssl/Jamfile new file mode 100644 index 000000000..b0eaa4aa2 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless-ssl/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-server-stackless-ssl : + websocket_server_stackless_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp b/src/boost/libs/beast/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp new file mode 100644 index 000000000..c6eaec60c --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp @@ -0,0 +1,314 @@ +// +// 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 server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/coroutine.hpp> +#include <boost/asio/strand.hpp> +#include <boost/asio/dispatch.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this<session> +{ + websocket::stream<beast::ssl_stream<beast::tcp_stream>> ws_; + beast::flat_buffer buffer_; + +public: + // Take ownership of the socket + session(tcp::socket&& socket, ssl::context& ctx) + : ws_(std::move(socket), ctx) + { + } + + // Start the asynchronous operation + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch(ws_.get_executor(), + beast::bind_front_handler(&session::loop, + shared_from_this(), + beast::error_code{}, + 0)); + } + + #include <boost/asio/yield.hpp> + + void + loop( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + reenter(*this) + { + // Set the timeout. + beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30)); + + // Perform the SSL handshake + yield ws_.next_layer().async_handshake( + ssl::stream_base::server, + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + 0)); + if(ec) + return fail(ec, "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::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-stackless-ssl"); + })); + + // Accept the websocket handshake + yield ws_.async_accept( + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + 0)); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // Read a message into our buffer + yield ws_.async_read( + buffer_, + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + if(ec == websocket::error::closed) + { + // This indicates that the session was closed + return; + } + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + yield ws_.async_write( + buffer_.data(), + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + } + } + } + + #include <boost/asio/unyield.hpp> +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this<listener> +{ + net::io_context& ioc_; + ssl::context& ctx_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + net::io_context& ioc, + ssl::context& ctx, + tcp::endpoint endpoint) + : ioc_(ioc) + , ctx_(ctx) + , acceptor_(ioc) + , socket_(ioc) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + loop(); + } + +private: + + #include <boost/asio/yield.hpp> + + void + loop(beast::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared<session>(std::move(socket_), ctx_)->run(); + } + + // Make sure each session gets its own strand + socket_ = tcp::socket(net::make_strand(ioc_)); + } + } + } + + #include <boost/asio/unyield.hpp> +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async-ssl <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-async-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::tlsv12}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared<listener>(ioc, ctx, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/stackless/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/stackless/CMakeLists.txt new file mode 100644 index 000000000..b260ca48b --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/stackless "/") + +add_executable (websocket-server-stackless + ${BOOST_BEAST_FILES} + Jamfile + websocket_server_stackless.cpp +) + +target_link_libraries(websocket-server-stackless + lib-asio + lib-beast) + +set_property(TARGET websocket-server-stackless PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/stackless/Jamfile b/src/boost/libs/beast/example/websocket/server/stackless/Jamfile new file mode 100644 index 000000000..ce9ab8987 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless/Jamfile @@ -0,0 +1,15 @@ +# +# 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 +# + +exe websocket-server-stackless : + websocket_server_stackless.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/stackless/websocket_server_stackless.cpp b/src/boost/libs/beast/example/websocket/server/stackless/websocket_server_stackless.cpp new file mode 100644 index 000000000..c57fe2899 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/stackless/websocket_server_stackless.cpp @@ -0,0 +1,282 @@ +// +// 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 server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/coroutine.hpp> +#include <boost/asio/strand.hpp> +#include <boost/asio/dispatch.hpp> +#include <algorithm> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <memory> +#include <string> +#include <thread> +#include <vector> + +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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this<session> +{ + websocket::stream<beast::tcp_stream> ws_; + beast::flat_buffer buffer_; + +public: + // Take ownership of the socket + explicit + session(tcp::socket socket) + : ws_(std::move(socket)) + { + } + + // Start the asynchronous operation + void + run() + { + // We need to be executing within a strand to perform async operations + // on the I/O objects in this session. Although not strictly necessary + // for single-threaded contexts, this example code is written to be + // thread-safe by default. + net::dispatch(ws_.get_executor(), + beast::bind_front_handler(&session::loop, + shared_from_this(), + beast::error_code{}, + 0)); + } + + #include <boost/asio/yield.hpp> + + void + loop( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + reenter(*this) + { + // Set suggested timeout settings for the websocket + ws_.set_option( + websocket::stream_base::timeout::suggested( + beast::role_type::server)); + + // Set a decorator to change the Server of the handshake + ws_.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-stackless"); + })); + + // Accept the websocket handshake + yield ws_.async_accept( + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + 0)); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // Read a message into our buffer + yield ws_.async_read( + buffer_, + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + if(ec == websocket::error::closed) + { + // This indicates that the session was closed + return; + } + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + yield ws_.async_write( + buffer_.data(), + std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + } + } + } + + #include <boost/asio/unyield.hpp> +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this<listener> +{ + net::io_context& ioc_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + net::io_context& ioc, + tcp::endpoint endpoint) + : ioc_(ioc) + , acceptor_(net::make_strand(ioc)) + , socket_(net::make_strand(ioc)) + { + beast::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Allow address reuse + acceptor_.set_option(net::socket_base::reuse_address(true), ec); + if(ec) + { + fail(ec, "set_option"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + net::socket_base::max_listen_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + loop(); + } + +private: + + #include <boost/asio/yield.hpp> + + void + loop(beast::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared<session>(std::move(socket_))->run(); + } + + // Make sure each session gets its own strand + socket_ = tcp::socket(net::make_strand(ioc_)); + } + } + } + + #include <boost/asio/unyield.hpp> +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-stackless <address> <port> <threads>\n" << + "Example:\n" << + " websocket-server-stackless 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + auto const threads = std::max<int>(1, std::atoi(argv[3])); + + // The io_context is required for all I/O + net::io_context ioc{threads}; + + // Create and launch a listening port + std::make_shared<listener>(ioc, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector<std::thread> v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ioc] + { + ioc.run(); + }); + ioc.run(); + + return EXIT_SUCCESS; +} diff --git a/src/boost/libs/beast/example/websocket/server/sync-ssl/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/sync-ssl/CMakeLists.txt new file mode 100644 index 000000000..0ad254ce5 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync-ssl/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/server/sync-ssl "/") + + add_executable (websocket-server-sync-ssl + ${BOOST_BEAST_FILES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_sync_ssl.cpp + ) + + set_property(TARGET websocket-server-sync-ssl PROPERTY FOLDER "example-websocket-server") + + target_link_libraries (websocket-server-sync-ssl + OpenSSL::SSL OpenSSL::Crypto + lib-asio + lib-asio-ssl + lib-beast + ) + +endif() diff --git a/src/boost/libs/beast/example/websocket/server/sync-ssl/Jamfile b/src/boost/libs/beast/example/websocket/server/sync-ssl/Jamfile new file mode 100644 index 000000000..55ac35b84 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync-ssl/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 : <library>/boost/beast//lib-asio-ssl/<link>static : <build>no ] + ; + +exe websocket-server-sync-ssl : + websocket_server_sync_ssl.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp b/src/boost/libs/beast/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp new file mode 100644 index 000000000..5a7b4dc06 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp @@ -0,0 +1,137 @@ +// +// 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 server, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include <boost/beast/core.hpp> +#include <boost/beast/ssl.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/beast/websocket/ssl.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <boost/asio/ssl/stream.hpp> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <string> +#include <thread> + +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> + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +void +do_session(tcp::socket& socket, ssl::context& ctx) +{ + try + { + // Construct the websocket stream around the socket + websocket::stream<beast::ssl_stream<tcp::socket&>> ws{socket, ctx}; + + // Perform the SSL handshake + ws.next_layer().handshake(ssl::stream_base::server); + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-sync-ssl"); + })); + + // Accept the websocket handshake + ws.accept(); + + for(;;) + { + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message + ws.read(buffer); + + // Echo the message back + ws.text(ws.got_text()); + ws.write(buffer.data()); + } + } + catch(beast::system_error const& se) + { + // This indicates that the session was closed + if(se.code() != websocket::error::closed) + std::cerr << "Error: " << se.code().message() << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 3) + { + std::cerr << + "Usage: websocket-server-sync-ssl <address> <port>\n" << + "Example:\n" << + " websocket-server-sync-ssl 0.0.0.0 8080\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + + // The io_context is required for all I/O + net::io_context ioc{1}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::tlsv12}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ioc, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ioc}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket), + std::ref(ctx))}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/src/boost/libs/beast/example/websocket/server/sync/CMakeLists.txt b/src/boost/libs/beast/example/websocket/server/sync/CMakeLists.txt new file mode 100644 index 000000000..486998615 --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# 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 +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/sync "/") + +add_executable (websocket-server-sync + ${BOOST_BEAST_FILES} + Jamfile + websocket_server_sync.cpp +) + +target_link_libraries(websocket-server-sync + lib-asio + lib-beast) + +set_property(TARGET websocket-server-sync PROPERTY FOLDER "example-websocket-server") diff --git a/src/boost/libs/beast/example/websocket/server/sync/Jamfile b/src/boost/libs/beast/example/websocket/server/sync/Jamfile new file mode 100644 index 000000000..7fd14ce4a --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync/Jamfile @@ -0,0 +1,15 @@ +# +# 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 +# + +exe websocket-server-sync : + websocket_server_sync.cpp + : + <variant>coverage:<build>no + <variant>ubasan:<build>no + ; diff --git a/src/boost/libs/beast/example/websocket/server/sync/websocket_server_sync.cpp b/src/boost/libs/beast/example/websocket/server/sync/websocket_server_sync.cpp new file mode 100644 index 000000000..2f75c468e --- /dev/null +++ b/src/boost/libs/beast/example/websocket/server/sync/websocket_server_sync.cpp @@ -0,0 +1,121 @@ +// +// 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 server, synchronous +// +//------------------------------------------------------------------------------ + +#include <boost/beast/core.hpp> +#include <boost/beast/websocket.hpp> +#include <boost/asio/ip/tcp.hpp> +#include <cstdlib> +#include <functional> +#include <iostream> +#include <string> +#include <thread> + +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> +using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +void +do_session(tcp::socket& socket) +{ + try + { + // Construct the stream by moving in the socket + websocket::stream<tcp::socket> ws{std::move(socket)}; + + // Set a decorator to change the Server of the handshake + ws.set_option(websocket::stream_base::decorator( + [](websocket::response_type& res) + { + res.set(http::field::server, + std::string(BOOST_BEAST_VERSION_STRING) + + " websocket-server-sync"); + })); + + // Accept the websocket handshake + ws.accept(); + + for(;;) + { + // This buffer will hold the incoming message + beast::flat_buffer buffer; + + // Read a message + ws.read(buffer); + + // Echo the message back + ws.text(ws.got_text()); + ws.write(buffer.data()); + } + } + catch(beast::system_error const& se) + { + // This indicates that the session was closed + if(se.code() != websocket::error::closed) + std::cerr << "Error: " << se.code().message() << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 3) + { + std::cerr << + "Usage: websocket-server-sync <address> <port>\n" << + "Example:\n" << + " websocket-server-sync 0.0.0.0 8080\n"; + return EXIT_FAILURE; + } + auto const address = net::ip::make_address(argv[1]); + auto const port = static_cast<unsigned short>(std::atoi(argv[2])); + + // The io_context is required for all I/O + net::io_context ioc{1}; + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ioc, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ioc}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket))}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} |