2017-01-30 09:53:10 -05:00
|
|
|
//
|
2017-02-06 20:07:03 -05:00
|
|
|
// Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com)
|
2017-01-30 09:53:10 -05:00
|
|
|
//
|
|
|
|
|
// 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)
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
#ifndef WEBSOCKET_SYNC_ECHO_SERVER_HPP
|
|
|
|
|
#define WEBSOCKET_SYNC_ECHO_SERVER_HPP
|
|
|
|
|
|
2017-05-04 15:40:07 -07:00
|
|
|
#include <beast/core/multi_buffer.hpp>
|
2017-01-30 09:53:10 -05:00
|
|
|
#include <beast/websocket.hpp>
|
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
|
#include <boost/optional.hpp>
|
|
|
|
|
#include <atomic>
|
|
|
|
|
#include <functional>
|
|
|
|
|
#include <memory>
|
|
|
|
|
#include <mutex>
|
|
|
|
|
#include <ostream>
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <thread>
|
|
|
|
|
#include <type_traits>
|
|
|
|
|
#include <typeindex>
|
|
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
|
|
namespace websocket {
|
|
|
|
|
|
|
|
|
|
/** Synchronous WebSocket echo client/server
|
|
|
|
|
*/
|
|
|
|
|
class sync_echo_server
|
|
|
|
|
{
|
|
|
|
|
public:
|
|
|
|
|
using error_code = beast::error_code;
|
|
|
|
|
using endpoint_type = boost::asio::ip::tcp::endpoint;
|
|
|
|
|
using address_type = boost::asio::ip::address;
|
|
|
|
|
using socket_type = boost::asio::ip::tcp::socket;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
/** A container of type-erased option setters.
|
|
|
|
|
*/
|
|
|
|
|
template<class NextLayer>
|
|
|
|
|
class options_set
|
|
|
|
|
{
|
|
|
|
|
// workaround for std::function bug in msvc
|
|
|
|
|
struct callable
|
|
|
|
|
{
|
|
|
|
|
virtual ~callable() = default;
|
|
|
|
|
virtual void operator()(
|
|
|
|
|
beast::websocket::stream<NextLayer>&) = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<class T>
|
|
|
|
|
class callable_impl : public callable
|
|
|
|
|
{
|
|
|
|
|
T t_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
template<class U>
|
|
|
|
|
callable_impl(U&& u)
|
|
|
|
|
: t_(std::forward<U>(u))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
operator()(beast::websocket::stream<NextLayer>& ws)
|
|
|
|
|
{
|
|
|
|
|
t_(ws);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
template<class Opt>
|
|
|
|
|
class lambda
|
|
|
|
|
{
|
|
|
|
|
Opt opt_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
lambda(lambda&&) = default;
|
|
|
|
|
lambda(lambda const&) = default;
|
|
|
|
|
|
|
|
|
|
lambda(Opt const& opt)
|
|
|
|
|
: opt_(opt)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
operator()(beast::websocket::stream<NextLayer>& ws) const
|
|
|
|
|
{
|
|
|
|
|
ws.set_option(opt_);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::unordered_map<std::type_index,
|
|
|
|
|
std::unique_ptr<callable>> list_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
template<class Opt>
|
|
|
|
|
void
|
|
|
|
|
set_option(Opt const& opt)
|
|
|
|
|
{
|
|
|
|
|
std::unique_ptr<callable> p;
|
|
|
|
|
p.reset(new callable_impl<lambda<Opt>>{opt});
|
|
|
|
|
list_[std::type_index{
|
|
|
|
|
typeid(Opt)}] = std::move(p);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
set_options(beast::websocket::stream<NextLayer>& ws)
|
|
|
|
|
{
|
|
|
|
|
for(auto const& op : list_)
|
|
|
|
|
(*op.second)(ws);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
std::ostream* log_;
|
|
|
|
|
boost::asio::io_service ios_;
|
|
|
|
|
socket_type sock_;
|
|
|
|
|
endpoint_type ep_;
|
|
|
|
|
boost::asio::ip::tcp::acceptor acceptor_;
|
|
|
|
|
std::thread thread_;
|
|
|
|
|
options_set<socket_type> opts_;
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
/** Constructor.
|
|
|
|
|
|
|
|
|
|
@param log A pointer to a stream to log to, or `nullptr`
|
|
|
|
|
to disable logging.
|
|
|
|
|
*/
|
|
|
|
|
sync_echo_server(std::ostream* log)
|
|
|
|
|
: log_(log)
|
|
|
|
|
, sock_(ios_)
|
|
|
|
|
, acceptor_(ios_)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Destructor.
|
|
|
|
|
*/
|
|
|
|
|
~sync_echo_server()
|
|
|
|
|
{
|
|
|
|
|
if(thread_.joinable())
|
|
|
|
|
{
|
|
|
|
|
error_code ec;
|
|
|
|
|
ios_.dispatch(
|
|
|
|
|
[&]{ acceptor_.close(ec); });
|
|
|
|
|
thread_.join();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Return the listening endpoint.
|
|
|
|
|
*/
|
|
|
|
|
endpoint_type
|
|
|
|
|
local_endpoint() const
|
|
|
|
|
{
|
|
|
|
|
return acceptor_.local_endpoint();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Set a websocket option.
|
|
|
|
|
|
|
|
|
|
The option will be applied to all new connections.
|
|
|
|
|
|
|
|
|
|
@param opt The option to apply.
|
|
|
|
|
*/
|
|
|
|
|
template<class Opt>
|
|
|
|
|
void
|
|
|
|
|
set_option(Opt const& opt)
|
|
|
|
|
{
|
|
|
|
|
opts_.set_option(opt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Open a listening port.
|
|
|
|
|
|
|
|
|
|
@param ep The address and port to bind to.
|
|
|
|
|
|
|
|
|
|
@param ec Set to the error, if any occurred.
|
|
|
|
|
*/
|
|
|
|
|
void
|
|
|
|
|
open(endpoint_type const& ep, error_code& ec)
|
|
|
|
|
{
|
|
|
|
|
acceptor_.open(ep.protocol(), ec);
|
|
|
|
|
if(ec)
|
|
|
|
|
return fail("open", ec);
|
|
|
|
|
acceptor_.set_option(
|
|
|
|
|
boost::asio::socket_base::reuse_address{true});
|
|
|
|
|
acceptor_.bind(ep, ec);
|
|
|
|
|
if(ec)
|
|
|
|
|
return fail("bind", ec);
|
|
|
|
|
acceptor_.listen(
|
|
|
|
|
boost::asio::socket_base::max_connections, ec);
|
|
|
|
|
if(ec)
|
|
|
|
|
return fail("listen", ec);
|
|
|
|
|
acceptor_.async_accept(sock_, ep_,
|
|
|
|
|
std::bind(&sync_echo_server::on_accept, this,
|
2017-05-10 13:08:11 -07:00
|
|
|
std::placeholders::_1));
|
2017-01-30 09:53:10 -05:00
|
|
|
thread_ = std::thread{[&]{ ios_.run(); }};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
void
|
|
|
|
|
fail(std::string what, error_code ec)
|
|
|
|
|
{
|
|
|
|
|
if(log_)
|
|
|
|
|
{
|
|
|
|
|
static std::mutex m;
|
|
|
|
|
std::lock_guard<std::mutex> lock{m};
|
|
|
|
|
(*log_) << what << ": " <<
|
|
|
|
|
ec.message() << std::endl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
fail(std::string what, error_code ec,
|
|
|
|
|
int id, endpoint_type const& ep)
|
|
|
|
|
{
|
|
|
|
|
if(log_)
|
|
|
|
|
if(ec != beast::websocket::error::closed)
|
|
|
|
|
fail("[#" + std::to_string(id) + " " +
|
|
|
|
|
boost::lexical_cast<std::string>(ep) +
|
|
|
|
|
"] " + what, ec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
on_accept(error_code ec)
|
|
|
|
|
{
|
|
|
|
|
if(ec == boost::asio::error::operation_aborted)
|
|
|
|
|
return;
|
|
|
|
|
if(ec)
|
|
|
|
|
return fail("accept", ec);
|
|
|
|
|
struct lambda
|
|
|
|
|
{
|
|
|
|
|
std::size_t id;
|
|
|
|
|
endpoint_type ep;
|
|
|
|
|
sync_echo_server& self;
|
|
|
|
|
boost::asio::io_service::work work;
|
|
|
|
|
// Must be destroyed before work otherwise the
|
|
|
|
|
// io_service could be destroyed before the socket.
|
|
|
|
|
socket_type sock;
|
|
|
|
|
|
|
|
|
|
lambda(sync_echo_server& self_,
|
|
|
|
|
endpoint_type const& ep_,
|
|
|
|
|
socket_type&& sock_)
|
|
|
|
|
: id([]
|
|
|
|
|
{
|
|
|
|
|
static std::atomic<std::size_t> n{0};
|
|
|
|
|
return ++n;
|
|
|
|
|
}())
|
|
|
|
|
, ep(ep_)
|
|
|
|
|
, self(self_)
|
|
|
|
|
, work(sock_.get_io_service())
|
|
|
|
|
, sock(std::move(sock_))
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void operator()()
|
|
|
|
|
{
|
|
|
|
|
self.do_peer(id, ep, std::move(sock));
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
std::thread{lambda{*this, ep_, std::move(sock_)}}.detach();
|
|
|
|
|
acceptor_.async_accept(sock_, ep_,
|
|
|
|
|
std::bind(&sync_echo_server::on_accept, this,
|
2017-05-10 13:08:11 -07:00
|
|
|
std::placeholders::_1));
|
2017-01-30 09:53:10 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
do_peer(std::size_t id,
|
|
|
|
|
endpoint_type const& ep, socket_type&& sock)
|
|
|
|
|
{
|
|
|
|
|
using boost::asio::buffer;
|
|
|
|
|
using boost::asio::buffer_copy;
|
|
|
|
|
beast::websocket::stream<
|
|
|
|
|
socket_type> ws{std::move(sock)};
|
|
|
|
|
opts_.set_options(ws);
|
|
|
|
|
error_code ec;
|
Refactor websocket decorators (API Change):
fix #80, #212, fix #303, fix #314, fix #317
websocket::stream now provides the following families of
functions for performing handshakes:
When operating in the server role:
* stream::accept
* stream::accept_ex
* stream::async_accept
* stream::async_accept_ex
When operating in the client role:
* stream::handshake
* stream::handshake_ex
* stream::async_handshake
* stream::async_handshake_ex
Member functions ending with "_ex" allow an additional
RequestDecorator parameter (for the accept family of
functions) or ResponseDecorator parameter (for the
handshake family of functions).
The decorator is called to optionally modify the contents
of the HTTP request or HTTP response object generated by
the implementation, before the message is sent. This
permits callers to set the User-Agent or Server fields,
add or modify HTTP fields related to subprotocols, or
perform any required transformation of the HTTP message
for application-specific needs.
The handshake() family of functions now have an additional
set of overloads accepting a parameter of type response_type&,
allowing the caller to receive the HTTP Response to the
Upgrade handshake. This permits inspection of the response
to handle things like subprotocols, authentication, or
other application-specific needs.
The new implementation does not require any state to be
stored in the stream object. Therefore, websocket::stream
objects are now smaller in size.
The overload of set_option for setting a decorator on the
stream is removed. The only way to set decorators now is
with a suitable overload of accept or handshake.
2017-04-25 09:35:22 -07:00
|
|
|
ws.accept_ex(
|
|
|
|
|
[](beast::websocket::response_type& res)
|
|
|
|
|
{
|
2017-06-05 19:28:17 -07:00
|
|
|
res.insert(
|
Refactor websocket decorators (API Change):
fix #80, #212, fix #303, fix #314, fix #317
websocket::stream now provides the following families of
functions for performing handshakes:
When operating in the server role:
* stream::accept
* stream::accept_ex
* stream::async_accept
* stream::async_accept_ex
When operating in the client role:
* stream::handshake
* stream::handshake_ex
* stream::async_handshake
* stream::async_handshake_ex
Member functions ending with "_ex" allow an additional
RequestDecorator parameter (for the accept family of
functions) or ResponseDecorator parameter (for the
handshake family of functions).
The decorator is called to optionally modify the contents
of the HTTP request or HTTP response object generated by
the implementation, before the message is sent. This
permits callers to set the User-Agent or Server fields,
add or modify HTTP fields related to subprotocols, or
perform any required transformation of the HTTP message
for application-specific needs.
The handshake() family of functions now have an additional
set of overloads accepting a parameter of type response_type&,
allowing the caller to receive the HTTP Response to the
Upgrade handshake. This permits inspection of the response
to handle things like subprotocols, authentication, or
other application-specific needs.
The new implementation does not require any state to be
stored in the stream object. Therefore, websocket::stream
objects are now smaller in size.
The overload of set_option for setting a decorator on the
stream is removed. The only way to set decorators now is
with a suitable overload of accept or handshake.
2017-04-25 09:35:22 -07:00
|
|
|
"Server", "sync_echo_server");
|
|
|
|
|
},
|
|
|
|
|
ec);
|
2017-01-30 09:53:10 -05:00
|
|
|
if(ec)
|
|
|
|
|
{
|
|
|
|
|
fail("accept", ec, id, ep);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
for(;;)
|
|
|
|
|
{
|
|
|
|
|
beast::websocket::opcode op;
|
2017-05-04 15:40:07 -07:00
|
|
|
beast::multi_buffer b;
|
|
|
|
|
ws.read(op, b, ec);
|
2017-01-30 09:53:10 -05:00
|
|
|
if(ec)
|
|
|
|
|
{
|
|
|
|
|
auto const s = ec.message();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
ws.set_option(beast::websocket::message_type{op});
|
2017-05-04 15:40:07 -07:00
|
|
|
ws.write(b.data(), ec);
|
2017-01-30 09:53:10 -05:00
|
|
|
if(ec)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if(ec && ec != beast::websocket::error::closed)
|
|
|
|
|
{
|
|
|
|
|
fail("read", ec, id, ep);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
} // websocket
|
|
|
|
|
|
|
|
|
|
#endif
|