summaryrefslogtreecommitdiffstats
path: root/src/boost/libs/beast/example/websocket/server/chat-multi
diff options
context:
space:
mode:
Diffstat (limited to 'src/boost/libs/beast/example/websocket/server/chat-multi')
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/CMakeLists.txt41
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/Jamfile19
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/beast.hpp19
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/chat_client.html60
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/http_session.cpp371
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/http_session.hpp48
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/listener.cpp101
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/listener.hpp42
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/main.cpp84
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/net.hpp18
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.cpp59
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/shared_state.hpp48
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.cpp135
-rw-r--r--src/boost/libs/beast/example/websocket/server/chat-multi/websocket_session.hpp86
14 files changed, 1131 insertions, 0 deletions
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