diff --git a/CHANGELOG.md b/CHANGELOG.md index a0a4b483..c83deea0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Version 60: * String comparisons are public interfaces * Fix response message type in async websocket accept +* New server-framework, full featured server example -------------------------------------------------------------------------------- diff --git a/CMakeLists.txt b/CMakeLists.txt index 2898f661..c03a3db6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -174,11 +174,11 @@ file(GLOB_RECURSE EXTRAS_INCLUDES ${PROJECT_SOURCE_DIR}/extras/beast/*.ipp ) +file(GLOB_RECURSE SERVER_INCLUDES + ${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp + ) + add_subdirectory (test) -add_subdirectory (test/core) -add_subdirectory (test/http) -add_subdirectory (test/websocket) -add_subdirectory (test/zlib) add_subdirectory (example) diff --git a/doc/0_main.qbk b/doc/0_main.qbk index 7cbbaeca..d8772b20 100644 --- a/doc/0_main.qbk +++ b/doc/0_main.qbk @@ -79,7 +79,7 @@ [import ../example/doc/http_examples.hpp] [import ../example/echo-op/echo_op.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 ../test/core/doc_snippets.cpp] diff --git a/doc/1_overview.qbk b/doc/1_overview.qbk index 6d022224..3cdf4c6c 100644 --- a/doc/1_overview.qbk +++ b/doc/1_overview.qbk @@ -42,9 +42,11 @@ standardized implementation of these protocols. [heading Requirements] -This library is for programmers familiar with __Asio__. Users who -wish to use asynchronous interfaces should already know how to -create concurrent network programs using callbacks or coroutines. +[important + This library is for programmers familiar with __Asio__. Users who + wish to use asynchronous interfaces should already know how to + create concurrent network programs using callbacks or coroutines. +] Beast requires: diff --git a/doc/2_examples.qbk b/doc/2_examples.qbk index 6bc6c4be..e8df84f7 100644 --- a/doc/2_examples.qbk +++ b/doc/2_examples.qbk @@ -5,7 +5,8 @@ file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) ] -[section:example Example Programs] +[section:example Examples] +[block''''''] These complete programs are intended to quickly impress upon readers the 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: @@ -21,9 +22,33 @@ File: [repo_file example/http-client/http_client.cpp] [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: @@ -31,60 +56,47 @@ File: [repo_file example/websocket-client/websocket_client.cpp] [example_websocket_client] - - -[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] +[endsect] -[heading Secure WebSocket] +[section WebSocket Client (with SSL)] Establish a WebSocket connection over an encrypted TLS connection, send a message and receive the reply. Requires OpenSSL to build. * [repo_file example/ssl/websocket_ssl_example.cpp] - - -[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] +[endsect] -[heading HTTP Crawl] +[section Server Framework] -This example retrieves the page at each of the most popular domains -as measured by Alexa. +This is a complete program and framework of classes implementing +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] - -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] +[section Composed Operations] This program shows how to use Beast's network foundations to build a 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] +[endsect] -[heading Documentation Samples] + +[section Documentation Samples] Here are all of the example functions and classes presented 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] +[endsect] + [endsect] diff --git a/doc/3_1_asio.qbk b/doc/3_1_asio.qbk index 9e657352..bd10a716 100644 --- a/doc/3_1_asio.qbk +++ b/doc/3_1_asio.qbk @@ -15,6 +15,13 @@ 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 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] diff --git a/doc/5_00_http.qbk b/doc/5_00_http.qbk index 2da28bc1..62d7b681 100644 --- a/doc/5_00_http.qbk +++ b/doc/5_00_http.qbk @@ -85,7 +85,7 @@ format using __Asio__. Specifically, the library provides: [include 5_05_parser_streams.qbk] [include 5_06_serializer_buffers.qbk] [include 5_07_parser_buffers.qbk] -[include 5_08_custom_parsers.qbk] -[include 5_09_custom_body.qbk] +[include 5_08_custom_body.qbk] +[include 5_09_custom_parsers.qbk] [endsect] diff --git a/doc/5_09_custom_body.qbk b/doc/5_08_custom_body.qbk similarity index 100% rename from doc/5_09_custom_body.qbk rename to doc/5_08_custom_body.qbk diff --git a/doc/5_08_custom_parsers.qbk b/doc/5_09_custom_parsers.qbk similarity index 100% rename from doc/5_08_custom_parsers.qbk rename to doc/5_09_custom_parsers.qbk diff --git a/doc/images/server.png b/doc/images/server.png new file mode 100644 index 00000000..52f0c2e2 Binary files /dev/null and b/doc/images/server.png differ diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fac79004..78e5dc52 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -3,6 +3,5 @@ add_subdirectory (echo-op) add_subdirectory (http-client) add_subdirectory (http-crawl) -add_subdirectory (http-server) +add_subdirectory (server-framework) add_subdirectory (websocket-client) -add_subdirectory (websocket-server) diff --git a/example/Jamfile b/example/Jamfile index d0840b73..4f6aa2ba 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -8,6 +8,5 @@ build-project echo-op ; build-project http-client ; build-project http-crawl ; -build-project http-server ; +build-project server-framework ; build-project websocket-client ; -build-project websocket-server ; diff --git a/example/http-crawl/urls_large_data.hpp b/example/http-crawl/urls_large_data.hpp index 74147a8a..da4d8eb5 100644 --- a/example/http-crawl/urls_large_data.hpp +++ b/example/http-crawl/urls_large_data.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef URLS_LARGE_DATA_H_INCLUDED -#define URLS_LARGE_DATA_H_INCLUDED +#ifndef BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP +#define BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP #include diff --git a/example/http-server/CMakeLists.txt b/example/http-server/CMakeLists.txt deleted file mode 100644 index d3547404..00000000 --- a/example/http-server/CMakeLists.txt +++ /dev/null @@ -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} ) diff --git a/example/http-server/http_async_server.hpp b/example/http-server/http_async_server.hpp deleted file mode 100644 index 69640906..00000000 --- a/example/http-server/http_async_server.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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; - using resp_type = response; - - 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 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 - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - template - class write_op - { - struct data - { - bool cont; - Stream& s; - message m; - - data(Handler& handler, Stream& s_, - message&& m_) - : s(s_) - , m(std::move(m_)) - { - using boost::asio::asio_handler_is_continuation; - cont = asio_handler_is_continuation(std::addressof(handler)); - } - }; - - handler_ptr d_; - - public: - write_op(write_op&&) = default; - write_op(write_op const&) = default; - - template - write_op(DeducedHandler&& h, Stream& s, Args&&... args) - : d_(std::forward(h), - s, std::forward(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 - 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 - static - void - async_write(Stream& stream, message< - isRequest, Body, Fields>&& msg, - DeducedHandler&& handler) - { - write_op::type, - isRequest, Body, Fields>{std::forward( - handler), stream, std::move(msg)}; - } - - class peer : public std::enable_shared_from_this - { - 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 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 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 - 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(std::move(sock), *this)->run(); - } -}; - -} // http -} // beast - -#endif diff --git a/example/http-server/http_sync_server.hpp b/example/http-server/http_sync_server.hpp deleted file mode 100644 index 6f017577..00000000 --- a/example/http-server/http_sync_server.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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; - using resp_type = response; - - 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 - void - log(Args const&... args) - { - if(log_) - { - std::lock_guard lock(m_); - log_args(args...); - } - } - -private: - void - log_args() - { - } - - template - 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 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 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 diff --git a/example/http-server/main.cpp b/example/http-server/main.cpp deleted file mode 100644 index 7694d559..00000000 --- a/example/http-server/main.cpp +++ /dev/null @@ -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 -#include - -#include - -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()->default_value("."), - "Set the root directory for serving files") - ("port,p", po::value()->default_value(8080), - "Set the port number for the server") - ("ip", po::value()->default_value("0.0.0.0"), - "Set the IP address to bind to, \"0.0.0.0\" for all") - ("threads,n", po::value()->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::uint16_t port = vm["port"].as(); - - std::string ip = vm["ip"].as(); - - std::size_t threads = vm["threads"].as(); - - 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(); - } -} diff --git a/example/http-server/mime_type.hpp b/example/http-server/mime_type.hpp deleted file mode 100644 index ac9e38b4..00000000 --- a/example/http-server/mime_type.hpp +++ /dev/null @@ -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 -#include -#include - -namespace beast { -namespace http { - -// Return the Mime-Type for a given file extension -template -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 diff --git a/example/server-framework/CMakeLists.txt b/example/server-framework/CMakeLists.txt new file mode 100644 index 00000000..70dbbc03 --- /dev/null +++ b/example/server-framework/CMakeLists.txt @@ -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}) diff --git a/example/http-server/Jamfile b/example/server-framework/Jamfile similarity index 91% rename from example/http-server/Jamfile rename to example/server-framework/Jamfile index 137770a2..73fe2bbd 100644 --- a/example/http-server/Jamfile +++ b/example/server-framework/Jamfile @@ -5,6 +5,6 @@ # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) # -exe http-server : +exe server-framework : main.cpp ; diff --git a/example/server-framework/README.md b/example/server-framework/README.md new file mode 100644 index 00000000..aadfc9d3 --- /dev/null +++ b/example/server-framework/README.md @@ -0,0 +1,68 @@ +Beast + +# 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: + +ServerFramework + diff --git a/example/http-server/file_body.hpp b/example/server-framework/file_body.hpp similarity index 84% rename from example/http-server/file_body.hpp rename to example/server-framework/file_body.hpp index 5faf75b5..526d9c78 100644 --- a/example/http-server/file_body.hpp +++ b/example/server-framework/file_body.hpp @@ -5,8 +5,8 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef BEAST_EXAMPLE_FILE_BODY_H_INCLUDED -#define BEAST_EXAMPLE_FILE_BODY_H_INCLUDED +#ifndef BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP +#define BEAST_EXAMPLE_HTTP_SERVER_FILE_BODY_HPP #include #include @@ -18,11 +18,18 @@ #include #include -namespace beast { -namespace http { - //[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 { /** The type of the @ref message::body member. @@ -71,6 +78,7 @@ struct file_body //[example_http_file_body_2 +inline std::uint64_t file_body:: size(value_type const& v) @@ -111,7 +119,7 @@ public: // always have the `file_body` as the body type. // template - reader(message const& m); + reader(beast::http::message const& m); // Destructor ~reader(); @@ -120,7 +128,7 @@ public: // of the body is started. // void - init(error_code& ec); + init(beast::error_code& ec); // This function is called zero or more times to // retrieve buffers. A return value of `boost::none` @@ -129,7 +137,7 @@ public: // to serialize, and a `bool` indicating whether // or not there may be additional buffers. boost::optional> - get(error_code& ec); + get(beast::error_code& ec); // This function is called when reading is complete. // It is an opportunity to perform any final actions @@ -138,7 +146,7 @@ public: // destructors, since an exception thrown from there // would terminate the program. void - finish(error_code& ec); + finish(beast::error_code& ec); }; //] @@ -151,7 +159,7 @@ public: // template file_body::reader:: -reader(message const& m) +reader(beast::http::message const& m) : path_(m.body) { } @@ -164,7 +172,7 @@ reader(message const& m) inline void file_body::reader:: -init(error_code& ec) +init(beast::error_code& ec) { // Attempt to open the file for reading file_ = fopen(path_.string().c_str(), "rb"); @@ -173,7 +181,7 @@ init(error_code& ec) { // Convert the old-school `errno` into // an error code using the system category. - ec = error_code{errno, system_category()}; + ec = beast::error_code{errno, beast::system_category()}; return; } @@ -191,7 +199,7 @@ init(error_code& ec) inline auto file_body::reader:: -get(error_code& ec) -> +get(beast::error_code& ec) -> boost::optional> { // Calculate the smaller of our buffer size, @@ -212,7 +220,7 @@ get(error_code& ec) -> if(ferror(file_)) { // Convert old-school `errno` to error_code - ec = error_code(errno, system_category()); + ec = beast::error_code(errno, beast::system_category()); return boost::none; } @@ -242,7 +250,7 @@ get(error_code& ec) -> inline void file_body::reader:: -finish(error_code& ec) +finish(beast::error_code& ec) { // Functions which pass back errors are // responsible for clearing the error code. @@ -281,20 +289,20 @@ public: // template explicit - writer(message& m); + writer(beast::http::message& m); // This function is called once before parsing // of the body is started. // void - init(boost::optional const& content_length, error_code& ec); + init(boost::optional const& content_length, beast::error_code& ec); // This function is called one or more times to store // buffer sequences corresponding to the incoming body. // template void - put(ConstBufferSequence const& buffers, error_code& ec); + put(ConstBufferSequence const& buffers, beast::error_code& ec); // This function is called when writing is complete. // It is an opportunity to perform any final actions @@ -304,7 +312,7 @@ public: // would terminate the program. // void - finish(error_code& ec); + finish(beast::error_code& ec); // Destructor. // @@ -320,7 +328,7 @@ public: // Just stash a reference to the path so we can open the file later. template file_body::writer:: -writer(message& m) +writer(beast::http::message& m) : path_(m.body) { } @@ -334,7 +342,7 @@ writer(message& m) inline void file_body::writer:: -init(boost::optional const& content_length, error_code& ec) +init(boost::optional const& content_length, beast::error_code& ec) { // Attempt to open the file for writing file_ = fopen(path_.string().c_str(), "wb"); @@ -343,7 +351,7 @@ init(boost::optional const& content_length, error_code& ec) { // Convert the old-school `errno` into // an error code using the system category. - ec = error_code{errno, system_category()}; + ec = beast::error_code{errno, beast::system_category()}; return; } @@ -356,7 +364,7 @@ init(boost::optional const& content_length, error_code& ec) template void 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, // and write each one to the file. @@ -372,7 +380,7 @@ put(ConstBufferSequence const& buffers, error_code& ec) if(ferror(file_)) { // Convert old-school `errno` to error_code - ec = error_code(errno, system_category()); + ec = beast::error_code(errno, beast::system_category()); return; } } @@ -385,8 +393,10 @@ put(ConstBufferSequence const& buffers, error_code& ec) inline void 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 = {}; } @@ -405,7 +415,4 @@ file_body::writer:: //] -} // http -} // beast - #endif diff --git a/example/server-framework/file_service.hpp b/example/server-framework/file_service.hpp new file mode 100644 index 00000000..c13bb103 --- /dev/null +++ b/example/server-framework/file_service.hpp @@ -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 +#include +#include +#include +#include +#include + +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&& 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 + beast::http::response + not_found(beast::http::request const& req, + boost::filesystem::path const& rel_path) const + { + beast::http::response 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 + beast::http::response + get(beast::http::request const& req, + boost::filesystem::path const& full_path) const + { + beast::http::response 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 + beast::http::response + head(beast::http::request const& req, + boost::filesystem::path const& full_path) const + { + beast::http::response 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 diff --git a/example/server-framework/framework.hpp b/example/server-framework/framework.hpp new file mode 100644 index 00000000..6c288c93 --- /dev/null +++ b/example/server-framework/framework.hpp @@ -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 +#include +#include +#include +#include + +/** 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 base_from_member +{ +public: + template + explicit + base_from_member(Args&&... args) + : member(std::forward(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 diff --git a/example/server-framework/http_async_port.hpp b/example/server-framework/http_async_port.hpp new file mode 100644 index 00000000..36d12658 --- /dev/null +++ b/example/server-framework/http_async_port.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 msg_; + Handler handler_; + +public: + // Constructor. + // + // Ownership of the message is transferred into the object + // + template + queued_http_write_impl( + Stream& stream, + beast::http::message&& msg, + DeducedHandler&& handler) + : stream_(stream) + , msg_(std::move(msg)) + , handler_(std::forward(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 +make_queued_http_write( + Stream& stream, + beast::http::message&& msg, + Handler&& handler) +{ + return std::unique_ptr{ + new queued_http_write_impl< + Stream, + isRequest, Body, Fields, + typename std::decay::type>{ + stream, + std::move(msg), + std::forward(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 async_http_con : public http_base +{ + // This function lets us access members of the derived class + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list 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> parser_; + + // This is the queue of outgoing messages + std::vector> 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 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 + void + operator()(beast::http::response&& 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 + void + do_write(beast::http::response&& 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 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> + + // 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 + + // Declare this base last now that everything else got set up first. + // + , public async_http_con, Services...> +{ +public: + // Construct the plain connection. + // + template + async_http_con_plain( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , async_http_con, Services...>( + std::forward(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 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_; + +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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + "http_async_port", + log_, + services_, + instance_.next_id(), + ep + )->run(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/http_base.hpp b/example/server-framework/http_base.hpp new file mode 100644 index 00000000..aa95cd4f --- /dev/null +++ b/example/server-framework/http_base.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +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 + beast::http::response + bad_request(beast::http::request const& req) const + { + beast::http::response 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 + beast::http::response + continue_100(beast::http::request const& req) const + { + beast::http::response 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 diff --git a/example/server-framework/http_sync_port.hpp b/example/server-framework/http_sync_port.hpp new file mode 100644 index 00000000..3b724c3b --- /dev/null +++ b/example/server-framework/http_sync_port.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 sync_http_con + : public http_base +{ + Derived& + impl() + { + return static_cast(*this); + } + + // The stream to use for logging + std::ostream& log_; + + // The services configured for the port + service_list 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 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 + void + operator()( + beast::http::response&& 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 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 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> + + // 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 + + // Declare this base last now that everything else got set up first. + // + , public sync_http_con, Services...> +{ +public: + // Construct the plain connection. + // + template + sync_http_con_plain( + socket_type&& sock, + Args&&... args) + : base_from_member(std::move(sock)) + , sync_http_con, Services...>( + std::forward(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 http_sync_port +{ + server& instance_; + std::ostream& log_; + service_list 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 + void + init(error_code& ec, Args&&... args) + { + services_.template init( + ec, + std::forward(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>( + std::move(sock), + "http_sync_port", + log_, + services_, + instance_.next_id(), + ep + )->run(); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/main.cpp b/example/server-framework/main.cpp new file mode 100644 index 00000000..aaeb96f2 --- /dev/null +++ b/example/server-framework/main.cpp @@ -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 + +#include + +/// 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 + void + operator()(beast::websocket::stream& 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()->default_value("."), + "Set the root directory for serving files") + ("port,p", po::value()->default_value(1000), + "Set the base port number for the server") + ("ip", po::value()->default_value("0.0.0.0"), + "Set the IP address to bind to, \"0.0.0.0\" for all") + ("threads,n", po::value()->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(); + + // Get the port number from the options + std::uint16_t const port = vm["port"].as(); + + // 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(); + + // Get the root path from the command line + boost::filesystem::path const root = vm["root"].as(); + + // 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( + 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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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( + ec, + endpoint_type{addr, + static_cast(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, + file_service + >>( + ec, + endpoint_type{addr, + static_cast(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(); +} diff --git a/example/server-framework/rfc7231.hpp b/example/server-framework/rfc7231.hpp new file mode 100644 index 00000000..6dcb3cbf --- /dev/null +++ b/example/server-framework/rfc7231.hpp @@ -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 +#include + +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 +bool +is_expect_100_continue(beast::http::request const& req) +{ + return beast::iequals( + req[beast::http::field::expect], "100-continue"); +} + +} // rfc7231 +} // framework + +#endif diff --git a/example/server-framework/server.hpp b/example/server-framework/server.hpp new file mode 100644 index 00000000..70f21489 --- /dev/null +++ b/example/server-framework/server.hpp @@ -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 +#include +#include +#include +#include +#include +#include + +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( + 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 tv_; + boost::optional 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 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 + std::shared_ptr + make_port( + error_code& ec, + endpoint_type const& ep, + Args&&... args); +}; + +//------------------------------------------------------------------------------ + +template +class port + : public std::enable_shared_from_this< + port> +{ + 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 + explicit + port(server& instance, Args&&... args) + : instance_(instance) + , handler_(std::forward(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 + 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( + 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 +std::shared_ptr +server:: +make_port( + error_code& ec, + endpoint_type const& ep, + Args&&... args) +{ + auto sp = std::make_shared>( + *this, std::forward(args)...); + sp->open(ep, ec); + if(ec) + return nullptr; + return sp->handler(); +} + +} // framework + +#endif diff --git a/example/server-framework/service_list.hpp b/example/server-framework/service_list.hpp new file mode 100644 index 00000000..2d47f39f --- /dev/null +++ b/example/server-framework/service_list.hpp @@ -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 +#include +#include + +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&& req, + Send const& send) const + }; + + @endcode + + @see file_service, ws_upgrade_service +*/ +template +class service_list +{ + // This helper is for tag-dispatching tuple index + template + using C = std::integral_constant; + + // 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...> 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 + void + init(error_code& ec, Args&&... args) + { + // First, construct the service inside the optional + std::get(list_).emplace(std::forward(args)...); + + // Now allow the service to finish the initialization + std::get(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 + void + send(response&&); + + @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&& 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&&, + Send const&, + C 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&& req, + Send const& send, + C const&) const + { + // If the I-th service handles the request then return + // + if(std::get(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{}); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ssl/ssl_stream.hpp b/example/server-framework/ssl/ssl_stream.hpp new file mode 100644 index 00000000..715cb498 --- /dev/null +++ b/example/server-framework/ssl/ssl_stream.hpp @@ -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 +#include +#include + +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 ssl_stream + : public boost::asio::ssl::stream_base +{ + using stream_type = boost::asio::ssl::stream; + + std::unique_ptr 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 + ssl_stream(Args&&... args) + : p_(new stream_type{std::forward(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 + void + set_verify_callback(VerifyCallback callback) + { + p_->set_verify_callback(callback); + } + + template + 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 + void + handshake( + handshake_type type, ConstBufferSequence const& buffers) + { + p_->handshake(type, buffers); + } + + template + boost::system::error_code + handshake(handshake_type type, + ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->handshake(type, buffers, ec); + } + + template + 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 + 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 + 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 + std::size_t + write_some(ConstBufferSequence const& buffers) + { + return p_->write_some(buffers); + } + + template + std::size_t + write_some(ConstBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->write_some(buffers, ec); + } + + template + 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 + std::size_t + read_some(MutableBufferSequence const& buffers) + { + return p_->read_some(buffers); + } + + template + std::size_t + read_some(MutableBufferSequence const& buffers, + boost::system::error_code& ec) + { + return p_->read_some(buffers, ec); + } + + template + 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 \ No newline at end of file diff --git a/example/server-framework/write_msg.hpp b/example/server-framework/write_msg.hpp new file mode 100644 index 00000000..e5d25c0e --- /dev/null +++ b/example/server-framework/write_msg.hpp @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 msg; + + data( + Handler& handler, + AsyncWriteStream& stream_, + beast::http::message&& msg_) + : stream(stream_) + , msg(std::move(msg_)) + { + } + }; + + beast::handler_ptr d_; + +public: + write_msg_op(write_msg_op&&) = default; + write_msg_op(write_msg_op const&) = default; + + template + write_msg_op(DeducedHandler&& h, AsyncWriteStream& s, Args&&... args) + : d_(std::forward(h), + s, std::forward(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 + 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 composed operation. + 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 +async_write_msg( + AsyncWriteStream& stream, + beast::http::message&& msg, + WriteHandler&& handler) +{ + static_assert( + beast::is_async_write_stream::value, + "AsyncWriteStream requirements not met"); + + static_assert(beast::http::is_body::value, + "Body requirements not met"); + + static_assert(beast::http::is_body_reader::value, + "BodyReader requirements not met"); + + beast::async_completion init{handler}; + + detail::write_msg_op< + AsyncWriteStream, + beast::handler_type, + isRequest, Body, Fields>{ + init.completion_handler, + stream, + std::move(msg)}(); + + return init.result.get(); +} + +} // framework + +#endif diff --git a/example/server-framework/ws_async_port.hpp b/example/server-framework/ws_async_port.hpp new file mode 100644 index 00000000..71a80044 --- /dev/null +++ b/example/server-framework/ws_async_port.hpp @@ -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 +#include +#include +#include +#include + +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 async_ws_con +{ + Derived& + impl() + { + return static_cast(*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 + 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 + void + run(beast::http::request 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 + , public base_from_member> + , public async_ws_con +{ +public: + template + explicit + async_ws_con_plain( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , async_ws_con(std::forward(args)...) + { + } + + beast::websocket::stream& + 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&)>; + + 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + accept( + socket_type&& stream, + endpoint_type ep, + beast::http::request&& req) + { + std::make_shared( + std::move(stream), + "ws_async_port", + log_, + instance_.next_id(), + ep, + cb_ + )->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ws_sync_port.hpp b/example/server-framework/ws_sync_port.hpp new file mode 100644 index 00000000..5636a9c3 --- /dev/null +++ b/example/server-framework/ws_sync_port.hpp @@ -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 +#include +#include +#include +#include +#include + +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 sync_ws_con +{ + Derived& + impl() + { + return static_cast(*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 + 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 + void + run(beast::http::request&& 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{ + 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 lambda + { + std::shared_ptr self_; + beast::http::request req_; + + public: + // Constructor + // + // This is the equivalent of the capture section of the lambda. + // + lambda( + std::shared_ptr self, + beast::http::request&& 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 + , public base_from_member> + , public sync_ws_con +{ +public: + template + explicit + sync_ws_con_plain( + socket_type&& sock, + Args&&... args) + : base_from_member>(std::move(sock)) + , sync_ws_con(std::forward(args)...) + { + } + + beast::websocket::stream& + 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&)>; + + 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 + void callback(beast::websocket::stream&); + @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 + 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( + 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 + void + accept( + socket_type&& sock, + endpoint_type ep, + beast::http::request&& req) + { + // Create the connection object and run it, + // transferring ownershop of the ugprade request. + // + std::make_shared( + std::move(sock), + "ws_sync_port", + log_, + instance_.next_id(), + ep, + cb_ + )->run(std::move(req)); + } +}; + +} // framework + +#endif diff --git a/example/server-framework/ws_upgrade_service.hpp b/example/server-framework/ws_upgrade_service.hpp new file mode 100644 index 00000000..8e80dc35 --- /dev/null +++ b/example/server-framework/ws_upgrade_service.hpp @@ -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 +#include +#include + +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 ws_upgrade_service +{ + std::shared_ptr handler_; + +public: + /** Constructor + + @param handler A shared pointer to the @b PortHandler to + handle WebSocket upgrade requests. + */ + explicit + ws_upgrade_service( + std::shared_ptr 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&& 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 diff --git a/example/websocket-server/CMakeLists.txt b/example/websocket-server/CMakeLists.txt deleted file mode 100644 index bb081d7d..00000000 --- a/example/websocket-server/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/example/websocket-server/Jamfile b/example/websocket-server/Jamfile deleted file mode 100644 index 97223064..00000000 --- a/example/websocket-server/Jamfile +++ /dev/null @@ -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 - ; diff --git a/example/websocket-server/main.cpp b/example/websocket-server/main.cpp deleted file mode 100644 index 7ed6808d..00000000 --- a/example/websocket-server/main.cpp +++ /dev/null @@ -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 -#include -#include - -/// 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 - void - operator()(beast::websocket::stream& 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(); -} diff --git a/example/websocket-server/websocket_async_echo_server.hpp b/example/websocket-server/websocket_async_echo_server.hpp deleted file mode 100644 index 9bccbe06..00000000 --- a/example/websocket-server/websocket_async_echo_server.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 thread_; - boost::optional work_; - std::function&)> 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 - 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 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 n{0}; - return ++n; - }()) - { - } - }; - - // VFALCO This could be unique_ptr in [Net.TS] - std::shared_ptr d_; - - public: - peer(peer&&) = default; - peer(peer const&) = default; - peer& operator=(peer&&) = delete; - peer& operator=(peer const&) = delete; - - template - explicit - peer(async_echo_server& server, - endpoint_type const& ep, socket_type&& sock, - Args&&... args) - : d_(std::make_shared(server, ep, - std::forward(sock), - std::forward(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(d.ep) + - "] " + what, ec); - } - }; - - void - fail(std::string what, error_code ec) - { - if(log_) - { - static std::mutex m; - std::lock_guard 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 diff --git a/example/websocket-server/websocket_sync_echo_server.hpp b/example/websocket-server/websocket_sync_echo_server.hpp deleted file mode 100644 index 369cc16a..00000000 --- a/example/websocket-server/websocket_sync_echo_server.hpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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&)> 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 - 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 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(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 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 diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e92e3c3..98981a05 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,5 +1,11 @@ # Part of Beast +add_subdirectory (core) +add_subdirectory (http) +add_subdirectory (server) +add_subdirectory (websocket) +add_subdirectory (zlib) + GroupSources(extras/beast extras) GroupSources(include/beast beast) GroupSources(test "/") diff --git a/test/Jamfile b/test/Jamfile index bee625f4..a19c70a1 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -14,6 +14,8 @@ compile version.cpp : : ; compile websocket.cpp : : ; compile zlib.cpp : : ; +build-project server ; + unit-test core-tests : ../extras/beast/unit_test/main.cpp core/async_result.cpp diff --git a/test/http/doc_examples.cpp b/test/http/doc_examples.cpp index 3b780d30..064d73ad 100644 --- a/test/http/doc_examples.cpp +++ b/test/http/doc_examples.cpp @@ -6,7 +6,7 @@ // #include "example/doc/http_examples.hpp" -#include "example/http-server/file_body.hpp" +#include "example/server-framework/file_body.hpp" #include #include diff --git a/test/server/CMakeLists.txt b/test/server/CMakeLists.txt new file mode 100644 index 00000000..f0fa20a9 --- /dev/null +++ b/test/server/CMakeLists.txt @@ -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) + diff --git a/test/server/Jamfile b/test/server/Jamfile new file mode 100644 index 00000000..17fcc5cd --- /dev/null +++ b/test/server/Jamfile @@ -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 + ; diff --git a/test/server/file_body.cpp b/test/server/file_body.cpp new file mode 100644 index 00000000..3a30b7ba --- /dev/null +++ b/test/server/file_body.cpp @@ -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" + diff --git a/test/server/file_service.cpp b/test/server/file_service.cpp new file mode 100644 index 00000000..334c60b2 --- /dev/null +++ b/test/server/file_service.cpp @@ -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" + diff --git a/test/server/framework.cpp b/test/server/framework.cpp new file mode 100644 index 00000000..ac995427 --- /dev/null +++ b/test/server/framework.cpp @@ -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" + diff --git a/test/server/http_async_port.cpp b/test/server/http_async_port.cpp new file mode 100644 index 00000000..dfed5a0a --- /dev/null +++ b/test/server/http_async_port.cpp @@ -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" + diff --git a/test/server/http_base.cpp b/test/server/http_base.cpp new file mode 100644 index 00000000..b27661d1 --- /dev/null +++ b/test/server/http_base.cpp @@ -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" + diff --git a/test/server/http_sync_port.cpp b/test/server/http_sync_port.cpp new file mode 100644 index 00000000..4625ad94 --- /dev/null +++ b/test/server/http_sync_port.cpp @@ -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" + diff --git a/test/server/main.cpp b/test/server/main.cpp new file mode 100644 index 00000000..e9335ff3 --- /dev/null +++ b/test/server/main.cpp @@ -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) +{ +} diff --git a/test/server/rfc7231.cpp b/test/server/rfc7231.cpp new file mode 100644 index 00000000..1ec53550 --- /dev/null +++ b/test/server/rfc7231.cpp @@ -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" + diff --git a/test/server/server.cpp b/test/server/server.cpp new file mode 100644 index 00000000..0920a344 --- /dev/null +++ b/test/server/server.cpp @@ -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" + diff --git a/test/server/service_list.cpp b/test/server/service_list.cpp new file mode 100644 index 00000000..dd1569f8 --- /dev/null +++ b/test/server/service_list.cpp @@ -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" + diff --git a/test/server/write_msg.cpp b/test/server/write_msg.cpp new file mode 100644 index 00000000..bea6c8da --- /dev/null +++ b/test/server/write_msg.cpp @@ -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" + diff --git a/test/server/ws_async_port.cpp b/test/server/ws_async_port.cpp new file mode 100644 index 00000000..e826d97c --- /dev/null +++ b/test/server/ws_async_port.cpp @@ -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" + diff --git a/test/server/ws_sync_port.cpp b/test/server/ws_sync_port.cpp new file mode 100644 index 00000000..8bba84d8 --- /dev/null +++ b/test/server/ws_sync_port.cpp @@ -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" + diff --git a/test/server/ws_upgrade_service.cpp b/test/server/ws_upgrade_service.cpp new file mode 100644 index 00000000..dcabff75 --- /dev/null +++ b/test/server/ws_upgrade_service.cpp @@ -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" +