New server-framework, full featured server example:

A new server framework is introduced, allowing users to
quickly get off the ground. Example servers are refactored
to use the common framework.
This commit is contained in:
Vinnie Falco
2017-06-12 19:16:39 -07:00
parent e856c46864
commit fd9a13b11f
60 changed files with 3701 additions and 1376 deletions

View File

@@ -2,6 +2,7 @@ Version 60:
* String comparisons are public interfaces * String comparisons are public interfaces
* Fix response message type in async websocket accept * Fix response message type in async websocket accept
* New server-framework, full featured server example
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -174,11 +174,11 @@ file(GLOB_RECURSE EXTRAS_INCLUDES
${PROJECT_SOURCE_DIR}/extras/beast/*.ipp ${PROJECT_SOURCE_DIR}/extras/beast/*.ipp
) )
file(GLOB_RECURSE SERVER_INCLUDES
${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp
)
add_subdirectory (test) add_subdirectory (test)
add_subdirectory (test/core)
add_subdirectory (test/http)
add_subdirectory (test/websocket)
add_subdirectory (test/zlib)
add_subdirectory (example) add_subdirectory (example)

View File

@@ -79,7 +79,7 @@
[import ../example/doc/http_examples.hpp] [import ../example/doc/http_examples.hpp]
[import ../example/echo-op/echo_op.cpp] [import ../example/echo-op/echo_op.cpp]
[import ../example/http-client/http_client.cpp] [import ../example/http-client/http_client.cpp]
[import ../example/http-server/file_body.hpp] [import ../example/server-framework/file_body.hpp]
[import ../example/websocket-client/websocket_client.cpp] [import ../example/websocket-client/websocket_client.cpp]
[import ../test/core/doc_snippets.cpp] [import ../test/core/doc_snippets.cpp]

View File

@@ -42,9 +42,11 @@ standardized implementation of these protocols.
[heading Requirements] [heading Requirements]
This library is for programmers familiar with __Asio__. Users who [important
wish to use asynchronous interfaces should already know how to This library is for programmers familiar with __Asio__. Users who
create concurrent network programs using callbacks or coroutines. wish to use asynchronous interfaces should already know how to
create concurrent network programs using callbacks or coroutines.
]
Beast requires: Beast requires:

View File

@@ -5,7 +5,8 @@
file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
] ]
[section:example Example Programs] [section:example Examples]
[block'''<?dbhtml stop-chunking?>''']
These complete programs are intended to quickly impress upon readers the These complete programs are intended to quickly impress upon readers the
flavor of the library. Source code and build scripts for them are located flavor of the library. Source code and build scripts for them are located
@@ -13,7 +14,7 @@ in the examples directory.
[heading HTTP GET] [section HTTP Client]
Use HTTP to make a GET request to a website and print the response: Use HTTP to make a GET request to a website and print the response:
@@ -21,9 +22,33 @@ File: [repo_file example/http-client/http_client.cpp]
[example_http_client] [example_http_client]
[endsect]
[heading WebSocket]
[section HTTP Client (with SSL)]
This example demonstrates sending and receiving HTTP messages
over a TLS connection. Requires OpenSSL to build.
* [repo_file example/ssl/http_ssl_example.cpp]
[endsect]
[section HTTP Crawl]
This example retrieves the page at each of the most popular domains
as measured by Alexa.
* [repo_file example/http-crawl/http_crawl.cpp]
[endsect]
[section WebSocket Client]
Establish a WebSocket connection, send a message and receive the reply: Establish a WebSocket connection, send a message and receive the reply:
@@ -31,60 +56,47 @@ File: [repo_file example/websocket-client/websocket_client.cpp]
[example_websocket_client] [example_websocket_client]
[endsect]
[heading WebSocket Echo Server]
This example demonstrates both synchronous and asynchronous
WebSocket server implementations.
* [repo_file example/websocket-server/main.cpp]
* [repo_file example/websocket-server/websocket_async_echo_server.hpp]
* [repo_file example/websocket-server/websocket_sync_echo_server.hpp]
[heading Secure WebSocket] [section WebSocket Client (with SSL)]
Establish a WebSocket connection over an encrypted TLS connection, Establish a WebSocket connection over an encrypted TLS connection,
send a message and receive the reply. Requires OpenSSL to build. send a message and receive the reply. Requires OpenSSL to build.
* [repo_file example/ssl/websocket_ssl_example.cpp] * [repo_file example/ssl/websocket_ssl_example.cpp]
[endsect]
[heading HTTPS GET]
This example demonstrates sending and receiving HTTP messages
over a TLS connection. Requires OpenSSL to build.
* [repo_file example/ssl/http_ssl_example.cpp]
[heading HTTP Crawl] [section Server Framework]
This example retrieves the page at each of the most popular domains This is a complete program and framework of classes implementing
as measured by Alexa. a general purpose server that users may copy to use as the basis
for writing their own servers. It serves both HTTP and WebSocket.
* [repo_file example/http-crawl/http_crawl.cpp] * [repo_file example/server-framework/file_body.hpp]
* [repo_file example/server-framework/file_service.hpp]
* [repo_file example/server-framework/framework.hpp]
* [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/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/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]
[endsect]
[heading HTTP Server] [section Composed Operations]
This example demonstrates both synchronous and asynchronous server
implementations. It also provides an example of implementing a [*Body]
type, in `file_body`.
* [repo_file example/http-server/file_body.hpp]
* [repo_file example/http-server/http_async_server.hpp]
* [repo_file example/http-server/http_sync_server.hpp]
* [repo_file example/http-server/main.cpp]
[heading Composed Operations]
This program shows how to use Beast's network foundations to build a This program shows how to use Beast's network foundations to build a
composable asynchronous initiation function with associated composed composable asynchronous initiation function with associated composed
@@ -93,9 +105,11 @@ the example described in the Core Foundations document section.
* [repo_file example/echo-op/echo_op.cpp] * [repo_file example/echo-op/echo_op.cpp]
[endsect]
[heading Documentation Samples]
[section Documentation Samples]
Here are all of the example functions and classes presented Here are all of the example functions and classes presented
throughout the documentation, they can be included and used throughout the documentation, they can be included and used
@@ -105,6 +119,8 @@ in your program without modification
* [repo_file example/doc/http_examples.hpp] * [repo_file example/doc/http_examples.hpp]
[endsect]
[endsect] [endsect]

View File

@@ -15,6 +15,13 @@
left to the interfaces already existing on the underlying streams. left to the interfaces already existing on the underlying streams.
] ]
Library stream algorithms require a __socket__, __ssl_stream__, or other
__Stream__ object that has already established communication with an
endpoint. This example is provided as a reminder of how to work with
sockets:
[snippet_core_2]
Throughout this documentation identifiers with the following names have Throughout this documentation identifiers with the following names have
special meaning: special meaning:
@@ -52,11 +59,4 @@ special meaning:
]] ]]
] ]
Library stream algorithms require a __socket__, __ssl_stream__, or other
__Stream__ object that has already established communication with an
endpoint. This example is provided as a reminder of how to work with
sockets:
[snippet_core_2]
[endsect] [endsect]

View File

@@ -85,7 +85,7 @@ format using __Asio__. Specifically, the library provides:
[include 5_05_parser_streams.qbk] [include 5_05_parser_streams.qbk]
[include 5_06_serializer_buffers.qbk] [include 5_06_serializer_buffers.qbk]
[include 5_07_parser_buffers.qbk] [include 5_07_parser_buffers.qbk]
[include 5_08_custom_parsers.qbk] [include 5_08_custom_body.qbk]
[include 5_09_custom_body.qbk] [include 5_09_custom_parsers.qbk]
[endsect] [endsect]

BIN
doc/images/server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@@ -3,6 +3,5 @@
add_subdirectory (echo-op) add_subdirectory (echo-op)
add_subdirectory (http-client) add_subdirectory (http-client)
add_subdirectory (http-crawl) add_subdirectory (http-crawl)
add_subdirectory (http-server) add_subdirectory (server-framework)
add_subdirectory (websocket-client) add_subdirectory (websocket-client)
add_subdirectory (websocket-server)

View File

@@ -8,6 +8,5 @@
build-project echo-op ; build-project echo-op ;
build-project http-client ; build-project http-client ;
build-project http-crawl ; build-project http-crawl ;
build-project http-server ; build-project server-framework ;
build-project websocket-client ; build-project websocket-client ;
build-project websocket-server ;

View File

@@ -5,8 +5,8 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
// //
#ifndef URLS_LARGE_DATA_H_INCLUDED #ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#define URLS_LARGE_DATA_H_INCLUDED #define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP
#include <vector> #include <vector>

View File

@@ -1,18 +0,0 @@
# Part of Beast
GroupSources(extras/beast extras)
GroupSources(include/beast beast)
GroupSources(example/http-server "/")
add_executable (http-server
${BEAST_INCLUDES}
${EXTRAS_INCLUDES}
file_body.hpp
mime_type.hpp
http_async_server.hpp
http_sync_server.hpp
main.cpp
)
target_link_libraries(http-server Beast ${Boost_PROGRAM_OPTIONS_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} )

View File

@@ -1,323 +0,0 @@
//
// 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_HTTP_ASYNC_SERVER_H_INCLUDED
#define BEAST_EXAMPLE_HTTP_ASYNC_SERVER_H_INCLUDED
#include "file_body.hpp"
#include "mime_type.hpp"
#include <beast/http.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/core/multi_buffer.hpp>
#include <boost/asio.hpp>
#include <cstddef>
#include <cstdio>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
namespace beast {
namespace http {
class http_async_server
{
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
using req_type = request<string_body>;
using resp_type = response<file_body>;
std::mutex m_;
bool log_ = true;
boost::asio::io_service ios_;
boost::asio::ip::tcp::acceptor acceptor_;
socket_type sock_;
std::string root_;
std::vector<std::thread> thread_;
public:
http_async_server(endpoint_type const& ep,
std::size_t threads, std::string const& root)
: acceptor_(ios_)
, sock_(ios_)
, root_(root)
{
acceptor_.open(ep.protocol());
acceptor_.bind(ep);
acceptor_.listen(
boost::asio::socket_base::max_connections);
acceptor_.async_accept(sock_,
std::bind(&http_async_server::on_accept, this,
std::placeholders::_1));
thread_.reserve(threads);
for(std::size_t i = 0; i < threads; ++i)
thread_.emplace_back(
[&] { ios_.run(); });
}
~http_async_server()
{
error_code ec;
ios_.dispatch(
[&]{ acceptor_.close(ec); });
for(auto& t : thread_)
t.join();
}
template<class... Args>
void
log(Args const&... args)
{
if(log_)
{
std::lock_guard<std::mutex> lock(m_);
log_args(args...);
}
}
private:
template<class Stream, class Handler,
bool isRequest, class Body, class Fields>
class write_op
{
struct data
{
bool cont;
Stream& s;
message<isRequest, Body, Fields> m;
data(Handler& handler, Stream& s_,
message<isRequest, Body, Fields>&& m_)
: s(s_)
, m(std::move(m_))
{
using boost::asio::asio_handler_is_continuation;
cont = asio_handler_is_continuation(std::addressof(handler));
}
};
handler_ptr<data, Handler> d_;
public:
write_op(write_op&&) = default;
write_op(write_op const&) = default;
template<class DeducedHandler, class... Args>
write_op(DeducedHandler&& h, Stream& s, Args&&... args)
: d_(std::forward<DeducedHandler>(h),
s, std::forward<Args>(args)...)
{
(*this)(error_code{}, false);
}
void
operator()(error_code ec, bool again = true)
{
auto& d = *d_;
d.cont = d.cont || again;
if(! again)
{
beast::http::async_write(d.s, d.m, std::move(*this));
return;
}
d_.invoke(ec);
}
friend
void* asio_handler_allocate(
std::size_t size, write_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->d_.handler()));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_op* op)
{
using boost::asio::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->d_.handler()));
}
friend
bool asio_handler_is_continuation(write_op* op)
{
return op->d_->cont;
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(op->d_.handler()));
}
};
template<class Stream,
bool isRequest, class Body, class Fields,
class DeducedHandler>
static
void
async_write(Stream& stream, message<
isRequest, Body, Fields>&& msg,
DeducedHandler&& handler)
{
write_op<Stream, typename std::decay<DeducedHandler>::type,
isRequest, Body, Fields>{std::forward<DeducedHandler>(
handler), stream, std::move(msg)};
}
class peer : public std::enable_shared_from_this<peer>
{
int id_;
multi_buffer sb_;
socket_type sock_;
http_async_server& server_;
boost::asio::io_service::strand strand_;
req_type req_;
public:
peer(peer&&) = default;
peer(peer const&) = default;
peer& operator=(peer&&) = delete;
peer& operator=(peer const&) = delete;
peer(socket_type&& sock, http_async_server& server)
: sock_(std::move(sock))
, server_(server)
, strand_(sock_.get_io_service())
{
static int n = 0;
id_ = ++n;
}
void
fail(error_code ec, std::string what)
{
if(ec != boost::asio::error::operation_aborted &&
ec != error::end_of_stream)
server_.log("#", id_, " ", what, ": ", ec.message(), "\n");
}
void run()
{
do_read();
}
void do_read()
{
async_read(sock_, sb_, req_, strand_.wrap(
std::bind(&peer::on_read, shared_from_this(),
std::placeholders::_1)));
}
void on_read(error_code const& ec)
{
if(ec)
return fail(ec, "read");
auto path = req_.target().to_string();
if(path == "/")
path = "/index.html";
path = server_.root_ + path;
if(! boost::filesystem::exists(path))
{
response<string_body> res;
res.result(status::not_found);
res.version = req_.version;
res.insert(field::server, "http_async_server");
res.insert(field::content_type, "text/html");
res.body = "The file '" + path + "' was not found";
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));
return;
}
try
{
resp_type res;
res.result(status::ok);
res.version = req_.version;
res.insert(field::server, "http_async_server");
res.insert(field::content_type, mime_type(path));
res.body = path;
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));
}
catch(std::exception const& e)
{
response<string_body> res;
res.result(status::internal_server_error);
res.version = req_.version;
res.insert(field::server, "http_async_server");
res.insert(field::content_type, "text/html");
res.body =
std::string{"An internal error occurred"} + e.what();
res.prepare();
async_write(sock_, std::move(res),
std::bind(&peer::on_write, shared_from_this(),
std::placeholders::_1));
}
}
void on_write(error_code ec)
{
if(ec)
fail(ec, "write");
do_read();
}
};
void
log_args()
{
}
template<class Arg, class... Args>
void
log_args(Arg const& arg, Args const&... args)
{
std::cerr << arg;
log_args(args...);
}
void
fail(error_code ec, std::string what)
{
log(what, ": ", ec.message(), "\n");
}
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec)
return fail(ec, "accept");
socket_type sock(std::move(sock_));
acceptor_.async_accept(sock_,
std::bind(&http_async_server::on_accept, this,
std::placeholders::_1));
std::make_shared<peer>(std::move(sock), *this)->run();
}
};
} // http
} // beast
#endif

View File

@@ -1,215 +0,0 @@
//
// 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_HTTP_SYNC_SERVER_H_INCLUDED
#define BEAST_EXAMPLE_HTTP_SYNC_SERVER_H_INCLUDED
#include "file_body.hpp"
#include "mime_type.hpp"
#include <beast/http.hpp>
#include <beast/core/multi_buffer.hpp>
#include <boost/asio.hpp>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <thread>
#include <utility>
#include <iostream>
namespace beast {
namespace http {
class http_sync_server
{
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
using req_type = request<string_body>;
using resp_type = response<file_body>;
bool log_ = true;
std::mutex m_;
boost::asio::io_service ios_;
socket_type sock_;
boost::asio::ip::tcp::acceptor acceptor_;
std::string root_;
std::thread thread_;
public:
http_sync_server(endpoint_type const& ep,
std::string const& root)
: sock_(ios_)
, acceptor_(ios_)
, root_(root)
{
acceptor_.open(ep.protocol());
acceptor_.bind(ep);
acceptor_.listen(
boost::asio::socket_base::max_connections);
acceptor_.async_accept(sock_,
std::bind(&http_sync_server::on_accept, this,
std::placeholders::_1));
thread_ = std::thread{[&]{ ios_.run(); }};
}
~http_sync_server()
{
error_code ec;
ios_.dispatch(
[&]{ acceptor_.close(ec); });
thread_.join();
}
template<class... Args>
void
log(Args const&... args)
{
if(log_)
{
std::lock_guard<std::mutex> lock(m_);
log_args(args...);
}
}
private:
void
log_args()
{
}
template<class Arg, class... Args>
void
log_args(Arg const& arg, Args const&... args)
{
std::cerr << arg;
log_args(args...);
}
void
fail(error_code ec, std::string what)
{
log(what, ": ", ec.message(), "\n");
}
void
fail(int id, error_code const& ec)
{
if(ec != boost::asio::error::operation_aborted &&
ec != error::end_of_stream)
log("#", id, " ", ec.message(), "\n");
}
struct lambda
{
int id;
http_sync_server& self;
socket_type sock;
boost::asio::io_service::work work;
lambda(int id_, http_sync_server& self_,
socket_type&& sock_)
: id(id_)
, self(self_)
, sock(std::move(sock_))
, work(sock.get_io_service())
{
}
void operator()()
{
self.do_peer(id, std::move(sock));
}
};
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec)
return fail(ec, "accept");
static int id_ = 0;
std::thread{lambda{++id_, *this, std::move(sock_)}}.detach();
acceptor_.async_accept(sock_,
std::bind(&http_sync_server::on_accept, this,
std::placeholders::_1));
}
void
do_peer(int id, socket_type&& sock0)
{
socket_type sock(std::move(sock0));
multi_buffer b;
error_code ec;
for(;;)
{
req_type req;
http::read(sock, b, req, ec);
if(ec)
break;
auto path = req.target().to_string();
if(path == "/")
path = "/index.html";
path = root_ + path;
if(! boost::filesystem::exists(path))
{
response<string_body> res;
res.result(status::not_found);
res.version = req.version;
res.insert(field::server, "http_sync_server");
res.insert(field::content_type, "text/html");
res.body = "The file '" + path + "' was not found";
res.prepare();
write(sock, res, ec);
if(ec)
break;
return;
}
try
{
resp_type res;
res.result(status::ok);
res.reason("OK");
res.version = req.version;
res.insert(field::server, "http_sync_server");
res.insert(field::content_type, mime_type(path));
res.body = path;
res.prepare();
write(sock, res, ec);
if(ec)
break;
}
catch(std::exception const& e)
{
response<string_body> res;
res.result(status::internal_server_error);
res.reason("Internal Error");
res.version = req.version;
res.insert(field::server, "http_sync_server");
res.insert(field::content_type, "text/html");
res.body =
std::string{"An internal error occurred: "} + e.what();
res.prepare();
write(sock, res, ec);
if(ec)
break;
}
}
fail(id, ec);
}
};
} // http
} // beast
#endif

View File

@@ -1,61 +0,0 @@
//
// 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)
//
#include "http_async_server.hpp"
#include "http_sync_server.hpp"
#include <beast/test/sig_wait.hpp>
#include <boost/program_options.hpp>
#include <iostream>
int main(int ac, char const* av[])
{
using namespace beast::http;
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("root,r", po::value<std::string>()->default_value("."),
"Set the root directory for serving files")
("port,p", po::value<std::uint16_t>()->default_value(8080),
"Set the port number for the server")
("ip", po::value<std::string>()->default_value("0.0.0.0"),
"Set the IP address to bind to, \"0.0.0.0\" for all")
("threads,n", po::value<std::size_t>()->default_value(4),
"Set the number of threads to use")
("sync,s", "Launch a synchronous server")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
std::string root = vm["root"].as<std::string>();
std::uint16_t port = vm["port"].as<std::uint16_t>();
std::string ip = vm["ip"].as<std::string>();
std::size_t threads = vm["threads"].as<std::size_t>();
bool sync = vm.count("sync") > 0;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
endpoint_type ep{address_type::from_string(ip), port};
if(sync)
{
http_sync_server server(ep, root);
beast::test::sig_wait();
}
else
{
http_async_server server(ep, threads, root);
beast::test::sig_wait();
}
}

View File

@@ -1,52 +0,0 @@
//
// 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_HTTP_MIME_TYPE_H_INCLUDED
#define BEAST_EXAMPLE_HTTP_MIME_TYPE_H_INCLUDED
#include <beast/core/string.hpp>
#include <boost/filesystem/path.hpp>
#include <string>
namespace beast {
namespace http {
// Return the Mime-Type for a given file extension
template<class = void>
string_view
mime_type(std::string const& path)
{
auto const ext =
boost::filesystem::path{path}.extension().string();
if(ext == ".txt") return "text/plain";
if(ext == ".htm") return "text/html";
if(ext == ".html") return "text/html";
if(ext == ".php") return "text/html";
if(ext == ".css") return "text/css";
if(ext == ".js") return "application/javascript";
if(ext == ".json") return "application/json";
if(ext == ".xml") return "application/xml";
if(ext == ".swf") return "application/x-shockwave-flash";
if(ext == ".flv") return "video/x-flv";
if(ext == ".png") return "image/png";
if(ext == ".jpe") return "image/jpeg";
if(ext == ".jpeg") return "image/jpeg";
if(ext == ".jpg") return "image/jpeg";
if(ext == ".gif") return "image/gif";
if(ext == ".bmp") return "image/bmp";
if(ext == ".ico") return "image/vnd.microsoft.icon";
if(ext == ".tiff") return "image/tiff";
if(ext == ".tif") return "image/tiff";
if(ext == ".svg") return "image/svg+xml";
if(ext == ".svgz") return "image/svg+xml";
return "application/text";
}
} // http
} // beast
#endif

View File

@@ -0,0 +1,17 @@
# Part of Beast
GroupSources(include/beast beast)
GroupSources(example/server-framework "/")
add_executable (server-framework
${BEAST_INCLUDES}
${SERVER_INCLUDES}
main.cpp
)
target_link_libraries(
server-framework
Beast
${Boost_PROGRAM_OPTIONS_LIBRARY}
${Boost_FILESYSTEM_LIBRARY})

View File

@@ -5,6 +5,6 @@
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
# #
exe http-server : exe server-framework :
main.cpp main.cpp
; ;

View File

@@ -0,0 +1,68 @@
<img width="880" height = "80" alt = "Beast"
src="https://raw.githubusercontent.com/vinniefalco/Beast/master/doc/images/readme.png">
# HTTP and WebSocket built on Boost.Asio in C++11
## Server-Framework
This example is a complete, multi-threaded server built with Beast.
It contains the following components
* WebSocket ports (synchronous and asynchronous)
- Echoes back any message received
* HTTP ports (synchronous and asynchronous)
- Serves files from a configurable directory on GET request
- Responds to HEAD requests with the appropriate result
- Routes WebSocket Upgrade requests to a WebSocket port
- Handles Expect: 100-continue
- Supports pipelined requests
The server is designed to use modular components that users may simply copy
into their own project to get started quickly. Two concepts are introduced:
## PortHandler
The **PortHandler** concept defines an algorithm for handling incoming
connections received on a listening socket. The example comes with four
port handlers:
* `http_sync_port` Serves HTTP content using synchronous Beast calls
* `http_async_port` Serves HTTP content using asynchronous Beast calls
* `ws_sync_port` A synchronous WebSocket echo server using synchronous Beast calls
* `ws_async_port` An asynchronous WebSocket echo server using synchronous Beast calls
A port handler takes the stream object resulting form an incoming connection
request and constructs a handler-specific connection object which provides
the desired behavior.
## Service
The HTTP ports which come with the example have a system built in which allows
installation of framework and user-defined "HTTP services". These services
inform connections using the port on how to handle specific requests. This is
similar in concept to an "HTTP router" which is an element of most modern
servers.
These HTTP services are represented by the **Service** concept, and managed
in a container holding a type-list, called a `service_list`. Each HTTP port
allows the sevice list to be defined at compile-time and initialized at run
time. The framework provides these services:
* `file_service` Produces HTTP responses delivering files from a system path
* `ws_upgrade_service` Transports a connection requesting a WebSocket Upgrade
to a websocket port handler.
## Relationship
This diagram shows the relationship of the server object, to the four
ports created in the example program, and the HTTP services contained by
the HTTP ports:
<img width="880" height = "344" alt = "ServerFramework"
src="https://raw.githubusercontent.com/vinniefalco/Beast/server/doc/images/server.png">

View File

@@ -5,8 +5,8 @@
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
// //
#ifndef BEAST_EXAMPLE_FILE_BODY_H_INCLUDED #ifndef BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED #define BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP
#include <beast/core/error.hpp> #include <beast/core/error.hpp>
#include <beast/http/message.hpp> #include <beast/http/message.hpp>
@@ -18,11 +18,18 @@
#include <cstdint> #include <cstdint>
#include <utility> #include <utility>
namespace beast {
namespace http {
//[example_http_file_body_1 //[example_http_file_body_1
/** A message body represented by a file on the filesystem.
Messages with this type have bodies represented by a
file on the file system. When parsing a message using
this body type, the data is stored in the file pointed
to by the path, which must be writable. When serializing,
the implementation will read the file and present those
octets as the body content. This may be used to serve
content from a directory as part of a web service.
*/
struct file_body struct file_body
{ {
/** The type of the @ref message::body member. /** The type of the @ref message::body member.
@@ -71,6 +78,7 @@ struct file_body
//[example_http_file_body_2 //[example_http_file_body_2
inline
std::uint64_t std::uint64_t
file_body:: file_body::
size(value_type const& v) size(value_type const& v)
@@ -111,7 +119,7 @@ public:
// always have the `file_body` as the body type. // always have the `file_body` as the body type.
// //
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
reader(message<isRequest, file_body, Fields> const& m); reader(beast::http::message<isRequest, file_body, Fields> const& m);
// Destructor // Destructor
~reader(); ~reader();
@@ -120,7 +128,7 @@ public:
// of the body is started. // of the body is started.
// //
void void
init(error_code& ec); init(beast::error_code& ec);
// This function is called zero or more times to // This function is called zero or more times to
// retrieve buffers. A return value of `boost::none` // retrieve buffers. A return value of `boost::none`
@@ -129,7 +137,7 @@ public:
// to serialize, and a `bool` indicating whether // to serialize, and a `bool` indicating whether
// or not there may be additional buffers. // or not there may be additional buffers.
boost::optional<std::pair<const_buffers_type, bool>> boost::optional<std::pair<const_buffers_type, bool>>
get(error_code& ec); get(beast::error_code& ec);
// This function is called when reading is complete. // This function is called when reading is complete.
// It is an opportunity to perform any final actions // It is an opportunity to perform any final actions
@@ -138,7 +146,7 @@ public:
// destructors, since an exception thrown from there // destructors, since an exception thrown from there
// would terminate the program. // would terminate the program.
void void
finish(error_code& ec); finish(beast::error_code& ec);
}; };
//] //]
@@ -151,7 +159,7 @@ public:
// //
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
file_body::reader:: file_body::reader::
reader(message<isRequest, file_body, Fields> const& m) reader(beast::http::message<isRequest, file_body, Fields> const& m)
: path_(m.body) : path_(m.body)
{ {
} }
@@ -164,7 +172,7 @@ reader(message<isRequest, file_body, Fields> const& m)
inline inline
void void
file_body::reader:: file_body::reader::
init(error_code& ec) init(beast::error_code& ec)
{ {
// Attempt to open the file for reading // Attempt to open the file for reading
file_ = fopen(path_.string().c_str(), "rb"); file_ = fopen(path_.string().c_str(), "rb");
@@ -173,7 +181,7 @@ init(error_code& ec)
{ {
// Convert the old-school `errno` into // Convert the old-school `errno` into
// an error code using the system category. // an error code using the system category.
ec = error_code{errno, system_category()}; ec = beast::error_code{errno, beast::system_category()};
return; return;
} }
@@ -191,7 +199,7 @@ init(error_code& ec)
inline inline
auto auto
file_body::reader:: file_body::reader::
get(error_code& ec) -> get(beast::error_code& ec) ->
boost::optional<std::pair<const_buffers_type, bool>> boost::optional<std::pair<const_buffers_type, bool>>
{ {
// Calculate the smaller of our buffer size, // Calculate the smaller of our buffer size,
@@ -212,7 +220,7 @@ get(error_code& ec) ->
if(ferror(file_)) if(ferror(file_))
{ {
// Convert old-school `errno` to error_code // Convert old-school `errno` to error_code
ec = error_code(errno, system_category()); ec = beast::error_code(errno, beast::system_category());
return boost::none; return boost::none;
} }
@@ -242,7 +250,7 @@ get(error_code& ec) ->
inline inline
void void
file_body::reader:: file_body::reader::
finish(error_code& ec) finish(beast::error_code& ec)
{ {
// Functions which pass back errors are // Functions which pass back errors are
// responsible for clearing the error code. // responsible for clearing the error code.
@@ -281,20 +289,20 @@ public:
// //
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
explicit explicit
writer(message<isRequest, file_body, Fields>& m); writer(beast::http::message<isRequest, file_body, Fields>& m);
// This function is called once before parsing // This function is called once before parsing
// of the body is started. // of the body is started.
// //
void void
init(boost::optional<std::uint64_t> const& content_length, error_code& ec); init(boost::optional<std::uint64_t> const& content_length, beast::error_code& ec);
// This function is called one or more times to store // This function is called one or more times to store
// buffer sequences corresponding to the incoming body. // buffer sequences corresponding to the incoming body.
// //
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
put(ConstBufferSequence const& buffers, error_code& ec); put(ConstBufferSequence const& buffers, beast::error_code& ec);
// This function is called when writing is complete. // This function is called when writing is complete.
// It is an opportunity to perform any final actions // It is an opportunity to perform any final actions
@@ -304,7 +312,7 @@ public:
// would terminate the program. // would terminate the program.
// //
void void
finish(error_code& ec); finish(beast::error_code& ec);
// Destructor. // Destructor.
// //
@@ -320,7 +328,7 @@ public:
// Just stash a reference to the path so we can open the file later. // Just stash a reference to the path so we can open the file later.
template<bool isRequest, class Fields> template<bool isRequest, class Fields>
file_body::writer:: file_body::writer::
writer(message<isRequest, file_body, Fields>& m) writer(beast::http::message<isRequest, file_body, Fields>& m)
: path_(m.body) : path_(m.body)
{ {
} }
@@ -334,7 +342,7 @@ writer(message<isRequest, file_body, Fields>& m)
inline inline
void void
file_body::writer:: file_body::writer::
init(boost::optional<std::uint64_t> const& content_length, error_code& ec) init(boost::optional<std::uint64_t> const& content_length, beast::error_code& ec)
{ {
// Attempt to open the file for writing // Attempt to open the file for writing
file_ = fopen(path_.string().c_str(), "wb"); file_ = fopen(path_.string().c_str(), "wb");
@@ -343,7 +351,7 @@ init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
{ {
// Convert the old-school `errno` into // Convert the old-school `errno` into
// an error code using the system category. // an error code using the system category.
ec = error_code{errno, system_category()}; ec = beast::error_code{errno, beast::system_category()};
return; return;
} }
@@ -356,7 +364,7 @@ init(boost::optional<std::uint64_t> const& content_length, error_code& ec)
template<class ConstBufferSequence> template<class ConstBufferSequence>
void void
file_body::writer:: file_body::writer::
put(ConstBufferSequence const& buffers, error_code& ec) put(ConstBufferSequence const& buffers, beast::error_code& ec)
{ {
// Loop over all the buffers in the sequence, // Loop over all the buffers in the sequence,
// and write each one to the file. // and write each one to the file.
@@ -372,7 +380,7 @@ put(ConstBufferSequence const& buffers, error_code& ec)
if(ferror(file_)) if(ferror(file_))
{ {
// Convert old-school `errno` to error_code // Convert old-school `errno` to error_code
ec = error_code(errno, system_category()); ec = beast::error_code(errno, beast::system_category());
return; return;
} }
} }
@@ -385,8 +393,10 @@ put(ConstBufferSequence const& buffers, error_code& ec)
inline inline
void void
file_body::writer:: file_body::writer::
finish(error_code& ec) finish(beast::error_code& ec)
{ {
// This has to be cleared before returning, to
// indicate no error. The specification requires it.
ec = {}; ec = {};
} }
@@ -405,7 +415,4 @@ file_body::writer::
//] //]
} // http
} // beast
#endif #endif

View File

@@ -0,0 +1,248 @@
//
// 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_FILE_SERVICE_HPP
#define BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP
#include "file_body.hpp"
#include "framework.hpp"
#include <beast/core/string.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <boost/filesystem/path.hpp>
#include <string>
namespace framework {
/** An HTTP service which delivers files from a root directory.
This service will accept GET and HEAD requests for files,
and deliver them as responses. The service constructs with
the location on the file system to act as the root for the
tree of files to serve.
Meets the requirements of @b Service
*/
class file_service
{
// The path to serve files from
boost::filesystem::path root_;
// The name to use in the Server HTTP field
std::string server_;
public:
/** Constructor
@param root A path with files to serve. A GET request
for "/" will try to deliver the file "/index.html".
@param The string to use in the Server HTTP field.
*/
explicit
file_service(
boost::filesystem::path const& root,
beast::string_view server)
: root_(root)
, server_(server)
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
@note This is needed for to meet the requirements for @b Service
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Process a request.
@note This is needed for to meet the requirements for @b Service
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&&,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
{
// Check the method and take action
switch(req.method())
{
case beast::http::verb::get:
{
// For GET requests we deliver the actual file
boost::filesystem::path rel_path(req.target().to_string());
// Give them the root web page if the target is "/"
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
// Make sure the file is there
if(boost::filesystem::exists(full_path))
{
// Send the file
send(get(req, full_path));
}
else
{
// Send a Not Found result
send(not_found(req, rel_path));
}
// Indicate that we handled the request
return true;
}
case beast::http::verb::head:
{
// We are just going to give them the headers they
// would otherwise get, but without the body.
boost::filesystem::path rel_path(req.target().to_string());
if(rel_path == "/")
rel_path = "/index.html";
// Calculate full path from root
boost::filesystem::path full_path = root_ / rel_path;
// Make sure the file is there
if(! boost::filesystem::exists(full_path))
{
// Send a HEAD response
send(head(req, full_path));
}
else
{
// Send a Not Found result
send(not_found(req, rel_path));
}
// Indicate that we handled the request
return true;
}
default:
break;
}
// We didn't handle this request, so return false to
// inform the service list to try the next service.
//
return false;
}
private:
// Return a reasonable mime type based on the extension of a file.
//
beast::string_view
mime_type(boost::filesystem::path const& path) const
{
using beast::iequals;
auto const ext = path.extension().string();
if(iequals(ext, ".txt")) return "text/plain";
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, ".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";
}
// Return an HTTP Not Found response
//
template<class Body, class Fields>
beast::http::response<beast::http::string_body>
not_found(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& rel_path) const
{
beast::http::response<beast::http::string_body> res;
res.version = req.version;
res.result(beast::http::status::not_found);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, "text/html");
res.body = "The file was not found"; // VFALCO append rel_path
res.prepare();
return res;
}
// Return a file response to an HTTP GET request
//
template<class Body, class Fields>
beast::http::response<file_body>
get(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<file_body> res;
res.version = req.version;
res.result(beast::http::status::ok);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
res.body = full_path;
res.prepare();
return res;
}
// Return a response to an HTTP HEAD request
//
template<class Body, class Fields>
beast::http::response<beast::http::empty_body>
head(beast::http::request<Body, Fields> const& req,
boost::filesystem::path const& full_path) const
{
beast::http::response<beast::http::empty_body> res;
res.version = req.version;
res.result(beast::http::status::ok);
res.set(beast::http::field::server, server_);
res.set(beast::http::field::content_type, mime_type(full_path));
// Set the Content-Length field manually. We don't have a body,
// but this is a response to a HEAD request so we include the
// content length anyway.
//
res.set(beast::http::field::content_length, file_body::size(full_path));
return res;
}
};
} // framework
#endif

View File

@@ -0,0 +1,53 @@
//
// 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_FRAMEWORK_HPP
#define BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP
#include <boost/asio/io_service.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/system/error_code.hpp>
#include <utility>
/** The framework namespace
This namespace contains all of the identifiers in the
server-framework system. Here we import some commonly
used types for brevity.
*/
namespace framework {
// This is our own base from member idiom written for C++11
// which is simple and works around a glitch in boost's version.
//
template<class T>
class base_from_member
{
public:
template<class... Args>
explicit
base_from_member(Args&&... args)
: member(std::forward<Args>(args)...)
{
}
protected:
T member;
};
using error_code = boost::system::error_code;
using socket_type = boost::asio::ip::tcp::socket;
using strand_type = boost::asio::io_service::strand;
using address_type = boost::asio::ip::address_v4;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using acceptor_type = boost::asio::ip::tcp::acceptor;
using io_service_type = boost::asio::io_service;
} // framework
#endif

View File

@@ -0,0 +1,550 @@
//
// 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_HTTP_ASYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP
#include "server.hpp"
#include "http_base.hpp"
#include "rfc7231.hpp"
#include "service_list.hpp"
#include "write_msg.hpp"
#include <beast/core/flat_buffer.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <beast/http/write.hpp>
#include <memory>
#include <utility>
#include <ostream>
namespace framework {
// Base class for a type-erased, queued asynchronous HTTP write
//
struct queued_http_write
{
virtual ~queued_http_write() = default;
// When invoked, performs the write operation.
virtual void invoke() = 0;
};
/* This implements an object which, when invoked, writes an HTTP
message asynchronously to the stream. These objects are used
to form a queue of outgoing messages for pipelining. The base
class type-erases the message so the queue can hold messsages
of different types.
*/
template<
class Stream,
bool isRequest, class Body, class Fields,
class Handler>
class queued_http_write_impl : public queued_http_write
{
Stream& stream_;
beast::http::message<isRequest, Body, Fields> msg_;
Handler handler_;
public:
// Constructor.
//
// Ownership of the message is transferred into the object
//
template<class DeducedHandler>
queued_http_write_impl(
Stream& stream,
beast::http::message<isRequest, Body, Fields>&& msg,
DeducedHandler&& handler)
: stream_(stream)
, msg_(std::move(msg))
, handler_(std::forward<DeducedHandler>(handler))
{
}
// Writes the stored message
void
invoke() override
{
async_write_msg(
stream_,
std::move(msg_),
std::move(handler_));
}
};
// This helper function creates a queued_http_write
// object and returns it inside a unique_ptr.
//
template<
class Stream,
bool isRequest, class Body, class Fields,
class Handler>
std::unique_ptr<queued_http_write>
make_queued_http_write(
Stream& stream,
beast::http::message<isRequest, Body, Fields>&& msg,
Handler&& handler)
{
return std::unique_ptr<queued_http_write>{
new queued_http_write_impl<
Stream,
isRequest, Body, Fields,
typename std::decay<Handler>::type>{
stream,
std::move(msg),
std::forward<Handler>(handler)}};
}
//------------------------------------------------------------------------------
/** An asynchronous HTTP connection.
This base class implements an HTTP connection object using
asynchronous calls.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derivd class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class async_http_con : public http_base
{
// This function lets us access members of the derived class
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The stream to use for logging
std::ostream& log_;
// The services configured for the port
service_list<Services...> const& services_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// The buffer for performing reads
beast::flat_buffer buffer_;
// The parser for reading the requests
boost::optional<beast::http::request_parser<beast::http::dynamic_body>> parser_;
// This is the queue of outgoing messages
std::vector<std::unique_ptr<queued_http_write>> queue_;
// Indicates if we have a write active.
bool writing_ = false;
protected:
// The strand makes sure that our data is
// accessed from only one thread at a time.
//
strand_type strand_;
public:
// Constructor
async_http_con(
beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
std::size_t id,
endpoint_type const& ep)
: http_base(server_name)
, log_(log)
, services_(services)
, id_(id)
, ep_(ep)
// The buffer has a limit of 8192, otherwise
// the server is vulnerable to a buffer attack.
//
, buffer_(8192)
, strand_(impl().stream().get_io_service())
{
}
// Called by the port after creating the object
void
run()
{
// Start reading the header for the first request.
//
do_read_header();
}
private:
// Perform an asynchronous read for the next request header
//
void
do_read_header()
{
// On each read the parser needs to be destroyed and
// recreated. We store it in a boost::optional to
// achieve that.
//
// Arguments passed to the parser constructor are
// forwarded to the message object. A single argument
// is forwarded to the body constructor.
//
// We construct the dynamic body with a 1MB limit
// to prevent vulnerability to buffer attacks.
//
parser_.emplace(1024 * 1024);
// Read just the header
beast::http::async_read_header(
impl().stream(),
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con::on_read_header,
impl().shared_from_this(),
std::placeholders::_1)));
}
// This lambda is passed to the service list to handle
// the case of sending request objects of varying types.
// In C++14 this is more easily accomplished using a generic
// lambda, but we want C+11 compatibility so we manually
// write the lambda out.
//
struct send_lambda
{
// holds "this"
async_http_con& self_;
public:
// capture "this"
explicit
send_lambda(async_http_con& self)
: self_(self)
{
}
// sends a message
template<class Body, class Fields>
void
operator()(beast::http::response<Body, Fields>&& res) const
{
self_.do_write(std::move(res));
}
};
// Called when the header has been read in
void
on_read_header(error_code ec)
{
// 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.
//
if(ec)
return fail("on_read", ec);
// The parser holds the request object,
// at this point it only has the header in it.
auto& req = parser_->get();
send_lambda send{*this};
// See if they are specifying Expect: 100-continue
//
if(rfc7231::is_expect_100_continue(req))
{
// They want to know if they should continue,
// so send the appropriate response.
//
send(this->continue_100(req));
}
// Read the rest of the message, if any.
//
beast::http::async_read(
impl().stream(),
buffer_,
*parser_,
strand_.wrap(std::bind(
&async_http_con::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message is complete
void
on_read(error_code ec)
{
// Grab a reference to the request again
auto& req = parser_->get();
// Create a variable for our send
// lambda since we use it more than once.
//
send_lambda send{*this};
// Give each services a chance to handle the request
//
if(! services_.respond(
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));
}
else
{
// See if the service that handled the
// response took ownership of the stream.
//
if(! impl().stream().lowest_layer().is_open())
{
// They took ownership so just return and
// let this async_http_con object get destroyed.
//
return;
}
}
// VFALCO Right now we do unlimited pipelining which
// can lead to unbounded resource consumption.
// A more sophisticated server might only issue
// this read when the queue is below some limit.
//
// Start reading another header
do_read_header();
}
// This function either queues a message or
// starts writing it if no other writes are taking place.
//
template<class Body, class Fields>
void
do_write(beast::http::response<Body, Fields>&& res)
{
// See if a write is in progress
if(! writing_)
{
// An assert or two to keep things sane when
// writing asynchronous code can be very helpful.
BOOST_ASSERT(queue_.empty());
// We're going to be writing so set the flag
writing_ = true;
// And now perform the write
return async_write_msg(
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Queue is not empty, so append this message to the queue.
// It will be sent late when the queue empties.
//
queue_.emplace_back(make_queued_http_write(
impl().stream(),
std::move(res),
strand_.wrap(std::bind(
&async_http_con::on_write,
impl().shared_from_this(),
std::placeholders::_1))));
}
// Called when a message finishes writing
void
on_write(error_code ec)
{
// Make sure our state is what we think it is
BOOST_ASSERT(writing_);
// On failure just log and return
if(ec)
return fail("on_write", ec);
// See if the queue is empty
if(queue_.empty())
{
// Queue was empty so clear the flag...
writing_ = false;
// ...and return
return;
}
// Queue was not empty, so invoke the object
// at the head of the queue. This will start
// another wrte.
queue_.front()->invoke();
// 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;
}
}
};
//------------------------------------------------------------------------------
// This class represents an asynchronous HTTP connection which
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class async_http_con_plain
// 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_http_con_plain<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<socket_type>
// Declare this base last now that everything else got set up first.
//
, public async_http_con<async_http_con_plain<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
async_http_con_plain(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, async_http_con<async_http_con_plain<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.
//
socket_type&
stream()
{
return this->member;
}
};
//------------------------------------------------------------------------------
/* An asynchronous HTTP 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 http_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_;
public:
// Constructor
http_async_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** 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 a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<async_http_con_plain<Services...>>(
std::move(sock),
"http_async_port",
log_,
services_,
instance_.next_id(),
ep
)->run();
}
};
} // framework
#endif

View File

@@ -0,0 +1,77 @@
//
// 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_HTTP_BASE_HPP
#define BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP
#include <beast/core/string.hpp>
#include <beast/http/empty_body.hpp>
#include <beast/http/message.hpp>
#include <beast/http/string_body.hpp>
#include <memory>
#include <utility>
#include <ostream>
namespace framework {
/* Base class for HTTP PortHandlers
This holds the server name and has some shared
routines for building typical HTTP responses.
*/
class http_base
{
beast::string_view server_name_;
public:
explicit
http_base(beast::string_view server_name)
: server_name_(server_name)
{
}
protected:
// Returns a bad request result response
//
template<class Body, class Fields>
beast::http::response<beast::http::string_body>
bad_request(beast::http::request<Body, Fields> const& req) const
{
beast::http::response<beast::http::string_body> res;
// Match the version to the request
res.version = req.version;
res.result(beast::http::status::bad_request);
res.set(beast::http::field::server, server_name_);
res.set(beast::http::field::content_type, "text/html");
res.body = "Bad request";
res.prepare();
return res;
}
// Returns a 100 Continue result response
//
template<class Body, class Fields>
beast::http::response<beast::http::empty_body>
continue_100(beast::http::request<Body, Fields> const& req) const
{
beast::http::response<beast::http::empty_body> res;
// Match the version to the request
res.version = req.version;
res.result(beast::http::status::continue_);
res.set(beast::http::field::server, server_name_);
return res;
}
};
} // framework
#endif

View File

@@ -0,0 +1,354 @@
//
// 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_HTTP_SYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP
#include "server.hpp"
#include "http_base.hpp"
#include "rfc7231.hpp"
#include "service_list.hpp"
#include "write_msg.hpp"
#include <beast/core/flat_buffer.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/http/dynamic_body.hpp>
#include <beast/http/parser.hpp>
#include <beast/http/read.hpp>
#include <beast/http/string_body.hpp>
#include <beast/http/write.hpp>
#include <memory>
#include <utility>
#include <ostream>
#include <thread>
namespace framework {
/** A synchronous HTTP connection.
This base class implements an HTTP connection object using
synchronous calls.
It uses the Curiously Recurring Template pattern (CRTP) where
we refer to the derivd class in order to access the stream object
to use for reading and writing. This lets the same class be used
for plain and SSL stream objects.
@tparam Services The list of services this connection will support.
*/
template<class Derived, class... Services>
class sync_http_con
: public http_base
{
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The stream to use for logging
std::ostream& log_;
// The services configured for the port
service_list<Services...> const& services_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// The buffer for performing reads
beast::flat_buffer buffer_;
public:
/// Constructor
sync_http_con(
beast::string_view server_name,
std::ostream& log,
service_list<Services...> const& services,
std::size_t id,
endpoint_type const& ep)
: http_base(server_name)
, log_(log)
, services_(services)
, id_(id)
, ep_(ep)
// The buffer has a limit of 8192, otherwise
// the server is vulnerable to a buffer attack.
//
, buffer_(8192)
{
}
void
run()
{
// Bind a shared pointer into the lambda for the
// thread, so the sync_http_con is destroyed after
// the thread function exits.
//
std::thread{
&sync_http_con::do_run,
impl().shared_from_this()
}.detach();
}
private:
// This lambda is passed to the service list to handle
// the case of sending request objects of varying types.
// In C++14 this is more easily accomplished using a generic
// lambda, but we want C+11 compatibility so we manually
// write the lambda out.
//
struct send_lambda
{
// holds "this"
sync_http_con& self_;
// holds the captured error code
error_code& ec_;
public:
// Constructor
//
// Capture "this" and "ec"
//
send_lambda(sync_http_con& self, error_code& ec)
: self_(self)
, ec_(ec)
{
}
// Sends a message
//
// Since this is a synchronous implementation we
// just call the write function and block.
//
template<class Body, class Fields>
void
operator()(
beast::http::response<Body, Fields>&& res) const
{
beast::http::write(self_.impl().stream(), res, ec_);
}
};
void
do_run()
{
// The main connection loop, we alternate between
// reading a request and sending a response. On
// error we log and return, which destroys the thread
// and the stream (thus closing the connection)
//
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.
//
// We construct the dynamic body with a 1MB limit
// to prevent vulnerability to buffer attacks.
//
beast::http::request_parser<beast::http::dynamic_body> parser(1024* 1024);
// Read the header first
beast::http::read_header(impl().stream(), buffer_, parser, ec);
if(ec)
return fail("on_read", ec);
send_lambda send{*this, ec};
auto& req = parser.get();
// See if they are specifying Expect: 100-continue
//
if(rfc7231::is_expect_100_continue(req))
{
// They want to know if they should continue,
// so send the appropriate response synchronously.
//
send(this->continue_100(req));
}
// 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
//
if(! services_.respond(
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));
}
else
{
// See if the service that handled the
// response took ownership of the stream.
if(! impl().stream().is_open())
{
// They took ownership so just return and
// let this sync_http_con 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;
}
}
};
//------------------------------------------------------------------------------
// This class represents a synchronous HTTP connection which
// uses a plain TCP/IP socket (no encryption) as the stream.
//
template<class... Services>
class sync_http_con_plain
// 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...>>
// 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<socket_type>
// Declare this base last now that everything else got set up first.
//
, public sync_http_con<sync_http_con_plain<Services...>, Services...>
{
public:
// Construct the plain connection.
//
template<class... Args>
sync_http_con_plain(
socket_type&& sock,
Args&&... args)
: base_from_member<socket_type>(std::move(sock))
, sync_http_con<sync_http_con_plain<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.
//
socket_type&
stream()
{
return this->member;
}
};
//------------------------------------------------------------------------------
/* A synchronous HTTP 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 http_sync_port
{
server& instance_;
std::ostream& log_;
service_list<Services...> services_;
public:
// Constructor
http_sync_port(
server& instance,
std::ostream& log)
: instance_(instance)
, log_(log)
{
}
/** Initialize a service
Every service in the list must be initialized exactly once.
@param ec Set to the error, if any occurred
@param args Optional arguments forwarded to the service
constructor.
@tparam Index The 0-based index of the service to initialize.
*/
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 a plain http connection object
// and transfer ownership of the socket.
//
std::make_shared<sync_http_con_plain<Services...>>(
std::move(sock),
"http_sync_port",
log_,
services_,
instance_.next_id(),
ep
)->run();
}
};
} // framework
#endif

View File

@@ -0,0 +1,242 @@
//
// 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)
//
#include "server.hpp"
#include "http_async_port.hpp"
#include "http_sync_port.hpp"
#include "ws_async_port.hpp"
#include "ws_sync_port.hpp"
#include "file_service.hpp"
#include "ws_upgrade_service.hpp"
#include <boost/program_options.hpp>
#include <iostream>
/// Block until SIGINT or SIGTERM is received.
void
sig_wait()
{
// Create our own io_service for this
boost::asio::io_service ios;
// Get notified on the signals we want
boost::asio::signal_set signals(
ios, SIGINT, SIGTERM);
// Now perform the asynchronous call
signals.async_wait(
[&](boost::system::error_code const&, int)
{
});
// Block the current thread on run(), when the
// signal is received then this call will return.
ios.run();
}
/** Set the options on a WebSocket stream.
This is used by the websocket server port handlers.
It is called every time a new websocket stream is
created, to provide the opportunity to set settings
for the connection.
*/
class set_ws_options
{
beast::websocket::permessage_deflate pmd_;
public:
set_ws_options(beast::websocket::permessage_deflate const& pmd)
: pmd_(pmd)
{
}
template<class NextLayer>
void
operator()(beast::websocket::stream<NextLayer>& ws) const
{
ws.auto_fragment(false);
ws.set_option(pmd_);
ws.read_message_max(64 * 1024 * 1024);
}
};
int
main(
int ac,
char const* av[])
{
// Helper for reporting failures
//
using namespace framework;
using namespace beast::http;
auto const fail =
[&](
std::string const& what,
error_code const& ec)
{
std::cerr <<
av[0] << ": " <<
what << " failed, " <<
ec.message() <<
std::endl;
return EXIT_FAILURE;
};
namespace po = boost::program_options;
po::options_description desc("Options");
desc.add_options()
("root,r", po::value<std::string>()->default_value("."),
"Set the root directory for serving files")
("port,p", po::value<std::uint16_t>()->default_value(1000),
"Set the base port number for the server")
("ip", po::value<std::string>()->default_value("0.0.0.0"),
"Set the IP address to bind to, \"0.0.0.0\" for all")
("threads,n", po::value<std::size_t>()->default_value(4),
"Set the number of threads to use")
;
po::variables_map vm;
po::store(po::parse_command_line(ac, av, desc), vm);
// Get the IP address from the options
std::string const ip = vm["ip"].as<std::string>();
// Get the port number from the options
std::uint16_t const port = vm["port"].as<std::uint16_t>();
// Build an endpoint from the address and port
address_type const addr{address_type::from_string(ip)};
// Get the number of threads from the command line
std::size_t const threads = vm["threads"].as<std::size_t>();
// Get the root path from the command line
boost::filesystem::path const root = vm["root"].as<std::string>();
// These settings will be applied to all new websocket connections
beast::websocket::permessage_deflate pmd;
pmd.client_enable = true;
pmd.server_enable = true;
pmd.compLevel = 3;
error_code ec;
// Create our server instance with the specified number of threads
server instance{threads};
{
// Install an asynchronous WebSocket echo port handler
//
auto wsp = instance.make_port<ws_async_port>(
ec,
endpoint_type{addr, port},
instance,
std::cout,
set_ws_options{pmd}
);
if(ec)
return fail("ws_async_port", ec);
// Install an asynchronous HTTP port handler
//
auto sp = instance.make_port<http_async_port<
ws_upgrade_service<ws_async_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 1)},
instance,
std::cout);
if(ec)
return fail("http_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("http_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
"http_async_port" // The value for the Server field
);
if(ec)
return fail("http_async_port/file_service", ec);
}
{
// Install a synchronous WebSocket echo port handler
//
auto wsp = instance.make_port<ws_sync_port>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 2)},
instance,
std::cout,
set_ws_options{pmd});
if(ec)
return fail("ws_sync_port", ec);
// Install a synchronous HTTP port handler
//
auto sp = instance.make_port<http_sync_port<
ws_upgrade_service<ws_sync_port>,
file_service
>>(
ec,
endpoint_type{addr,
static_cast<unsigned short>(port + 3)},
instance,
std::cout);
if(ec)
return fail("http_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
);
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,
"http_sync_port"
);
if(ec)
return fail("http_sync_port/file_service", ec);
}
sig_wait();
}

View File

@@ -0,0 +1,40 @@
//
// 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_RFC7231_HPP
#define BEAST_EXAMPLE_SERVER_RFC7231_HPP
#include <beast/core/string.hpp>
#include <beast/http/message.hpp>
namespace framework {
namespace rfc7231 {
// This aggregates a collection of algorithms
// corresponding to specifications in rfc7231:
//
// https://tools.ietf.org/html/rfc7231
//
/** Returns `true` if the message specifies Expect: 100-continue
@param req The request to check
@see https://tools.ietf.org/html/rfc7231#section-5.1.1
*/
template<class Body, class Fields>
bool
is_expect_100_continue(beast::http::request<Body, Fields> const& req)
{
return beast::iequals(
req[beast::http::field::expect], "100-continue");
}
} // rfc7231
} // framework
#endif

View File

@@ -0,0 +1,258 @@
//
// 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_FRAMEWORK_SERVER_HPP
#define BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP
#include "framework.hpp"
#include <boost/optional.hpp>
#include <memory>
#include <string>
#include <stdexcept>
#include <thread>
#include <type_traits>
#include <utility>
namespace framework {
/** A server instance that accepts TCP/IP connections.
This is a general purpose TCP/IP server which contains
zero or more user defined "ports". Each port represents
a listening socket whose behavior is defined by an
instance of the @b PortHandler concept.
To use the server, construct the class and then add the
ports that you want using @ref make_port.
@par Example
@code
// Create a server with 4 threads
//
framework::server si(4);
// Create a port that echoes everything back.
// Bind all available interfaces on port 1000.
//
framework::error_code ec;
si.make_port<echo_port>(
ec,
server::endpoint_type{
server::address_type::from_string("0.0.0.0"), 1000}
);
...
// Close all connections, shut down the server
si.stop();
@endcode
*/
class server
{
io_service_type ios_;
std::vector<std::thread> tv_;
boost::optional<boost::asio::io_service::work> work_;
public:
server(server const&) = delete;
server& operator=(server const&) = delete;
/** Constructor
@param n The number of threads to run on the `io_service`,
which must be greater than zero.
*/
explicit
server(std::size_t n = 1)
: work_(ios_)
{
if(n < 1)
throw std::invalid_argument{"threads < 1"};
tv_.reserve(n);
while(n--)
tv_.emplace_back(
[&]
{
ios_.run();
});
}
/** Destructor
Upon destruction, the `io_service` will be stopped
and all pending completion handlers destroyed.
*/
~server()
{
work_ = boost::none;
ios_.stop();
for(auto& t : tv_)
t.join();
}
/// Return the `io_service` associated with the server
boost::asio::io_service&
get_io_service()
{
return ios_;
}
/** Return a new, small integer unique id
These ids are used to uniquely identify connections
in log output.
*/
std::size_t
next_id()
{
static std::atomic<std::size_t> id_{0};
return ++id_;
}
/** Create a listening port.
@param ec Set to the error, if any occurred.
@param ep The address and port to bind to.
@param args Optional arguments, forwarded to the
port handler's constructor.
@tparam PortHandler The port handler to use for handling
incoming connections on this port. This handler must meet
the requirements of @b PortHandler. A model of PortHandler
is as follows:
@code
struct PortHandler
{
void
on_accept(
endpoint_type ep, // address of the remote endpoint
socket_type&& sock, // the connected socket
);
};
@endcode
*/
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args);
};
//------------------------------------------------------------------------------
template<class PortHandler>
class port
: public std::enable_shared_from_this<
port<PortHandler>>
{
server& instance_;
PortHandler handler_;
endpoint_type ep_;
strand_type strand_;
acceptor_type acceptor_;
socket_type sock_;
public:
// Constructor
//
// args are forwarded to the PortHandler
//
template<class... Args>
explicit
port(server& instance, Args&&... args)
: instance_(instance)
, handler_(std::forward<Args>(args)...)
, strand_(instance.get_io_service())
, acceptor_(instance.get_io_service())
, sock_(instance.get_io_service())
{
}
// Return the PortHandler wrapped in a shared_ptr
//
std::shared_ptr<PortHandler>
handler()
{
// This uses a feature of std::shared_ptr invented by
// Peter Dimov where the managed object piggy backs off
// the reference count of another object containing it.
//
return std::shared_ptr<PortHandler>(
this->shared_from_this(), &handler_);
}
// Open the listening socket
//
void
open(endpoint_type const& ep, error_code& ec)
{
acceptor_.open(ep.protocol(), ec);
if(ec)
return;
acceptor_.set_option(
boost::asio::socket_base::reuse_address{true});
acceptor_.bind(ep, ec);
if(ec)
return;
acceptor_.listen(
boost::asio::socket_base::max_connections, ec);
if(ec)
return;
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
private:
// Called when an incoming connection is accepted
//
void
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec == boost::asio::error::operation_aborted)
return;
if(! ec)
handler_.on_accept(std::move(sock_), ep_);
acceptor_.async_accept(sock_, ep_,
std::bind(&port::on_accept, this->shared_from_this(),
std::placeholders::_1));
}
};
//------------------------------------------------------------------------------
template<class PortHandler, class... Args>
std::shared_ptr<PortHandler>
server::
make_port(
error_code& ec,
endpoint_type const& ep,
Args&&... args)
{
auto sp = std::make_shared<port<PortHandler>>(
*this, std::forward<Args>(args)...);
sp->open(ep, ec);
if(ec)
return nullptr;
return sp->handler();
}
} // framework
#endif

View File

@@ -0,0 +1,224 @@
//
// 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_SERVICE_LIST_HPP
#define BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP
#include "framework.hpp"
#include <beast/http/message.hpp>
#include <boost/optional.hpp>
#include <utility>
namespace framework {
/** A list of HTTP services which may process requests.
When a service is invoked, it is provided with the stream and
endpoint metadata in addtion to an HTTP request. The service
decides whether or not the process the request, returning
`true` if the request is processed or `false` if it does not
process the request.
@b Service requirements
@code
struct Service
{
// Initialize the service
//
void
init(error_code& ec);
// Maybe respond to an HTTP request
//
// Returns `true` if it handled the response.
//
// Upon handling the response, the service may optionally
// take ownership of either the stream, the request, or both.
//
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&&,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
};
@endcode
@see file_service, ws_upgrade_service
*/
template<class... Services>
class service_list
{
// This helper is for tag-dispatching tuple index
template<std::size_t I>
using C = std::integral_constant<std::size_t, I>;
// Each service is wrapped in a boost::optional so we
// can construct them one by one later, instead of
// having to construct them all at once.
//
std::tuple<boost::optional<Services>...> list_;
public:
/// Constructor
service_list() = default;
/// Constructor
service_list(service_list&&) = default;
/// Constructor
service_list(service_list const&) = default;
/** Initialize a service.
Every service in the list must be initialized exactly once
before the service list is invoked.
@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)
{
// First, construct the service inside the optional
std::get<Index>(list_).emplace(std::forward<Args>(args)...);
// Now allow the service to finish the initialization
std::get<Index>(list_)->init(ec);
}
/** Handle a request.
This function attempts to process the given HTTP request by
invoking each service one at a time starting with the first
service in the list. When a service indicates that it handles
the request, by returning `true`, the function stops and
returns the value `true`. Otherwise, if no service handles
the request then the function returns the value `false`.
@param stream The stream belonging to the connection. A service
which handles the request may optionally take ownership of the
stream.
@param ep The remote endpoint of the connection corresponding
to the stream.
@param req The request message to attempt handling. A service
which handles the request may optionally take ownershop of the
message.
@param send The function to invoke with the response. The function
should have this equivalent signature:
@code
template<class Body, class Fields>
void
send(response<Body, Fields>&&);
@endcode
In C++14 this can be expressed using a generic lambda. In
C++11 it will require a template member function of an invocable
object.
@return `true` if the request was handled by a service.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send) const
{
return try_respond(
std::move(stream),
ep,
std::move(req),
send, C<0>{});
}
private:
/* The implementation of `try_respond` is implemented using
tail recursion which can usually be optimized away to
something resembling a switch statement.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
try_respond(
Stream&&,
endpoint_type const&,
beast::http::request<Body, Fields>&&,
Send const&,
C<sizeof...(Services)> const&) const
{
// This function breaks the recursion for the case where
// where the Index is one past the last type in the list.
//
return false;
}
// Invoke the I-th type in the type list
//
template<
class Stream,
class Body, class Fields,
class Send,
std::size_t I>
bool
try_respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const& send,
C<I> const&) const
{
// If the I-th service handles the request then return
//
if(std::get<I>(list_)->respond(
std::move(stream),
ep,
std::move(req),
send))
return true;
// Try the I+1th service. If I==sizeof...(Services)
// then we call the other overload and return false.
//
return try_respond(
std::move(stream),
ep,
std::move(req),
send,
C<I+1>{});
}
};
} // framework
#endif

View File

@@ -0,0 +1,264 @@
//
// 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_STREAM_HPP
#define BEAST_EXAMPLE_SERVER_SSL_STREAM_HPP
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <memory>
namespace beast {
/** Movable SSL socket wrapper
This wrapper provides an interface identical to `boost::asio::ssl::stream`,
which is additionally move constructible and move assignable.
*/
template<class NextLayer>
class ssl_stream
: public boost::asio::ssl::stream_base
{
using stream_type = boost::asio::ssl::stream<NextLayer>;
std::unique_ptr<stream_type> p_;
public:
/// The native handle type of the SSL stream.
using native_handle_type = typename stream_type::native_handle_type;
/// Structure for use with deprecated impl_type.
using impl_struct = typename stream_type::impl_struct;
/// (Deprecated: Use native_handle_type.) The underlying implementation type.
using impl_type = typename stream_type::impl_type;
/// The type of the next layer.
using next_layer_type = typename stream_type::next_layer_type;
/// 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)...)
{
}
boost::asio::io_service&
get_io_service()
{
return p_->get_io_service();
}
native_handle_type
native_handle()
{
return p_->native_handle();
}
impl_type
impl()
{
return p_->impl();
}
next_layer_type const&
next_layer() const
{
return p_->next_layer();
}
next_layer_type&
next_layer()
{
return p_->next_layer();
}
lowest_layer_type&
lowest_layer()
{
return p_->lowest_layer();
}
lowest_layer_type const&
lowest_layer() const
{
return p_->lowest_layer();
}
void
set_verify_mode(boost::asio::ssl::verify_mode v)
{
p_->set_verify_mode(v);
}
boost::system::error_code
set_verify_mode(boost::asio::ssl::verify_mode v,
boost::system::error_code& ec)
{
return p_->set_verify_mode(v, ec);
}
void
set_verify_depth(int depth)
{
p_->set_verify_depth(depth);
}
boost::system::error_code
set_verify_depth(
int depth, boost::system::error_code& ec)
{
return p_->set_verify_depth(depth, ec);
}
template<class VerifyCallback>
void
set_verify_callback(VerifyCallback callback)
{
p_->set_verify_callback(callback);
}
template<class VerifyCallback>
boost::system::error_code
set_verify_callback(VerifyCallback callback,
boost::system::error_code& ec)
{
return p_->set_verify_callback(callback, ec);
}
void
handshake(handshake_type type)
{
p_->handshake(type);
}
boost::system::error_code
handshake(handshake_type type,
boost::system::error_code& ec)
{
return p_->handshake(type, ec);
}
template<class ConstBufferSequence>
void
handshake(
handshake_type type, ConstBufferSequence const& buffers)
{
p_->handshake(type, buffers);
}
template<class ConstBufferSequence>
boost::system::error_code
handshake(handshake_type type,
ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->handshake(type, buffers, ec);
}
template<class HandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(HandshakeHandler,
void(boost::system::error_code))
async_handshake(handshake_type type,
BOOST_ASIO_MOVE_ARG(HandshakeHandler) handler)
{
return p_->async_handshake(type,
BOOST_ASIO_MOVE_CAST(HandshakeHandler)(handler));
}
template<class ConstBufferSequence, class BufferedHandshakeHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(BufferedHandshakeHandler,
void (boost::system::error_code, std::size_t))
async_handshake(handshake_type type, ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(BufferedHandshakeHandler) handler)
{
return p_->async_handshake(type, buffers,
BOOST_ASIO__MOVE_CAST(BufferedHandshakeHandler)(handler));
}
void
shutdown()
{
p_->shutdown();
}
boost::system::error_code
shutdown(boost::system::error_code& ec)
{
return p_->shutdown(ec);
}
template<class ShutdownHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ShutdownHandler,
void (boost::system::error_code))
async_shutdown(BOOST_ASIO_MOVE_ARG(ShutdownHandler) handler)
{
return p_->async_shutdown(
BOOST_ASIO_MOVE_CAST(ShutdownHandler)(handler));
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers)
{
return p_->write_some(buffers);
}
template<class ConstBufferSequence>
std::size_t
write_some(ConstBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->write_some(buffers, ec);
}
template<class ConstBufferSequence, class WriteHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler,
void (boost::system::error_code, std::size_t))
async_write_some(ConstBufferSequence const& buffers,
BOOST_ASIO_MOVE_ARG(WriteHandler) handler)
{
return p_->async_write_some(buffers,
BOOST_ASIO_MOVE_CAST(WriteHandler)(handler));
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers)
{
return p_->read_some(buffers);
}
template<class MutableBufferSequence>
std::size_t
read_some(MutableBufferSequence const& buffers,
boost::system::error_code& ec)
{
return p_->read_some(buffers, ec);
}
template<class MutableBufferSequence, class ReadHandler>
BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler,
void(boost::system::error_code, std::size_t))
async_read_some(MutableBufferSequence& buffers,
BOOST_ASIO_MOVE_ARG(ReadHandler) handler)
{
return p_->async_read_some(buffers,
BOOST_ASIO_MOVE_CAST(ReadHandler)(handler))
}
};
} // beast
#endif

View File

@@ -0,0 +1,188 @@
//
// 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_WRITE_MSG_HPP
#define BEAST_EXAMPLE_SERVER_WRITE_MSG_HPP
#include "server.hpp"
#include <beast/core/async_result.hpp>
#include <beast/core/handler_ptr.hpp>
#include <beast/core/type_traits.hpp>
#include <beast/http/message.hpp>
#include <beast/http/write.hpp>
#include <beast/http/type_traits.hpp>
#include <boost/asio/handler_alloc_hook.hpp>
#include <boost/asio/handler_continuation_hook.hpp>
#include <boost/asio/handler_invoke_hook.hpp>
namespace framework {
namespace detail {
/** Composed operation to send an HTTP message
This implements the composed operation needed for the
@ref async_write_msg function.
*/
template<
class AsyncWriteStream,
class Handler,
bool isRequest, class Body, class Fields>
class write_msg_op
{
struct data
{
AsyncWriteStream& stream;
beast::http::message<isRequest, Body, Fields> msg;
data(
Handler& handler,
AsyncWriteStream& stream_,
beast::http::message<isRequest, Body, Fields>&& msg_)
: stream(stream_)
, msg(std::move(msg_))
{
}
};
beast::handler_ptr<data, Handler> d_;
public:
write_msg_op(write_msg_op&&) = default;
write_msg_op(write_msg_op const&) = default;
template<class DeducedHandler, class... Args>
write_msg_op(DeducedHandler&& h, AsyncWriteStream& s, Args&&... args)
: d_(std::forward<DeducedHandler>(h),
s, std::forward<Args>(args)...)
{
}
void
operator()()
{
auto& d = *d_;
beast::http::async_write(
d.stream, d.msg, std::move(*this));
}
void
operator()(error_code ec)
{
d_.invoke(ec);
}
friend
void* asio_handler_allocate(
std::size_t size, write_msg_op* op)
{
using boost::asio::asio_handler_allocate;
return asio_handler_allocate(
size, std::addressof(op->d_.handler()));
}
friend
void asio_handler_deallocate(
void* p, std::size_t size, write_msg_op* op)
{
using boost::asio::asio_handler_deallocate;
asio_handler_deallocate(
p, size, std::addressof(op->d_.handler()));
}
friend
bool asio_handler_is_continuation(write_msg_op* op)
{
using boost::asio::asio_handler_is_continuation;
return asio_handler_is_continuation(std::addressof(op->d_.handler()));
}
template<class Function>
friend
void asio_handler_invoke(Function&& f, write_msg_op* op)
{
using boost::asio::asio_handler_invoke;
asio_handler_invoke(
f, std::addressof(op->d_.handler()));
}
};
} // detail
/** Write an HTTP message to a stream asynchronously
This function is used to write a complete message to a stream asynchronously
using HTTP/1. The function call always returns immediately. The asynchronous
operation will continue until one of the following conditions is true:
@li The entire message is written.
@li An error occurs.
This operation is implemented in terms of zero or more calls to the stream's
`async_write_some` function, and is known as a <em>composed operation</em>.
The program must ensure that the stream performs no other write operations
until this operation completes. The algorithm will use a temporary
@ref serializer with an empty chunk decorator to produce buffers. If
the semantics of the message indicate that the connection should be
closed after the message is sent, the error delivered by this function
will be @ref error::end_of_stream
@param stream The stream to which the data is to be written.
The type must support the @b AsyncWriteStream concept.
@param msg The message to write. The function will take ownership
of the object as if by move constrction.
@param handler The handler to be called when the operation
completes. Copies will be made of the handler as required.
The equivalent function signature of the handler must be:
@code void handler(
error_code const& error // result of operation
); @endcode
Regardless of whether the asynchronous operation completes
immediately or not, the handler will not be invoked from within
this function. Invocation of the handler will be performed in a
manner equivalent to using `boost::asio::io_service::post`.
*/
template<
class AsyncWriteStream,
bool isRequest, class Body, class Fields,
class WriteHandler>
beast::async_return_type<WriteHandler, void(error_code)>
async_write_msg(
AsyncWriteStream& stream,
beast::http::message<isRequest, Body, Fields>&& msg,
WriteHandler&& handler)
{
static_assert(
beast::is_async_write_stream<AsyncWriteStream>::value,
"AsyncWriteStream requirements not met");
static_assert(beast::http::is_body<Body>::value,
"Body requirements not met");
static_assert(beast::http::is_body_reader<Body>::value,
"BodyReader requirements not met");
beast::async_completion<WriteHandler, void(error_code)> init{handler};
detail::write_msg_op<
AsyncWriteStream,
beast::handler_type<WriteHandler, void(error_code)>,
isRequest, Body, Fields>{
init.completion_handler,
stream,
std::move(msg)}();
return init.result.get();
}
} // framework
#endif

View File

@@ -0,0 +1,330 @@
//
// 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_WS_ASYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP
#include "server.hpp"
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket/stream.hpp>
#include <functional>
#include <memory>
#include <ostream>
namespace framework {
// This object holds the state of the connection
// including, most importantly, the socket or stream.
//
// `Stream` is the type of socket or stream used as the
// transport. Examples include boost::asio::ip::tcp::socket
// or `ssl_stream`.
//
template<class Derived>
class async_ws_con
{
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
// This is used to hold the message data
beast::multi_buffer buffer_;
protected:
// The strand makes sure that our data is
// accessed from only one thread at a time.
//
strand_type strand_;
public:
// Constructor
template<class Callback>
async_ws_con(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
// Limit of 1MB on messages
, buffer_(1024 * 1024)
, strand_(impl().ws().get_io_service())
{
cb(impl().ws());
}
// Run the connection
//
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)));
}
// Run the connection.
//
// This overload handles the case where we
// already have the WebSocket Upgrade request.
//
template<class Body, class Fields>
void
run(beast::http::request<Body, Fields> const& req)
{
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
impl().ws().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,
impl().shared_from_this(),
std::placeholders::_1)));
}
private:
// Called when accept_ex completes
//
void
on_accept(error_code ec)
{
if(ec)
return fail("async_accept", ec);
do_read();
}
// Read the next WebSocket message
//
void
do_read()
{
impl().ws().async_read(
buffer_,
strand_.wrap(std::bind(
&async_ws_con::on_read,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message read completes
//
void
on_read(error_code ec)
{
if(ec)
return fail("on_read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().ws().binary(impl().ws().got_binary());
// Now echo back the message
//
impl().ws().async_write(
buffer_.data(),
strand_.wrap(std::bind(
&async_ws_con::on_write,
impl().shared_from_this(),
std::placeholders::_1)));
}
// Called when the message write completes
//
void
on_write(error_code ec)
{
if(ec)
return fail("on_write", ec);
// Empty out the contents of the message buffer
// to prepare it for the next call to read.
//
buffer_.consume(buffer_.size());
// Now read another message
//
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;
}
};
//------------------------------------------------------------------------------
class async_ws_con_plain
: public std::enable_shared_from_this<async_ws_con_plain>
, public base_from_member<beast::websocket::stream<socket_type>>
, public async_ws_con<async_ws_con_plain>
{
public:
template<class... Args>
explicit
async_ws_con_plain(
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)...)
{
}
beast::websocket::stream<socket_type>&
ws()
{
return this->member;
}
};
//------------------------------------------------------------------------------
/** An synchronous 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 ws_async_port
{
// The type of the on_stream callback
using on_new_stream_cb = std::function<
void(beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@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>
ws_async_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(cb)
{
}
/** Accept a TCP/IP async_ws_con.
This function is called when the server has accepted an
incoming async_ws_con.
@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_ws_con_plain>(
std::move(sock),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_
)->run();
}
/** Accept a WebSocket upgrade request.
This is used to accept a async_ws_con that has already
delivered the handshake.
@param stream The stream corresponding to the async_ws_con.
@param ep The remote endpoint.
@param req The upgrade request.
*/
template<class Body, class Fields>
void
accept(
socket_type&& stream,
endpoint_type ep,
beast::http::request<Body, Fields>&& req)
{
std::make_shared<async_ws_con_plain>(
std::move(stream),
"ws_async_port",
log_,
instance_.next_id(),
ep,
cb_
)->run(std::move(req));
}
};
} // framework
#endif

View File

@@ -0,0 +1,369 @@
//
// 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_WS_SYNC_PORT_HPP
#define BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP
#include "server.hpp"
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket.hpp>
#include <functional>
#include <memory>
#include <ostream>
#include <thread>
namespace framework {
// The connection object holds the state of the connection
// including, most importantly, the socket or stream.
//
// `Stream` is the type of socket or stream used as the
// transport. Examples include boost::asio::ip::tcp::socket
// or `ssl_stream`.
//
template<class Derived>
class sync_ws_con
{
Derived&
impl()
{
return static_cast<Derived&>(*this);
}
// The string used to set the Server http field
std::string server_name_;
// The stream to use for logging
std::ostream& log_;
// A small unique integer for logging
std::size_t id_;
// The remote endpoint. We cache it here because
// calls to remote_endpoint() can fail / throw.
//
endpoint_type ep_;
public:
// Constructor
template<class Callback>
sync_ws_con(
beast::string_view server_name,
std::ostream& log,
std::size_t id,
endpoint_type const& ep,
Callback const& cb)
: server_name_(server_name)
, log_(log)
, id_(id)
, ep_(ep)
{
cb(impl().ws());
}
// Run the connection.
//
void
run()
{
// We run the do_accept 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,
impl().shared_from_this()
}.detach();
}
// Run the connection from an already-received Upgrade request.
//
template<class Body, class Fields>
void
run(beast::http::request<Body, Fields>&& req)
{
BOOST_ASSERT(beast::websocket::is_upgrade(req));
// We need to transfer ownership of the request object into
// the lambda, but there's no C++14 lambda capture
// so we have to write it out by manually specifying the lambda.
//
std::thread{
lambda<Body, Fields>{
impl().shared_from_this(),
std::move(req)
}}.detach();
}
private:
// 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
// we write out the lambda ourselves. This is similar to what
// the compiler would generate anyway.
//
template<class Body, class Fields>
class lambda
{
std::shared_ptr<sync_ws_con> self_;
beast::http::request<Body, Fields> req_;
public:
// Constructor
//
// This is the equivalent of the capture section of the lambda.
//
lambda(
std::shared_ptr<sync_ws_con> self,
beast::http::request<Body, Fields>&& req)
: self_(std::move(self))
, req_(std::move(req))
{
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
}
// Invoke the lambda
//
void
operator()()
{
BOOST_ASSERT(beast::websocket::is_upgrade(req_));
error_code ec;
{
// Move the message to the stack so we can get
// rid of resources, otherwise it will linger
// for the lifetime of the connection.
//
auto req = std::move(req_);
// Call the overload of accept() which takes
// the request by parameter, instead of reading
// it from the network.
//
self_->impl().ws().accept_ex(req,
[&](beast::websocket::response_type& res)
{
res.insert(beast::http::field::server, self_->server_name_);
},
ec);
}
// Run the connection
//
self_->do_run(ec);
}
};
void
do_accept()
{
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(;;)
{
// This buffer holds the message. We place a one
// megabyte limit on the size to prevent abuse.
//
beast::multi_buffer buffer{1024*1024};
// Read the message
//
impl().ws().read(buffer, ec);
if(ec)
return fail("read", ec);
// Set the outgoing message type. We will use
// the same setting as the message we just read.
//
impl().ws().binary(impl().ws().got_binary());
// Now echo back the message
//
impl().ws().write(buffer.data(), ec);
if(ec)
return fail("write", ec);
}
}
};
//------------------------------------------------------------------------------
class sync_ws_con_plain
: public std::enable_shared_from_this<sync_ws_con_plain>
, public base_from_member<beast::websocket::stream<socket_type>>
, public sync_ws_con<sync_ws_con_plain>
{
public:
template<class... Args>
explicit
sync_ws_con_plain(
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)...)
{
}
beast::websocket::stream<socket_type>&
ws()
{
return this->member;
}
};
//------------------------------------------------------------------------------
/** A synchronous 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 ws_sync_port
{
// The type of the on_stream callback
using on_new_stream_cb = std::function<
void(beast::websocket::stream<socket_type>&)>;
server& instance_;
std::ostream& log_;
on_new_stream_cb cb_;
public:
/** Constructor
@param instance The server instance which owns this port
@param log The stream to use for logging
@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>
ws_sync_port(
server& instance,
std::ostream& log,
Callback const& cb)
: instance_(instance)
, log_(log)
, cb_(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_ws_con_plain>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_
)->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(
socket_type&& sock,
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_ws_con_plain>(
std::move(sock),
"ws_sync_port",
log_,
instance_.next_id(),
ep,
cb_
)->run(std::move(req));
}
};
} // framework
#endif

View File

@@ -0,0 +1,102 @@
//
// 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_WS_UPGRADE_SERVICE_HPP
#define BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP
#include "framework.hpp"
#include <beast/http/message.hpp>
#include <beast/websocket/rfc6455.hpp>
#include <memory>
namespace framework {
/** An HTTP service which transfers WebSocket upgrade request to another port handler.
@tparam PortHandler The type of port handler. The service will
handle WebSocket Upgrade requests by transferring ownership
of the stream and request to a port handler of this type.
*/
template<class PortHandler>
class ws_upgrade_service
{
std::shared_ptr<PortHandler> handler_;
public:
/** Constructor
@param handler A shared pointer to the @b PortHandler to
handle WebSocket upgrade requests.
*/
explicit
ws_upgrade_service(
std::shared_ptr<PortHandler> handler)
: handler_(std::move(handler))
{
}
/** Initialize the service.
This provides an opportunity for the service to perform
initialization which may fail, while reporting an error
code instead of throwing an exception from the constructor.
*/
void
init(error_code& ec)
{
// This is required by the error_code specification
//
ec = {};
}
/** Handle a WebSocket Upgrade request.
If the request is an upgrade request, ownership of the
stream and request will be transferred to the corresponding
WebSocket port handler.
@param stream The stream corresponding to the connection.
@param ep The remote endpoint associated with the stream.
@req The request to check.
*/
template<
class Stream,
class Body, class Fields,
class Send>
bool
respond(
Stream&& stream,
endpoint_type const& ep,
beast::http::request<Body, Fields>&& req,
Send const&) const
{
// If its not an upgrade request, return `false`
// to indicate that we are not handling it.
//
if(! beast::websocket::is_upgrade(req))
return false;
// Its an ugprade request, so transfer ownership
// of the stream and request to the port handler.
//
handler_->accept(
std::move(stream),
ep,
std::move(req));
// Tell the service list that we handled the request.
//
return true;
}
};
} // framework
#endif

View File

@@ -1,15 +0,0 @@
# Part of Beast
GroupSources(extras/beast extras)
GroupSources(include/beast beast)
GroupSources(example/websocket-server "/")
add_executable (websocket-server
${BEAST_INCLUDES}
main.cpp
websocket_async_echo_server.hpp
websocket_sync_echo_server.hpp
)
target_link_libraries(websocket-server Beast)

View File

@@ -1,10 +0,0 @@
#
# 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)
#
exe websocket-server :
main.cpp
;

View File

@@ -1,75 +0,0 @@
//
// 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)
//
#include "websocket_async_echo_server.hpp"
#include "websocket_sync_echo_server.hpp"
#include <boost/asio/io_service.hpp>
#include <boost/asio/signal_set.hpp>
#include <iostream>
/// Block until SIGINT or SIGTERM is received.
void
sig_wait()
{
boost::asio::io_service ios;
boost::asio::signal_set signals(
ios, SIGINT, SIGTERM);
signals.async_wait(
[&](boost::system::error_code const&, int)
{
});
ios.run();
}
class set_stream_options
{
beast::websocket::permessage_deflate pmd_;
public:
set_stream_options(set_stream_options const&) = default;
set_stream_options(
beast::websocket::permessage_deflate const& pmd)
: pmd_(pmd)
{
}
template<class NextLayer>
void
operator()(beast::websocket::stream<NextLayer>& ws) const
{
ws.auto_fragment(false);
ws.set_option(pmd_);
ws.read_message_max(64 * 1024 * 1024);
}
};
int main()
{
using namespace beast::websocket;
using endpoint_type = boost::asio::ip::tcp::endpoint;
using address_type = boost::asio::ip::address;
beast::error_code ec;
permessage_deflate pmd;
pmd.client_enable = true;
pmd.server_enable = true;
pmd.compLevel = 3;
websocket::async_echo_server s1{&std::cout, 1};
s1.on_new_stream(set_stream_options{pmd});
s1.open(endpoint_type{
address_type::from_string("127.0.0.1"), 6000 }, ec);
websocket::sync_echo_server s2{&std::cout};
s2.on_new_stream(set_stream_options{pmd});
s2.open(endpoint_type{
address_type::from_string("127.0.0.1"), 6001 }, ec);
sig_wait();
}

View File

@@ -1,282 +0,0 @@
//
// 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 WEBSOCKET_ASYNC_ECHO_SERVER_HPP
#define WEBSOCKET_ASYNC_ECHO_SERVER_HPP
#include <beast/core/multi_buffer.hpp>
#include <beast/websocket/stream.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 <utility>
namespace websocket {
/** Asynchronous WebSocket echo client/server
*/
class async_echo_server
{
public:
using error_code = beast::error_code;
using address_type = boost::asio::ip::address;
using socket_type = boost::asio::ip::tcp::socket;
using endpoint_type = boost::asio::ip::tcp::endpoint;
private:
std::ostream* log_;
boost::asio::io_service ios_;
socket_type sock_;
endpoint_type ep_;
boost::asio::ip::tcp::acceptor acceptor_;
std::vector<std::thread> thread_;
boost::optional<boost::asio::io_service::work> work_;
std::function<void(beast::websocket::stream<socket_type>&)> mod_;
public:
async_echo_server(async_echo_server const&) = delete;
async_echo_server& operator=(async_echo_server const&) = delete;
/** Constructor.
@param log A pointer to a stream to log to, or `nullptr`
to disable logging.
@param threads The number of threads in the io_service.
*/
async_echo_server(std::ostream* log,
std::size_t threads)
: log_(log)
, sock_(ios_)
, acceptor_(ios_)
, work_(ios_)
{
thread_.reserve(threads);
for(std::size_t i = 0; i < threads; ++i)
thread_.emplace_back(
[&]{ ios_.run(); });
}
/** Destructor.
*/
~async_echo_server()
{
work_ = boost::none;
ios_.dispatch(
[&]
{
error_code ec;
acceptor_.close(ec);
});
for(auto& t : thread_)
t.join();
}
/** Return the listening endpoint.
*/
endpoint_type
local_endpoint() const
{
return acceptor_.local_endpoint();
}
/** Set a handler called for new streams.
This function is called for each new stream.
It is used to set options for every connection.
*/
template<class F>
void
on_new_stream(F const& f)
{
mod_ = f;
}
/** 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(&async_echo_server::on_accept, this,
std::placeholders::_1));
}
private:
class peer
{
struct data
{
async_echo_server& server;
endpoint_type ep;
int state = 0;
beast::websocket::stream<socket_type> ws;
boost::asio::io_service::strand strand;
beast::multi_buffer db;
std::size_t id;
data(async_echo_server& server_,
endpoint_type const& ep_,
socket_type&& sock_)
: server(server_)
, ep(ep_)
, ws(std::move(sock_))
, strand(ws.get_io_service())
, id([]
{
static std::atomic<std::size_t> n{0};
return ++n;
}())
{
}
};
// VFALCO This could be unique_ptr in [Net.TS]
std::shared_ptr<data> d_;
public:
peer(peer&&) = default;
peer(peer const&) = default;
peer& operator=(peer&&) = delete;
peer& operator=(peer const&) = delete;
template<class... Args>
explicit
peer(async_echo_server& server,
endpoint_type const& ep, socket_type&& sock,
Args&&... args)
: d_(std::make_shared<data>(server, ep,
std::forward<socket_type>(sock),
std::forward<Args>(args)...))
{
auto& d = *d_;
d.server.mod_(d.ws);
run();
}
void run()
{
auto& d = *d_;
d.ws.async_accept_ex(
[](beast::websocket::response_type& res)
{
res.insert(
"Server", "async_echo_server");
},
std::move(*this));
}
void operator()(error_code ec, std::size_t)
{
(*this)(ec);
}
void operator()(error_code ec)
{
using boost::asio::buffer;
using boost::asio::buffer_copy;
auto& d = *d_;
switch(d.state)
{
// did accept
case 0:
if(ec)
return fail("async_accept", ec);
// start
case 1:
if(ec)
return fail("async_handshake", ec);
d.db.consume(d.db.size());
// read message
d.state = 2;
d.ws.async_read(d.db,
d.strand.wrap(std::move(*this)));
return;
// got message
case 2:
if(ec == beast::websocket::error::closed)
return;
if(ec)
return fail("async_read", ec);
// write message
d.state = 1;
d.ws.binary(d.ws.got_binary());
d.ws.async_write(d.db.data(),
d.strand.wrap(std::move(*this)));
return;
}
}
private:
void
fail(std::string what, error_code ec)
{
auto& d = *d_;
if(d.server.log_)
if(ec != beast::websocket::error::closed)
d.server.fail("[#" + std::to_string(d.id) +
" " + boost::lexical_cast<std::string>(d.ep) +
"] " + what, ec);
}
};
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
on_accept(error_code ec)
{
if(! acceptor_.is_open())
return;
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
fail("accept", ec);
peer{*this, ep_, std::move(sock_)};
acceptor_.async_accept(sock_, ep_,
std::bind(&async_echo_server::on_accept, this,
std::placeholders::_1));
}
};
} // websocket
#endif

View File

@@ -1,231 +0,0 @@
//
// 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 WEBSOCKET_SYNC_ECHO_SERVER_HPP
#define WEBSOCKET_SYNC_ECHO_SERVER_HPP
#include <beast/core/multi_buffer.hpp>
#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 <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:
std::ostream* log_;
boost::asio::io_service ios_;
socket_type sock_;
endpoint_type ep_;
boost::asio::ip::tcp::acceptor acceptor_;
std::thread thread_;
std::function<void(beast::websocket::stream<socket_type>&)> mod_;
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 handler called for new streams.
This function is called for each new stream.
It is used to set options for every connection.
*/
template<class F>
void
on_new_stream(F const& f)
{
mod_ = f;
}
/** 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,
std::placeholders::_1));
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,
std::placeholders::_1));
}
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)};
mod_(ws);
error_code ec;
ws.accept_ex(
[](beast::websocket::response_type& res)
{
res.insert(
"Server", "sync_echo_server");
},
ec);
if(ec)
{
fail("accept", ec, id, ep);
return;
}
for(;;)
{
beast::multi_buffer b;
ws.read(b, ec);
if(ec)
{
auto const s = ec.message();
break;
}
ws.binary(ws.got_binary());
ws.write(b.data(), ec);
if(ec)
break;
}
if(ec && ec != beast::websocket::error::closed)
{
fail("read", ec, id, ep);
}
}
};
} // websocket
#endif

View File

@@ -1,5 +1,11 @@
# Part of Beast # Part of Beast
add_subdirectory (core)
add_subdirectory (http)
add_subdirectory (server)
add_subdirectory (websocket)
add_subdirectory (zlib)
GroupSources(extras/beast extras) GroupSources(extras/beast extras)
GroupSources(include/beast beast) GroupSources(include/beast beast)
GroupSources(test "/") GroupSources(test "/")

View File

@@ -14,6 +14,8 @@ compile version.cpp : : ;
compile websocket.cpp : : ; compile websocket.cpp : : ;
compile zlib.cpp : : ; compile zlib.cpp : : ;
build-project server ;
unit-test core-tests : unit-test core-tests :
../extras/beast/unit_test/main.cpp ../extras/beast/unit_test/main.cpp
core/async_result.cpp core/async_result.cpp

View File

@@ -6,7 +6,7 @@
// //
#include "example/doc/http_examples.hpp" #include "example/doc/http_examples.hpp"
#include "example/http-server/file_body.hpp" #include "example/server-framework/file_body.hpp"
#include <beast/core/read_size.hpp> #include <beast/core/read_size.hpp>
#include <beast/core/detail/clamp.hpp> #include <beast/core/detail/clamp.hpp>

View File

@@ -0,0 +1,28 @@
# Part of Beast
GroupSources(example/server-framework framework)
GroupSources(include/beast beast)
GroupSources(test/server "/")
add_executable (server-test
${BEAST_INCLUDES}
${SERVER_INCLUDES}
file_body.cpp
file_service.cpp
framework.cpp
http_async_port.cpp
http_base.cpp
http_sync_port.cpp
main.cpp
rfc7231.cpp
server.cpp
service_list.cpp
write_msg.cpp
ws_async_port.cpp
ws_sync_port.cpp
ws_upgrade_service.cpp
)
target_link_libraries(server-test Beast)

23
test/server/Jamfile Normal file
View File

@@ -0,0 +1,23 @@
#
# 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)
#
exe server-test :
file_body.cpp
file_service.cpp
framework.cpp
http_async_port.cpp
http_base.cpp
http_sync_port.cpp
main.cpp
rfc7231.cpp
server.cpp
service_list.cpp
write_msg.cpp
ws_async_port.cpp
ws_sync_port.cpp
ws_upgrade_service.cpp
;

10
test/server/file_body.cpp Normal file
View File

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

View File

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

10
test/server/framework.cpp Normal file
View File

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

View File

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

10
test/server/http_base.cpp Normal file
View File

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

View File

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

10
test/server/main.cpp Normal file
View File

@@ -0,0 +1,10 @@
//
// 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)
//
int main(int argc, char** argv)
{
}

10
test/server/rfc7231.cpp Normal file
View File

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

10
test/server/server.cpp Normal file
View File

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

View File

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

10
test/server/write_msg.cpp Normal file
View File

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

View File

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

View File

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

View File

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