example/cppcon2018 is removed:

* Replaced by websocket-chat-multi
This commit is contained in:
Vinnie Falco
2019-02-16 04:16:55 -08:00
parent ca1b620527
commit 22dcb4a3cb
19 changed files with 4 additions and 1073 deletions

View File

@ -7,6 +7,7 @@ Version 215:
* Add experimental test/handler.hpp
* Rename to async_op_base::invoke_now
* Add async_op_base::invoke
* Remove CppCon2018 example
--------------------------------------------------------------------------------

View File

@ -215,9 +215,9 @@ and illustrate the implementation of advanced features.
This talk was given at [@https://cppcon.org CppCon 2018]. In this
presentation, we develop a multi-user chat server written in C++ using
Beast WebSocket, which uses a provided chat client written in HTML and
JavaScript. The source files for this example are located at
[source_file example/cppcon2018].
JavaScript. An improved, multi-threaded version of this program is
included here
[source_file example/websocket/server/chat-multi].
[block'''
<mediaobject>

View File

@ -8,7 +8,6 @@
#
add_subdirectory (advanced)
add_subdirectory (cppcon2018)
add_subdirectory (http)
add_subdirectory (websocket)

View File

@ -8,7 +8,6 @@
#
build-project advanced ;
build-project cppcon2018 ;
build-project http ;
build-project websocket ;

View File

@ -1,37 +0,0 @@
#
# 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/cppcon2018 "/")
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-server
${APP_FILES}
${BOOST_BEAST_FILES}
)
set_property(TARGET websocket-chat-server PROPERTY FOLDER "example-cppcon2018")

View File

@ -1,19 +0,0 @@
#
# 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-server :
http_session.cpp
listener.cpp
main.cpp
shared_state.cpp
websocket_session.cpp
:
<variant>coverage:<build>no
<variant>ubasan:<build>no
;

View File

@ -1,23 +0,0 @@
*This repository contains the presentation file and compiling
source code for the CppCon2018 talk.*
# Get Rich Quick! Using Boost.Beast WebSockets and Networking TS
Do you want to make a lot of money? You'll see some examples of free
browser and server based WebSocket programs which have earned their
respective individual authors tens of millions of dollars in no time
at all. Perhaps after seeing this talk in person, you'll write the
next massively successful WebSocket app!
The WebSocket protocol powers the interactive web by enabling two-way
messaging between the browser and the web server. The Boost.Beast
library implements this protocol on top of the industry standard
Boost.Asio library which models the Networking Technical Specification
proposed for the ISO C++ Standard.
This presentation introduces Networking TS concepts and algorithms,
how to read their requirements, and how to use them in your programs.
We will build from scratch a multi-user chat server in C++11 using
Beast, and the corresponding browser-based chat client in HTML and
JavaScript. No prior knowledge or understanding of Beast or Asio is
required, the talk is suited for everyone.

View File

@ -1,19 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_BEAST_HPP
#define CPPCON2018_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

View File

@ -1,57 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>WebSocket Chat - CppCon2018</title>
</head>
<body>
<h1>WebSocket Chat</h1>
<p>Source code: <a href="https://github.com/vinniefalco/CppCon2018">https://github.com/vinniefalco/CppCon2018</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; 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;
connect.onclick = function() {
ws = new WebSocket(uri.value);
ws.onopen = function(ev) {
messages.innerText += "[connection opened]\n";
};
ws.onclose = function(ev) {
messages.innerText += "[connection closed]\n";
};
ws.onmessage = function(ev) {
messages.innerText += ev.data + "\n";
};
ws.onerror = function(ev) {
messages.innerText += "[error]\n";
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 (event.keyCode === 13) {
send.click();
}
}
</script>
</body>
</html>

View File

@ -1,349 +0,0 @@
//
// Copyright (c) 2018 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);
#if 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));
}
//------------------------------------------------------------------------------
http_session::
http_session(
tcp::socket socket,
std::shared_ptr<shared_state> const& state)
: socket_(std::move(socket))
, state_(state)
{
}
void
http_session::
run()
{
// Read a request
http::async_read(socket_, buffer_, req_,
std::bind(
&http_session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
// 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";
}
template<bool isRequest, class Body, class Fields>
void
http_session::
send_lambda::
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 = std::make_shared<
http::message<isRequest, Body, Fields>>(std::move(msg));
// Write the response
auto self = self_.shared_from_this();
http::async_write(
self_.socket_,
*sp,
[self, sp](beast::error_code ec, std::size_t bytes)
{
self->on_write(ec, bytes, sp->need_eof());
});
}
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)
{
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(req_))
{
// Create a WebSocket session by transferring the socket
std::make_shared<websocket_session>(
std::move(socket_), state_)->run(std::move(req_));
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 = std::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->socket_, *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(this->socket_, *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(),
std::move(req_),
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.
socket_.shutdown(tcp::socket::shutdown_send, ec);
return;
}
// Clear contents of the request message,
// otherwise the read behavior is undefined.
req_ = {};
// Read another request
http::async_read(socket_, buffer_, req_,
std::bind(
&http_session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}

View File

@ -1,55 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_HTTP_SESSION_HPP
#define CPPCON2018_HTTP_SESSION_HPP
#include "net.hpp"
#include "beast.hpp"
#include "shared_state.hpp"
#include <cstdlib>
#include <memory>
/** Represents an established HTTP connection
*/
class http_session : public std::enable_shared_from_this<http_session>
{
tcp::socket socket_;
beast::flat_buffer buffer_;
std::shared_ptr<shared_state> state_;
http::request<http::string_body> req_;
struct 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;
};
void fail(beast::error_code ec, char const* what);
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,
std::shared_ptr<shared_state> const& state);
void run();
};
#endif

View File

@ -1,103 +0,0 @@
//
// Copyright (c) 2018 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,
std::shared_ptr<shared_state> const& state)
: acceptor_(ioc)
, socket_(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()
{
// Start accepting a connection
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}
// 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)
{
if(ec)
return fail(ec, "accept");
else
// Launch a new session for this connection
std::make_shared<http_session>(
std::move(socket_),
state_)->run();
// Accept another connection
acceptor_.async_accept(
socket_,
std::bind(
&listener::on_accept,
shared_from_this(),
std::placeholders::_1));
}

View File

@ -1,41 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_LISTENER_HPP
#define CPPCON2018_LISTENER_HPP
#include "beast.hpp"
#include "net.hpp"
#include <memory>
#include <string>
// Forward declaration
class shared_state;
// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
tcp::acceptor acceptor_;
tcp::socket socket_;
std::shared_ptr<shared_state> state_;
void fail(beast::error_code ec, char const* what);
void on_accept(beast::error_code ec);
public:
listener(
net::io_context& ioc,
tcp::endpoint endpoint,
std::shared_ptr<shared_state> const& state);
// Start accepting incoming connections
void run();
};
#endif

View File

@ -1,65 +0,0 @@
//
// Copyright (c) 2018 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
This implements a multi-user chat room using WebSocket.
*/
//------------------------------------------------------------------------------
#include "listener.hpp"
#include "shared_state.hpp"
#include <boost/asio/signal_set.hpp>
#include <iostream>
int
main(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 4)
{
std::cerr <<
"Usage: websocket-chat-server <address> <port> <doc_root>\n" <<
"Example:\n" <<
" websocket-chat-server 0.0.0.0 8080 .\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];
// The io_context is required for all I/O
net::io_context ioc;
// Create and launch a listening port
std::make_shared<listener>(
ioc,
tcp::endpoint{address, port},
std::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 main thread
ioc.run();
// (If we get here, it means we got a SIGINT or SIGTERM)
return EXIT_SUCCESS;
}

View File

@ -1,18 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_ASIO_HPP
#define CPPCON2018_ASIO_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

View File

@ -1,41 +0,0 @@
//
// Copyright (c) 2018 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)
{
sessions_.insert(&session);
}
void
shared_state::
leave(websocket_session& session)
{
sessions_.erase(&session);
}
void
shared_state::
send(std::string message)
{
auto const ss = std::make_shared<std::string const>(std::move(message));
for(auto session : sessions_)
session->send(ss);
}

View File

@ -1,45 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_SHARED_STATE_HPP
#define CPPCON2018_SHARED_STATE_HPP
#include <memory>
#include <string>
#include <unordered_set>
// Forward declaration
class websocket_session;
// Represents the shared server state
class shared_state
{
std::string doc_root_;
// This simple method of tracking
// sessions only works with an implicit
// strand (i.e. a single-threaded server)
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

View File

@ -1,127 +0,0 @@
//
// Copyright (c) 2018 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,
std::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_,
std::bind(
&websocket_session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
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_,
std::bind(
&websocket_session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
void
websocket_session::
send(std::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()),
std::bind(
&websocket_session::on_write,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
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()),
std::bind(
&websocket_session::on_write,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}

View File

@ -1,69 +0,0 @@
//
// Copyright (c) 2018 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 CPPCON2018_WEBSOCKET_SESSION_HPP
#define CPPCON2018_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 std::enable_shared_from_this<websocket_session>
{
beast::flat_buffer buffer_;
websocket::stream<tcp::socket> ws_;
std::shared_ptr<shared_state> state_;
std::vector<std::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,
std::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(std::shared_ptr<std::string const> const& ss);
};
template<class Body, class Allocator>
void
websocket_session::
run(http::request<Body, http::basic_fields<Allocator>> req)
{
// Accept the websocket handshake
ws_.async_accept(
req,
std::bind(
&websocket_session::on_accept,
shared_from_this(),
std::placeholders::_1));
}
#endif