Add server-framework SSL HTTP and WebSocket ports

This commit is contained in:
Vinnie Falco
2017-06-17 13:42:13 -07:00
parent 74891560ae
commit 65932ee343
19 changed files with 1648 additions and 200 deletions

View File

@ -8,6 +8,7 @@ Version 61:
* Tidy up names in error categories
* Flush the output stream in the example
* Clean close in Secure WebSocket client
* Add server-framework SSL HTTP and WebSocket ports
API Changes:

View File

@ -70,6 +70,13 @@ endif()
find_package(OpenSSL)
if (OPENSSL_FOUND)
add_definitions (-DBEAST_USE_OPENSSL=1)
else()
add_definitions (-DBEAST_USE_OPENSSL=0)
message("OpenSSL not found.")
endif()
#
#-------------------------------------------------------------------------------
@ -182,9 +189,7 @@ add_subdirectory (test)
add_subdirectory (example)
if (NOT OPENSSL_FOUND)
message("OpenSSL not found. Not building SSL tests and examples")
else()
if (OPENSSL_FOUND)
add_subdirectory (example/ssl-http-client)
add_subdirectory (example/ssl-websocket-client)
add_subdirectory (test/websocket/ssl)

View File

@ -113,14 +113,18 @@ for writing their own servers. It serves both HTTP and WebSocket.
* [repo_file example/server-framework/http_async_port.hpp]
* [repo_file example/server-framework/http_base.hpp]
* [repo_file example/server-framework/http_sync_port.hpp]
* [repo_file example/server-framework/https_ports.hpp]
* [repo_file example/server-framework/main.cpp]
* [repo_file example/server-framework/rfc7231.hpp]
* [repo_file example/server-framework/server.hpp]
* [repo_file example/server-framework/service_list.hpp]
* [repo_file example/server-framework/ssl_certificate.hpp]
* [repo_file example/server-framework/ssl_stream.hpp]
* [repo_file example/server-framework/write_msg.hpp]
* [repo_file example/server-framework/ws_async_port.hpp]
* [repo_file example/server-framework/ws_sync_port.hpp]
* [repo_file example/server-framework/ws_upgrade_service.hpp]
* [repo_file example/server-framework/wss_ports.hpp]
[endsect]

View File

@ -4,6 +4,10 @@ GroupSources(include/beast beast)
GroupSources(example/server-framework "/")
if (OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIR})
endif()
add_executable (server-framework
${BEAST_INCLUDES}
${SERVER_INCLUDES}
@ -15,3 +19,7 @@ target_link_libraries(
Beast
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY})
if (OPENSSL_FOUND)
target_link_libraries(server-framework ${OPENSSL_LIBRARIES})
endif()

View File

@ -118,7 +118,7 @@ make_queued_http_write(
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class async_http_con : public http_base
class async_http_con_base : public http_base
{
// This function lets us access members of the derived class
Derived&
@ -161,7 +161,7 @@ protected:
public:
// Constructor
async_http_con(
async_http_con_base(
beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
@ -180,15 +180,36 @@ public:
{
}
// Called by the port after creating the object
void
run()
{
// Start reading the header for the first request.
// Give the derived class a chance to do stuff
//
impl().do_handshake();
}
protected:
void
do_run()
{
do_read_header();
}
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't log operation aborted since those happen normally.
//
if(ec && ec != boost::asio::error::operation_aborted)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
private:
// Perform an asynchronous read for the next request header
//
@ -214,7 +235,7 @@ private:
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con::on_read_header,
&async_http_con_base::on_read_header,
impl().shared_from_this(),
std::placeholders::_1)));
}
@ -228,12 +249,12 @@ private:
struct send_lambda
{
// holds "this"
async_http_con& self_;
async_http_con_base& self_;
public:
// capture "this"
explicit
send_lambda(async_http_con& self)
send_lambda(async_http_con_base& self)
: self_(self)
{
}
@ -251,9 +272,17 @@ private:
void
on_read_header(error_code ec)
{
// This happens when the other end closes gracefully
//
if(ec == beast::http::error::end_of_stream)
{
// VFALCO what about the write queue?
return impl().do_shutdown();
}
// On failure we just return, the shared_ptr that is bound
// into the completion will go out of scope and eventually
// we will get destroyed.
// this will get destroyed.
//
if(ec)
return fail("on_read", ec);
@ -281,7 +310,7 @@ private:
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con::on_read,
&async_http_con_base::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
@ -290,6 +319,13 @@ private:
void
on_read(error_code ec)
{
// Shouldn't be getting end_of_stream here;
// that would mean that we got an incomplete
// message, counting as an error.
//
if(ec)
return fail("on_read", ec);
// Grab a reference to the request again
auto& req = parser_->get();
@ -298,10 +334,10 @@ private:
//
send_lambda send{*this};
// Give each services a chance to handle the request
// Give each service a chance to handle the request
//
if(! services_.respond(
impl().stream(),
std::move(impl().stream()),
ep_,
std::move(req),
send))
@ -319,7 +355,7 @@ private:
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this async_http_con object get destroyed.
// let this async_http_con_base object get destroyed.
//
return;
}
@ -357,7 +393,7 @@ private:
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con::on_write,
&async_http_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
@ -369,7 +405,7 @@ private:
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con::on_write,
&async_http_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1))));
}
@ -381,6 +417,14 @@ private:
// Make sure our state is what we think it is
BOOST_ASSERT(writing_);
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == beast::http::error::end_of_stream)
return impl().do_shutdown();
// On failure just log and return
if(ec)
return fail("on_write", ec);
@ -403,23 +447,6 @@ private:
// Delete the item since we used it
queue_.erase(queue_.begin());
}
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't log end of stream or operation aborted
// since those happen under normal circumstances.
//
if( ec != beast::http::error::end_of_stream &&
ec != boost::asio::error::operation_aborted)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
};
//------------------------------------------------------------------------------
@ -428,7 +455,7 @@ private:
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class async_http_con_plain
class async_http_con
// Note that we give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason we do that
@ -436,7 +463,7 @@ class async_http_con_plain
// derived class (this class) use its members in calls to std::bind,
// without an ugly call to `dynamic_downcast` or other nonsense.
//
: public std::enable_shared_from_this<async_http_con_plain<Services...>>
: public std::enable_shared_from_this<async_http_con<Services...>>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
@ -445,17 +472,17 @@ class async_http_con_plain
// Declare this base last now that everything else got set up first.
//
, public async_http_con<async_http_con_plain<Services...>, Services...>
, public async_http_con_base<async_http_con<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
async_http_con_plain(
async_http_con(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, async_http_con<async_http_con_plain<Services...>, Services...>(
, async_http_con_base<async_http_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
@ -469,6 +496,31 @@ public:
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class async_http_con_base<async_http_con<Services...>, Services...>;
// This is called by the base before running the main loop.
//
void
do_handshake()
{
// Run the main loop right away
//
this->do_run();
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown()
{
error_code ec;
stream().shutdown(socket_type::shutdown_both, ec);
if(ec)
return this->fail("shutdown", ec);
}
};
//------------------------------------------------------------------------------
@ -539,7 +591,7 @@ public:
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<async_http_con_plain<Services...>>(
std::make_shared<async_http_con<Services...>>(
std::move(sock),
"http_async_port",
log_,

View File

@ -42,7 +42,7 @@ namespace framework {
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class sync_http_con
class sync_http_con_base
: public http_base
{
// This function lets us access members of the derived class
@ -71,7 +71,7 @@ class sync_http_con
public:
/// Constructor
sync_http_con(
sync_http_con_base(
beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
@ -90,19 +90,36 @@ public:
{
}
// This is called to start the connection after
// it is accepted.
//
void
run()
{
// Bind a shared pointer into the lambda for the
// thread, so the sync_http_con is destroyed after
// thread, so the sync_http_con_base is destroyed after
// the thread function exits.
//
std::thread{
&sync_http_con::do_run,
&sync_http_con_base::do_run,
impl().shared_from_this()
}.detach();
}
protected:
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
if(ec)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
private:
// This lambda is passed to the service list to handle
// the case of sending request objects of varying types.
@ -113,7 +130,7 @@ private:
struct send_lambda
{
// holds "this"
sync_http_con& self_;
sync_http_con_base& self_;
// holds the captured error code
error_code& ec_;
@ -123,7 +140,7 @@ private:
//
// Capture "this" and "ec"
//
send_lambda(sync_http_con& self, error_code& ec)
send_lambda(sync_http_con_base& self, error_code& ec)
: self_(self)
, ec_(ec)
{
@ -146,6 +163,16 @@ private:
void
do_run()
{
error_code ec;
// Give the derived class a chance to do stuff before we
// enter the main loop. This is for SSL connections really.
//
impl().do_handshake(ec);
if(ec)
return fail("handshake", ec);
// The main connection loop, we alternate between
// reading a request and sending a response. On
// error we log and return, which destroys the thread
@ -153,8 +180,6 @@ private:
//
for(;;)
{
error_code ec;
// Arguments passed to the parser constructor are
// forwarded to the message object. A single argument
// is forwarded to the body constructor.
@ -167,8 +192,20 @@ private:
// Read the header first
beast::http::read_header(impl().stream(), buffer_, parser, ec);
// This happens when the other end closes gracefully
//
if(ec == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec)
return fail("shutdown", ec);
return;
}
// Any other error and we fail the connection
if(ec)
return fail("on_read", ec);
return fail("read_header", ec);
send_lambda send{*this, ec};
@ -182,58 +219,105 @@ private:
// so send the appropriate response synchronously.
//
send(this->continue_100(req));
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
}
// Read the rest of the message, if any.
//
beast::http::read(impl().stream(), buffer_, parser, ec);
// Give each services a chance to handle the request
// Shouldn't be getting end_of_stream here;
// that would mean that we got an incomplete
// message, counting as an error.
//
if(ec)
return fail("read", ec);
// Give each service a chance to handle the request
//
if(! services_.respond(
impl().stream(),
ep_,
std::move(req),
send))
std::move(impl().stream()),
ep_,
std::move(req),
send))
{
// No service handled the request,
// send a Bad Request result to the client.
//
send(this->bad_request(req));
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
}
else
{
// This happens when we send an HTTP message
// whose semantics indicate that the connection
// should be closed afterwards. For example if
// we send a Connection: close.
//
if(ec == beast::http::error::end_of_stream)
{
// Give the derived class a chance to do stuff
impl().do_shutdown(ec);
if(ec)
return fail("shutdown", ec);
return;
}
// Have to check the error every time we call the lambda
//
if(ec)
return fail("write", ec);
// See if the service that handled the
// response took ownership of the stream.
if(! impl().stream().is_open())
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this sync_http_con object get destroyed.
// let this sync_http_con_base object get destroyed.
return;
}
}
if(ec)
return fail("on_write", ec);
// Theres no pipelining possible in a synchronous server
// because we can't do reads and writes at the same time.
}
}
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
if( ec != beast::http::error::end_of_stream &&
ec != boost::asio::error::operation_aborted)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
}
};
//------------------------------------------------------------------------------
@ -242,12 +326,12 @@ private:
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class sync_http_con_plain
class sync_http_con
// Note that we give this object the `enable_shared_from_this`, and have
// the base class call `impl().shared_from_this()` when needed.
//
: public std::enable_shared_from_this<sync_http_con_plain<Services...>>
: public std::enable_shared_from_this<sync_http_con<Services...>>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
@ -256,17 +340,17 @@ class sync_http_con_plain
// Declare this base last now that everything else got set up first.
//
, public sync_http_con<sync_http_con_plain<Services...>, Services...>
, public sync_http_con_base<sync_http_con<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
sync_http_con_plain(
sync_http_con(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, sync_http_con<sync_http_con_plain<Services...>, Services...>(
, sync_http_con_base<sync_http_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
@ -281,6 +365,29 @@ public:
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_http_con_base<sync_http_con<Services...>, Services...>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
stream().shutdown(socket_type::shutdown_both, ec);
}
};
//------------------------------------------------------------------------------
@ -345,7 +452,7 @@ public:
// Create a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_http_con_plain<Services...>>(
std::make_shared<sync_http_con<Services...>>(
std::move(sock),
"http_sync_port",
log_,

View File

@ -0,0 +1,368 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#define BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP
#include "http_sync_port.hpp"
#include "http_async_port.hpp"
#include "ssl_stream.hpp"
#include <boost/asio/ssl.hpp>
namespace framework {
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class sync_https_con
// Note that we give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason we do that
// is so that the shared_ptr has the correct type. This lets the
// derived class (this class) use its members in calls to std::bind,
// without an ugly call to `dynamic_downcast` or other nonsense.
//
: public std::enable_shared_from_this<sync_https_con<Services...>>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
//
, public base_from_member<ssl_stream<socket_type>>
// Declare this base last now that everything else got set up first.
//
, public sync_http_con_base<sync_https_con<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
sync_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, sync_http_con_base<sync_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
// The base class calls this to obtain the object to
// use for reading and writing HTTP messages.
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
private:
friend class sync_http_con_base<sync_https_con<Services...>, Services...>;
// This is called by the base before running the main loop.
//
void
do_handshake(error_code& ec)
{
// Perform the SSL handshake
//
stream().handshake(boost::asio::ssl::stream_base::server, ec);
}
// This is called when the other end closes the connection gracefully.
//
void
do_shutdown(error_code& ec)
{
// Note that this is an SSL shutdown
//
stream().shutdown(ec);
if(ec)
return this->fail("shutdown", ec);
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous HTTP connection which
// uses an OpenSSL socket as the stream.
//
template<class... Services>
class async_https_con
// Note that we give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason we do that
// is so that the shared_ptr has the correct type. This lets the
// derived class (this class) use its members in calls to std::bind,
// without an ugly call to `dynamic_downcast` or other nonsense.
//
: public std::enable_shared_from_this<async_https_con<Services...>>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
//
, public base_from_member<ssl_stream<socket_type>>
// Declare this base last now that everything else got set up first.
//
, public async_http_con_base<async_https_con<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
async_https_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<ssl_stream<socket_type>>(std::move(sock), ctx)
, async_http_con_base<async_https_con<Services...>, Services...>(
std::forward<Args>(args)...)
{
}
// Returns the stream.
// The base class calls this to obtain the object to
// use for reading and writing HTTP messages.
//
ssl_stream<socket_type>&
stream()
{
return this->member;
}
private:
friend class async_http_con_base<async_https_con<Services...>, Services...>;
// Called by the base class before starting the main loop.
//
void
do_handshake()
{
// This is SSL so perform the handshake
//
stream().async_handshake(
boost::asio::ssl::stream_base::server,
this->strand_.wrap(std::bind(
&async_https_con::on_handshake,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL handshake completes
void
on_handshake(error_code ec)
{
if(ec)
return this->fail("on_handshake", ec);
// No error so run the main loop
this->do_run();
}
// Called when the end of stream is reached
void
do_shutdown()
{
// This is an SSL shutdown
//
stream().async_shutdown(
this->strand_.wrap(std::bind(
&async_https_con::on_shutdown,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL shutdown completes
void
on_shutdown(error_code ec)
{
if(ec)
return this->fail("on_shutdown", ec);
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_sync_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
*/
https_sync_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an HTTPS connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_https_con<Services...>>(
std::move(sock),
ctx_,
"https_sync_port",
log_,
services_,
instance_.next_id(),
ep
)->run();
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTPS port handler
This type meets the requirements of @b PortHandler. It supports
variable list of HTTP services in its template parameter list,
and provides a synchronous connection implementation to service
*/
template<class... Services>
class https_async_port
{
// Reference to the server instance that made us
server& instance_;
// The stream to log to
std::ostream& log_;
// The list of services connections created from this port will support
service_list<Services...> services_;
// The SSL context containing the server's credentials
boost::asio::ssl::context& ctx_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
*/
https_async_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx)
: instance_(instance)
, log_(log)
, ctx_(ctx)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
@return A reference to the service list. This permits
calls to be chained in a single expression.
*/
template<std::size_t Index, class... Args>
void
init(error_code& ec, Args&&... args)
{
services_.template init<Index>(
ec,
std::forward<Args>(args)...);
}
/** Called by the server to provide ownership of the socket for a new connection
@param sock The socket whose ownership is to be transferred
@ep The remote endpoint
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create an HTTPS connection object
// and transfer ownership of the socket.
//
std::make_shared<async_https_con<Services...>>(
std::move(sock),
ctx_,
"https_async_port",
log_,
services_,
instance_.next_id(),
ep
)->run();
}
};
} // framework
#endif

View File

@ -12,6 +12,12 @@
#include "ws_async_port.hpp"
#include "ws_sync_port.hpp"
#if BEAST_USE_OPENSSL
#include "https_ports.hpp"
#include "wss_ports.hpp"
#include "ssl_certificate.hpp"
#endif
#include "file_service.hpp"
#include "ws_upgrade_service.hpp"
@ -133,6 +139,13 @@ main(
// Create our server instance with the specified number of threads
server instance{threads};
//--------------------------------------------------------------------------
//
// Asynchronous WebSocket HTTP
//
// port port + 1
//
//--------------------------------------------------------------------------
{
// Install an asynchronous WebSocket echo port handler
//
@ -185,6 +198,13 @@ main(
return fail("http_async_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Synchronous WebSocket HTTP
//
// port + 2 port + 3
//
//--------------------------------------------------------------------------
{
// Install a synchronous WebSocket echo port handler
//
@ -238,5 +258,138 @@ main(
return fail("http_sync_port/file_service", ec);
}
//
// If OpenSSL is available then install some SSL-enabled ports
//
#if BEAST_USE_OPENSSL
ssl_certificate cert;
//--------------------------------------------------------------------------
//
// Asynchronous Secure WebSocket HTTPS
//
// port + 4 port + 5
//
//--------------------------------------------------------------------------
{
// Install an asynchronous Secure WebSocket echo port handler
//
auto wsp = instance.make_port<wss_async_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 4)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd}
);
if(ec)
return fail("ws_async_port", ec);
// Install an asynchronous HTTPS port handler
//
auto sp = instance.make_port<https_async_port<
ws_upgrade_service<wss_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 5)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_async_port", ec);
// Set up the ws_upgrade_service. We will route upgrade
// requests to the websocket port handler created earlier.
//
sp->template init<0>(
ec,
wsp // The websocket port handler
);
if(ec)
return fail("https_async_port/ws_upgrade_service", ec);
// Set up the file_service to point to the root path.
//
sp->template init<1>(
ec,
root, // The root path
"https_async_port" // The value for the Server field
);
if(ec)
return fail("https_async_port/file_service", ec);
}
//--------------------------------------------------------------------------
//
// Synchronous Secure WebSocket HTTPS
//
// port + 6 port + 7
//
//--------------------------------------------------------------------------
{
// Install a synchronous Secure WebSocket echo port handler
//
auto wsp = instance.make_port<wss_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 6)},
instance,
std::cout,
cert.get(),
set_ws_options{pmd});
if(ec)
return fail("wss_sync_port", ec);
// Install a synchronous HTTPS port handler
//
auto sp = instance.make_port<https_sync_port<
ws_upgrade_service<wss_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 7)},
instance,
std::cout,
cert.get());
if(ec)
return fail("https_sync_port", ec);
// Set up the ws_upgrade_service. We will route upgrade
// requests to the websocket port handler created earlier.
//
sp->template init<0>(
ec,
wsp // The websocket port handler
);
if(ec)
return fail("http_sync_port/ws_upgrade_service", ec);
// Set up the file_service to point to the root path.
//
sp->template init<1>(
ec,
root,
"https_sync_port"
);
if(ec)
return fail("https_sync_port/file_service", ec);
}
#endif
sig_wait();
}

View File

@ -0,0 +1,146 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#define BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP
#include <boost/asio/buffer.hpp>
#include <boost/asio/ssl/context.hpp>
#include <cstddef>
#include <memory>
namespace framework {
// This sets up the self-signed certificate that the server
// uses for its encrypted connections
class ssl_certificate
{
// The template argument is gratuitous, to
// make the definition header-only without
// also making it inline.
//
template<class = void>
void
construct();
boost::asio::ssl::context ctx_;
public:
ssl_certificate()
: ctx_(boost::asio::ssl::context::sslv23)
{
construct();
}
boost::asio::ssl::context&
get()
{
return ctx_;
}
};
template<class>
void
ssl_certificate::construct()
{
/*
The certificate was generated from CMD.EXE on Windows 10 using:
winpty openssl dhparam -out dh.pem 2048
winpty openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 10000 -out cert.pem -subj "//C=US\ST=CA\L=Los Angeles\O=Beast\CN=www.example.com"
*/
std::string const cert =
"-----BEGIN CERTIFICATE-----\n"
"MIIDaDCCAlCgAwIBAgIJAO8vBu8i8exWMA0GCSqGSIb3DQEBCwUAMEkxCzAJBgNV\n"
"BAYTAlVTMQswCQYDVQQIDAJDQTEtMCsGA1UEBwwkTG9zIEFuZ2VsZXNPPUJlYXN0\n"
"Q049d3d3LmV4YW1wbGUuY29tMB4XDTE3MDUwMzE4MzkxMloXDTQ0MDkxODE4Mzkx\n"
"MlowSTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMS0wKwYDVQQHDCRMb3MgQW5n\n"
"ZWxlc089QmVhc3RDTj13d3cuZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUA\n"
"A4IBDwAwggEKAoIBAQDJ7BRKFO8fqmsEXw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcF\n"
"xqGitbnLIrOgiJpRAPLy5MNcAXE1strVGfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7b\n"
"Fu8TsCzO6XrxpnVtWk506YZ7ToTa5UjHfBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO\n"
"9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wWKIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBp\n"
"yY8anC8u4LPbmgW0/U31PH0rRVfGcBbZsAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrv\n"
"enu2tOK9Qx6GEzXh3sekZkxcgh+NlIxCNxu//Dk9AgMBAAGjUzBRMB0GA1UdDgQW\n"
"BBTZh0N9Ne1OD7GBGJYz4PNESHuXezAfBgNVHSMEGDAWgBTZh0N9Ne1OD7GBGJYz\n"
"4PNESHuXezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCmTJVT\n"
"LH5Cru1vXtzb3N9dyolcVH82xFVwPewArchgq+CEkajOU9bnzCqvhM4CryBb4cUs\n"
"gqXWp85hAh55uBOqXb2yyESEleMCJEiVTwm/m26FdONvEGptsiCmF5Gxi0YRtn8N\n"
"V+KhrQaAyLrLdPYI7TrwAOisq2I1cD0mt+xgwuv/654Rl3IhOMx+fKWKJ9qLAiaE\n"
"fQyshjlPP9mYVxWOxqctUdQ8UnsUKKGEUcVrA08i1OAnVKlPFjKBvk+r7jpsTPcr\n"
"9pWXTO9JrYMML7d+XRSZA1n3856OqZDX4403+9FnXCvfcLZLLKTBvwwFgEFGpzjK\n"
"UEVbkhd5qstF6qWK\n"
"-----END CERTIFICATE-----\n";
std::string const key =
"-----BEGIN PRIVATE KEY-----\n"
"MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDJ7BRKFO8fqmsE\n"
"Xw8v9YOVXyrQVsVbjSSGEs4Vzs4cJgcFxqGitbnLIrOgiJpRAPLy5MNcAXE1strV\n"
"GfdEf7xMYSZ/4wOrxUyVw/Ltgsft8m7bFu8TsCzO6XrxpnVtWk506YZ7ToTa5UjH\n"
"fBi2+pWTxbpN12UhiZNUcrRsqTFW+6fO9d7xm5wlaZG8cMdg0cO1bhkz45JSl3wW\n"
"KIES7t3EfKePZbNlQ5hPy7Pd5JTmdGBpyY8anC8u4LPbmgW0/U31PH0rRVfGcBbZ\n"
"sAoQw5Tc5dnb6N2GEIbq3ehSfdDHGnrvenu2tOK9Qx6GEzXh3sekZkxcgh+NlIxC\n"
"Nxu//Dk9AgMBAAECggEBAK1gV8uETg4SdfE67f9v/5uyK0DYQH1ro4C7hNiUycTB\n"
"oiYDd6YOA4m4MiQVJuuGtRR5+IR3eI1zFRMFSJs4UqYChNwqQGys7CVsKpplQOW+\n"
"1BCqkH2HN/Ix5662Dv3mHJemLCKUON77IJKoq0/xuZ04mc9csykox6grFWB3pjXY\n"
"OEn9U8pt5KNldWfpfAZ7xu9WfyvthGXlhfwKEetOuHfAQv7FF6s25UIEU6Hmnwp9\n"
"VmYp2twfMGdztz/gfFjKOGxf92RG+FMSkyAPq/vhyB7oQWxa+vdBn6BSdsfn27Qs\n"
"bTvXrGe4FYcbuw4WkAKTljZX7TUegkXiwFoSps0jegECgYEA7o5AcRTZVUmmSs8W\n"
"PUHn89UEuDAMFVk7grG1bg8exLQSpugCykcqXt1WNrqB7x6nB+dbVANWNhSmhgCg\n"
"VrV941vbx8ketqZ9YInSbGPWIU/tss3r8Yx2Ct3mQpvpGC6iGHzEc/NHJP8Efvh/\n"
"CcUWmLjLGJYYeP5oNu5cncC3fXUCgYEA2LANATm0A6sFVGe3sSLO9un1brA4zlZE\n"
"Hjd3KOZnMPt73B426qUOcw5B2wIS8GJsUES0P94pKg83oyzmoUV9vJpJLjHA4qmL\n"
"CDAd6CjAmE5ea4dFdZwDDS8F9FntJMdPQJA9vq+JaeS+k7ds3+7oiNe+RUIHR1Sz\n"
"VEAKh3Xw66kCgYB7KO/2Mchesu5qku2tZJhHF4QfP5cNcos511uO3bmJ3ln+16uR\n"
"GRqz7Vu0V6f7dvzPJM/O2QYqV5D9f9dHzN2YgvU9+QSlUeFK9PyxPv3vJt/WP1//\n"
"zf+nbpaRbwLxnCnNsKSQJFpnrE166/pSZfFbmZQpNlyeIuJU8czZGQTifQKBgHXe\n"
"/pQGEZhVNab+bHwdFTxXdDzr+1qyrodJYLaM7uFES9InVXQ6qSuJO+WosSi2QXlA\n"
"hlSfwwCwGnHXAPYFWSp5Owm34tbpp0mi8wHQ+UNgjhgsE2qwnTBUvgZ3zHpPORtD\n"
"23KZBkTmO40bIEyIJ1IZGdWO32q79nkEBTY+v/lRAoGBAI1rbouFYPBrTYQ9kcjt\n"
"1yfu4JF5MvO9JrHQ9tOwkqDmNCWx9xWXbgydsn/eFtuUMULWsG3lNjfst/Esb8ch\n"
"k5cZd6pdJZa4/vhEwrYYSuEjMCnRb0lUsm7TsHxQrUd6Fi/mUuFU/haC0o0chLq7\n"
"pVOUFq5mW8p0zbtfHbjkgxyF\n"
"-----END PRIVATE KEY-----\n";
std::string const dh =
"-----BEGIN DH PARAMETERS-----\n"
"MIIBCAKCAQEArzQc5mpm0Fs8yahDeySj31JZlwEphUdZ9StM2D8+Fo7TMduGtSi+\n"
"/HRWVwHcTFAgrxVdm+dl474mOUqqaz4MpzIb6+6OVfWHbQJmXPepZKyu4LgUPvY/\n"
"4q3/iDMjIS0fLOu/bLuObwU5ccZmDgfhmz1GanRlTQOiYRty3FiOATWZBRh6uv4u\n"
"tff4A9Bm3V9tLx9S6djq31w31Gl7OQhryodW28kc16t9TvO1BzcV3HjRPwpe701X\n"
"oEEZdnZWANkkpR/m/pfgdmGPU66S2sXMHgsliViQWpDCYeehrvFRHEdR9NV+XJfC\n"
"QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n"
"-----END DH PARAMETERS-----\n";
ctx_.set_password_callback(
[](std::size_t size,
boost::asio::ssl::context_base::password_purpose)
{
return "test";
});
ctx_.set_options(
boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
ctx_.use_certificate_chain(
boost::asio::buffer(cert.data(), cert.size()));
ctx_.use_private_key(
boost::asio::buffer(key.data(), key.size()),
boost::asio::ssl::context::file_format::pem);
ctx_.use_tmp_dh(
boost::asio::buffer(dh.data(), dh.size()));
}
} // framework
#endif

View File

@ -8,24 +8,40 @@
#ifndef BEAST_EXAMPLE_SERVER_SSL_STREAM_HPP
#define BEAST_EXAMPLE_SERVER_SSL_STREAM_HPP
// This include is necessary to work with `ssl::stream` and `beast::websocket::stream`
#include <beast/websocket/ssl.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
namespace beast {
/** Movable SSL socket wrapper
/** C++11 enabled SSL socket wrapper
This wrapper provides an interface identical to `boost::asio::ssl::stream`,
which is additionally move constructible and move assignable.
with the following additional properties:
@li Satisfies @b MoveConstructible
@li Satisfies @b MoveAssignable
@li Constructible from a moved socket.
*/
template<class NextLayer>
class ssl_stream
: public boost::asio::ssl::stream_base
{
// only works for boost::asio::ip::tcp::socket
// for now because of the move limitations
static_assert(std::is_same<NextLayer, boost::asio::ip::tcp::socket>::value,
"NextLayer requirements not met");
using stream_type = boost::asio::ssl::stream<NextLayer>;
std::unique_ptr<stream_type> p_;
boost::asio::ssl::context* ctx_;
public:
/// The native handle type of the SSL stream.
@ -43,15 +59,30 @@ public:
/// The type of the lowest layer.
using lowest_layer_type = typename stream_type::lowest_layer_type;
ssl_stream(ssl_stream&&) = default;
ssl_stream(ssl_stream const&) = delete;
ssl_stream& operator=(ssl_stream&&) = default;
ssl_stream& operator=(ssl_stream const&) = delete;
template<class... Args>
ssl_stream(Args&&... args)
: p_(new stream_type{std::forward<Args>(args)...)
ssl_stream(boost::asio::ip::tcp::socket&& sock, boost::asio::ssl::context& ctx)
: p_(new stream_type{sock.get_io_service(), ctx})
, ctx_(&ctx)
{
p_->next_layer() = std::move(sock);
}
ssl_stream(ssl_stream&& other)
: p_(new stream_type(other.get_io_service(), *other.ctx_))
, ctx_(other.ctx_)
{
using std::swap;
swap(p_, other.p_);
}
ssl_stream& operator=(ssl_stream&& other)
{
std::unique_ptr<stream_type> p(
new stream_type{other.get_io_service(), other.ctx_});
using std::swap;
swap(p_, p);
swap(p_, other.p_);
ctx_ = other.ctx_;
return *this;
}
boost::asio::io_service&
@ -184,7 +215,7 @@ public:
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
{
return p_->async_handshake(type, buffers,
BOOST_ASIO__MOVE_CAST(BufferedHandshakeHandler)(handler));
BOOST_ASIO_MOVE_CAST(BufferedHandshakeHandler)(handler));
}
void
@ -251,14 +282,53 @@ public:
template<class MutableBufferSequence, class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void(boost::system::error_code, std::size_t))
async_read_some(MutableBufferSequence& buffers,
async_read_some(MutableBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
return p_->async_read_some(buffers,
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler))
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler));
}
template<class SyncStream>
friend
void
teardown(beast::websocket::teardown_tag,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec);
template<class AsyncStream, class TeardownHandler>
friend
void
async_teardown(beast::websocket::teardown_tag,
ssl_stream<AsyncStream>& stream, TeardownHandler&& handler);
};
} // beast
// These hooks are used to inform beast::websocket::stream on
// how to tear down the connection as part of the WebSocket
// protocol specifications
#endif
template<class SyncStream>
inline
void
teardown(beast::websocket::teardown_tag,
ssl_stream<SyncStream>& stream,
boost::system::error_code& ec)
{
// Just forward it to the wrapped ssl::stream
using beast::websocket::teardown;
teardown(beast::websocket::teardown_tag{}, *stream.p_, ec);
}
template<class AsyncStream, class TeardownHandler>
inline
void
async_teardown(beast::websocket::teardown_tag,
ssl_stream<AsyncStream>& stream, TeardownHandler&& handler)
{
// Just forward it to the wrapped ssl::stream
using beast::websocket::async_teardown;
async_teardown(beast::websocket::teardown_tag{},
*stream.p_, std::forward<TeardownHandler>(handler));
}
#endif

View File

@ -23,7 +23,7 @@ namespace framework {
//
//
template<class Derived>
class async_ws_con
class async_ws_con_base
{
// This function lets us access members of the derived class
Derived&
@ -58,7 +58,7 @@ protected:
public:
// Constructor
template<class Callback>
async_ws_con(
async_ws_con_base(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
@ -72,9 +72,9 @@ public:
// Limit of 1MB on messages
, buffer_(1024 * 1024)
, strand_(impl().ws().get_io_service())
, strand_(impl().stream().get_io_service())
{
cb(impl().ws());
cb(impl().stream());
}
// Run the connection
@ -82,18 +82,7 @@ public:
void
run()
{
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().ws().async_accept_ex(
[&](beast::websocket::response_type& res)
{
res.set(beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
impl().do_handshake();
}
// Run the connection.
@ -109,17 +98,47 @@ public:
// the request by parameter, instead of reading
// it from the network.
//
impl().ws().async_accept_ex(req,
impl().stream().async_accept_ex(req,
[&](beast::websocket::response_type& res)
{
res.set(beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con::on_accept,
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
protected:
// Performs the WebSocket handshake
void
do_accept()
{
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().async_accept_ex(
[&](beast::websocket::response_type& res)
{
res.set(beast::http::field::server, server_name_);
},
strand_.wrap(std::bind(
&async_ws_con_base::on_accept,
impl().shared_from_this(),
std::placeholders::_1)));
}
// This helper reports failures
//
void
fail(std::string what, error_code ec)
{
if(ec != beast::websocket::error::closed)
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
private:
// Called when accept_ex completes
//
@ -136,10 +155,10 @@ private:
void
do_read()
{
impl().ws().async_read(
impl().stream().async_read(
buffer_,
strand_.wrap(std::bind(
&async_ws_con::on_read,
&async_ws_con_base::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
@ -155,14 +174,14 @@ private:
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().ws().binary(impl().ws().got_binary());
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().ws().async_write(
impl().stream().async_write(
buffer_.data(),
strand_.wrap(std::bind(
&async_ws_con::on_write,
&async_ws_con_base::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
@ -184,17 +203,6 @@ private:
//
do_read();
}
// This helper reports failures
//
void
fail(std::string what, error_code ec)
{
if(ec != beast::websocket::error::closed)
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
}
};
//------------------------------------------------------------------------------
@ -202,12 +210,12 @@ private:
// This class represents an asynchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class async_ws_con_plain
class async_ws_con
// Note that we give this object the `enable_shared_from_this`, and have
// the base class call `impl().shared_from_this()` when needed.
//
: public std::enable_shared_from_this<async_ws_con_plain>
: public std::enable_shared_from_this<async_ws_con>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
@ -216,18 +224,18 @@ class async_ws_con_plain
// Declare this base last now that everything else got set up first.
//
, public async_ws_con<async_ws_con_plain>
, public async_ws_con_base<async_ws_con>
{
public:
// Construct the plain connection.
//
template<class... Args>
explicit
async_ws_con_plain(
async_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
, async_ws_con<async_ws_con_plain>(std::forward<Args>(args)...)
, async_ws_con_base<async_ws_con>(std::forward<Args>(args)...)
{
}
@ -236,10 +244,20 @@ public:
// The base class calls this to obtain the websocket stream object.
//
beast::websocket::stream<socket_type>&
ws()
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend async_ws_con_base<async_ws_con>;
void
do_handshake()
{
do_accept();
}
};
//------------------------------------------------------------------------------
@ -304,7 +322,7 @@ public:
socket_type&& sock,
endpoint_type ep)
{
std::make_shared<async_ws_con_plain>(
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,
@ -332,7 +350,7 @@ public:
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
std::make_shared<async_ws_con_plain>(
std::make_shared<async_ws_con>(
std::move(sock),
"ws_async_port",
log_,

View File

@ -30,7 +30,7 @@ namespace framework {
for plain and SSL stream objects.
*/
template<class Derived>
class sync_ws_con
class sync_ws_con_base
{
// This function lets us access members of the derived class
Derived&
@ -56,7 +56,7 @@ class sync_ws_con
public:
// Constructor
template<class Callback>
sync_ws_con(
sync_ws_con_base(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
@ -67,22 +67,23 @@ public:
, id_(id)
, ep_(ep)
{
cb(impl().ws());
cb(impl().stream());
}
// Run the connection.
// Run the connection. This is called for the case
// where we have not received the upgrade request yet.
//
void
run()
{
// We run the do_accept function in its own thread,
// We run the do_run function in its own thread,
// and bind a shared pointer to the connection object
// into the function. The last reference to the shared
// pointer will go away when the thread exits, thus
// destroying the connection object.
//
std::thread{
&sync_ws_con::do_accept,
&sync_ws_con_base::do_accept,
impl().shared_from_this()
}.detach();
}
@ -106,7 +107,58 @@ public:
}}.detach();
}
protected:
// Called when a failure occurs
//
void
fail(std::string what, error_code ec)
{
// Don't report the "closed" error since that
// happens under normal circumstances.
//
if(ec && ec != beast::websocket::error::closed)
{
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ec.message() << std::endl;
log_.flush();
}
}
private:
// This function performs the WebSocket handshake
// and runs the main loop upon success.
void
do_accept()
{
error_code ec;
// Give the derived class a chance to do stuff before we
// enter the main loop. This is for SSL connections really.
//
impl().do_handshake(ec);
if(ec)
return fail("handshake", ec);
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().stream().accept_ex(
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, server_name_);
},
ec);
if(ec)
return fail("accept", ec);
// Run the connection
//
do_run();
}
// This is the lambda used when launching a connection from
// an already-received request. In C++14 we could simply use
// a lambda capture but this example requires only C++11 so
@ -116,7 +168,7 @@ private:
template<class Body, class Fields>
class lambda
{
std::shared_ptr<sync_ws_con> self_;
std::shared_ptr<sync_ws_con_base> self_;
beast::http::request<Body, Fields> req_;
public:
@ -125,7 +177,7 @@ private:
// This is the equivalent of the capture section of the lambda.
//
lambda(
std::shared_ptr<sync_ws_con> self,
std::shared_ptr<sync_ws_con_base> self,
beast::http::request<Body, Fields>&& req)
: self_(std::move(self))
, req_(std::move(req))
@ -151,7 +203,7 @@ private:
// the request by parameter, instead of reading
// it from the network.
//
self_->impl().ws().accept_ex(req,
self_->impl().stream().accept_ex(req,
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, self_->server_name_);
@ -159,55 +211,18 @@ private:
ec);
}
// Run the connection
//
self_->do_run(ec);
if(ec)
return self_->fail("accept", ec);
self_->do_run();
}
};
void
do_accept()
do_run()
{
error_code ec;
// Read the WebSocket upgrade request and attempt
// to send back the response.
//
impl().ws().accept_ex(
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, server_name_);
},
ec);
// Run the connection
//
do_run(ec);
}
void
do_run(error_code ec)
{
// Helper lambda to report a failure
//
auto const fail =
[&](std::string const& what, error_code ev)
{
if(ev != beast::websocket::error::closed)
log_ <<
"[#" << id_ << " " << ep_ << "] " <<
what << ": " << ev.message() << std::endl;
};
// Check for an error upon entry. This will
// come from one of the two calls to accept()
//
if(ec)
{
fail("accept", ec);
return;
}
// Loop, reading messages and echoing them back.
//
for(;;)
@ -219,7 +234,7 @@ private:
// Read the message
//
impl().ws().read(buffer, ec);
impl().stream().read(buffer, ec);
if(ec)
return fail("read", ec);
@ -227,11 +242,11 @@ private:
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().ws().binary(impl().ws().got_binary());
impl().stream().binary(impl().stream().got_binary());
// Now echo back the message
//
impl().ws().write(buffer.data(), ec);
impl().stream().write(buffer.data(), ec);
if(ec)
return fail("write", ec);
@ -244,12 +259,12 @@ private:
// This class represents a synchronous WebSocket connection
// which uses a plain TCP/IP socket (no encryption) as the stream.
//
class sync_ws_con_plain
class sync_ws_con
// Note that we give this object the `enable_shared_from_this`, and have
// the base class call `impl().shared_from_this()` when needed.
//
: public std::enable_shared_from_this<sync_ws_con_plain>
: public std::enable_shared_from_this<sync_ws_con>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
@ -258,18 +273,18 @@ class sync_ws_con_plain
// Declare this base last now that everything else got set up first.
//
, public sync_ws_con<sync_ws_con_plain>
, public sync_ws_con_base<sync_ws_con>
{
public:
// Construct the plain connection.
//
template<class... Args>
explicit
sync_ws_con_plain(
sync_ws_con(
socket_type&& sock,
Args&&... args)
: base_from_member<beast::websocket::stream<socket_type>>(std::move(sock))
, sync_ws_con<sync_ws_con_plain>(std::forward<Args>(args)...)
, sync_ws_con_base<sync_ws_con>(std::forward<Args>(args)...)
{
}
@ -278,10 +293,25 @@ public:
// The base class calls this to obtain the websocket stream object.
//
beast::websocket::stream<socket_type>&
ws()
stream()
{
return this->member;
}
private:
// Base class needs to be a friend to call our private members
friend class sync_ws_con_base<sync_ws_con>;
// This is called by the base before running the main loop.
// There's nothing to do for a plain connection.
//
void
do_handshake(error_code& ec)
{
// This is required by the specifications for error_code
//
ec = {};
}
};
//------------------------------------------------------------------------------
@ -346,7 +376,7 @@ public:
{
// Create our connection object and run it
//
std::make_shared<sync_ws_con_plain>(
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,
@ -377,7 +407,7 @@ public:
// Create the connection object and run it,
// transferring ownershop of the ugprade request.
//
std::make_shared<sync_ws_con_plain>(
std::make_shared<sync_ws_con>(
std::move(sock),
"ws_sync_port",
log_,

View File

@ -0,0 +1,414 @@
//
// 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)
//
#ifndef BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP
#define BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP
#include "ws_sync_port.hpp"
#include "ws_async_port.hpp"
#include "ssl_stream.hpp"
#include <boost/asio/ssl.hpp>
#include <boost/function.hpp>
namespace framework {
//------------------------------------------------------------------------------
// This class represents a synchronous Secure WebSocket connection
//
class sync_wss_con
// Note that we give this object the `enable_shared_from_this`, and have
// the base class call `impl().shared_from_this()` when needed.
//
: public std::enable_shared_from_this<sync_wss_con>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
//
, public base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>
// Declare this base last now that everything else got set up first.
//
, public sync_ws_con_base<sync_wss_con>
{
public:
// Construct the plain connection.
//
template<class... Args>
explicit
sync_wss_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(sock), ctx)
, sync_ws_con_base<sync_wss_con>(std::forward<Args>(args)...)
{
}
// Construct the connection from an existing, handshaked SSL stream
//
template<class... Args>
sync_wss_con(
ssl_stream<socket_type>&& stream,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(stream))
, sync_ws_con_base<sync_wss_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
//
// The base class calls this to obtain the websocket stream object.
//
beast::websocket::stream<ssl_stream<socket_type>>&
stream()
{
return this->member;
}
private:
friend class sync_ws_con_base<sync_wss_con>;
// This is called by the base before running the main loop.
//
void
do_handshake(error_code& ec)
{
// Perform the SSL handshake
//
// We use next_layer() to get at the underlying ssl_stream
//
stream().next_layer().handshake(boost::asio::ssl::stream_base::server, ec);
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous Secure WebSocket
// connection which uses an OpenSSL socket as the stream.
//
class async_wss_con
// Note that we give this object the enable_shared_from_this, and have
// the base class call impl().shared_from_this(). The reason we do that
// is so that the shared_ptr has the correct type. This lets the
// derived class (this class) use its members in calls to std::bind,
// without an ugly call to `dynamic_downcast` or other nonsense.
//
: public std::enable_shared_from_this<async_wss_con>
// We want the socket to be created before the base class so we use
// the "base from member" idiom which Boost provides as a class.
//
, public base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>
// Declare this base last now that everything else got set up first.
//
, public async_ws_con_base<async_wss_con>
{
public:
// Construct the connection.
//
template<class... Args>
async_wss_con(
socket_type&& sock,
boost::asio::ssl::context& ctx,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(sock), ctx)
, async_ws_con_base<async_wss_con>(std::forward<Args>(args)...)
{
}
// Construct the connection from an existing, handshaked SSL stream
//
template<class... Args>
async_wss_con(
ssl_stream<socket_type>&& stream,
Args&&... args)
: base_from_member<beast::websocket::stream<ssl_stream<socket_type>>>(std::move(stream))
, async_ws_con_base<async_wss_con>(std::forward<Args>(args)...)
{
}
// Returns the stream.
// The base class calls this to obtain the object to
// use for reading and writing HTTP messages.
//
beast::websocket::stream<ssl_stream<socket_type>>&
stream()
{
return this->member;
}
private:
friend class async_ws_con_base<async_wss_con>;
// Called by the port to start the connection
// after creating the object
//
void
do_handshake()
{
// This is SSL so perform the handshake first
//
stream().next_layer().async_handshake(
boost::asio::ssl::stream_base::server,
this->strand_.wrap(std::bind(
&async_wss_con::on_handshake,
this->shared_from_this(),
std::placeholders::_1)));
}
// Called when the SSL handshake completes
void
on_handshake(error_code ec)
{
if(ec)
return this->fail("on_handshake", ec);
// Move on to accepting the WebSocket handshake
//
this->do_accept();
}
};
//------------------------------------------------------------------------------
/** A synchronous Secure WebSocket @b PortHandler which implements echo.
This is a port handler which accepts Secure WebSocket upgrade
HTTP requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class wss_sync_port
{
// VFALCO We use boost::function to work around a compiler
// crash with gcc and clang using libstdc++
// The types of the on_stream callback
using on_new_stream_cb1 = boost::function<
void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 = boost::function<
void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
server& instance_;
std::ostream& log_;
boost::asio::ssl::context& ctx_;
on_new_stream_cb1 cb1_;
on_new_stream_cb2 cb2_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
wss_sync_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx,
Callback const& cb)
: instance_(instance)
, log_(log)
, ctx_(ctx)
, cb1_(cb)
, cb2_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(socket_type&& sock, endpoint_type ep)
{
// Create our connection object and run it
//
std::make_shared<sync_wss_con>(
std::move(sock),
ctx_,
"wss_sync_port",
log_,
instance_.next_id(),
ep,
cb2_
)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body, class Fields>
void
accept(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
// Create the connection object and run it,
// transferring ownershop of the ugprade request.
//
std::make_shared<sync_wss_con>(
std::move(stream),
"wss_sync_port",
log_,
instance_.next_id(),
ep,
cb2_
)->run(std::move(req));
}
};
//------------------------------------------------------------------------------
/** An asynchronous WebSocket @b PortHandler which implements echo.
This is a port handler which accepts WebSocket upgrade HTTP
requests and implements the echo protocol. All received
WebSocket messages will be echoed back to the remote host.
*/
class wss_async_port
{
// VFALCO We use boost::function to work around a compiler
// crash with gcc and clang using libstdc++
// The types of the on_stream callback
using on_new_stream_cb1 = boost::function<
void(beast::websocket::stream<socket_type>&)>;
using on_new_stream_cb2 = boost::function<
void(beast::websocket::stream<ssl_stream<socket_type>>&)>;
server& instance_;
std::ostream& log_;
boost::asio::ssl::context& ctx_;
on_new_stream_cb1 cb1_;
on_new_stream_cb2 cb2_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@param ctx The SSL context holding the SSL certificates to use
@param cb A callback which will be invoked for every new
WebSocket connection. This provides an opportunity to change
the settings on the stream before it is used. The callback
should have this equivalent signature:
@code
template<class NextLayer>
void callback(beast::websocket::stream<NextLayer>&);
@endcode
In C++14 this can be accomplished with a generic lambda. In
C++11 it will be necessary to write out a lambda manually,
with a templated operator().
*/
template<class Callback>
wss_async_port(
server& instance,
std::ostream& log,
boost::asio::ssl::context& ctx,
Callback const& cb)
: instance_(instance)
, log_(log)
, ctx_(ctx)
, cb1_(cb)
, cb2_(cb)
{
}
/** Accept a TCP/IP connection.
This function is called when the server has accepted an
incoming connection.
@param sock The connected socket.
@param ep The endpoint of the remote host.
*/
void
on_accept(
socket_type&& sock,
endpoint_type ep)
{
std::make_shared<async_wss_con>(
std::move(sock),
ctx_,
"wss_async_port",
log_,
instance_.next_id(),
ep,
cb2_
)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a connection that has already
delivered the handshake.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body, class Fields>
void
accept(
ssl_stream<socket_type>&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
std::make_shared<async_wss_con>(
std::move(stream),
"wss_async_port",
log_,
instance_.next_id(),
ep,
cb2_
)->run(std::move(req));
}
};
} // framework
#endif

View File

@ -5,6 +5,16 @@ GroupSources(include/beast beast)
GroupSources(test/server "/")
if (OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIR})
set(SSL_SOURCES
https_ports.cpp
ssl_stream.cpp
)
else ()
set(SSL_SOURCES "")
endif()
add_executable (server-test
${BEAST_INCLUDES}
${SERVER_INCLUDES}
@ -14,15 +24,22 @@ add_executable (server-test
http_async_port.cpp
http_base.cpp
http_sync_port.cpp
https_ports.cpp
main.cpp
rfc7231.cpp
server.cpp
service_list.cpp
ssl_certificate
write_msg.cpp
ws_async_port.cpp
ws_sync_port.cpp
ws_upgrade_service.cpp
wss_ports.cpp
${SSL_SOURCES}
)
target_link_libraries(server-test Beast)
if (OPENSSL_FOUND)
target_link_libraries(server-test ${OPENSSL_LIBRARIES})
endif()

View File

@ -20,4 +20,7 @@ exe server-test :
ws_async_port.cpp
ws_sync_port.cpp
ws_upgrade_service.cpp
#https_ports.cpp
#ssl_certificate.cpp
#ssl_stream.cpp
;

View File

@ -0,0 +1,13 @@
//
// 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)
//
#if BEAST_USE_OPENSSL
// Test that header file is self-contained.
#include "../../example/server-framework/https_ports.hpp"
#endif

View File

@ -0,0 +1,13 @@
//
// 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)
//
#if BEAST_USE_OPENSSL
// Test that header file is self-contained.
#include "../../example/server-framework/ssl_certificate.hpp"
#endif

View File

@ -0,0 +1,13 @@
//
// 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)
//
#if BEAST_USE_OPENSSL
// Test that header file is self-contained.
#include "../../example/server-framework/ssl_stream.hpp"
#endif

13
test/server/wss_ports.cpp Normal file
View File

@ -0,0 +1,13 @@
//
// 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)
//
#if BEAST_USE_OPENSSL
// Test that header file is self-contained.
#include "../../example/server-framework/wss_ports.hpp"
#endif