From fd9a13b11fd3c26123cf361f2495c3589dd0357c Mon Sep 17 00:00:00 2001 From: Vinnie Falco Date: Mon, 12 Jun 2017 19:16:39 -0700 Subject: [PATCH] New server-framework, full featured server example: A new server framework is introduced, allowing users to quickly get off the ground. Example servers are refactored to use the common framework. --- CHANGELOG.md | 1 + CMakeLists.txt | 8 +- doc/0_main.qbk | 2 +- doc/1_overview.qbk | 8 +- doc/2_examples.qbk | 98 ++-- doc/3_1_asio.qbk | 14 +- doc/5_00_http.qbk | 4 +- ...9_custom_body.qbk => 5_08_custom_body.qbk} | 0 ...om_parsers.qbk => 5_09_custom_parsers.qbk} | 0 doc/images/server.png | Bin 0 -> 18586 bytes example/CMakeLists.txt | 3 +- example/Jamfile | 3 +- example/http-crawl/urls_large_data.hpp | 4 +- example/http-server/CMakeLists.txt | 18 - example/http-server/http_async_server.hpp | 323 ---------- example/http-server/http_sync_server.hpp | 215 ------- example/http-server/main.cpp | 61 -- example/http-server/mime_type.hpp | 52 -- example/server-framework/CMakeLists.txt | 17 + .../{http-server => server-framework}/Jamfile | 2 +- example/server-framework/README.md | 68 +++ .../file_body.hpp | 63 +- example/server-framework/file_service.hpp | 248 ++++++++ example/server-framework/framework.hpp | 53 ++ example/server-framework/http_async_port.hpp | 550 ++++++++++++++++++ example/server-framework/http_base.hpp | 77 +++ example/server-framework/http_sync_port.hpp | 354 +++++++++++ example/server-framework/main.cpp | 242 ++++++++ example/server-framework/rfc7231.hpp | 40 ++ example/server-framework/server.hpp | 258 ++++++++ example/server-framework/service_list.hpp | 224 +++++++ example/server-framework/ssl/ssl_stream.hpp | 264 +++++++++ example/server-framework/write_msg.hpp | 188 ++++++ example/server-framework/ws_async_port.hpp | 330 +++++++++++ example/server-framework/ws_sync_port.hpp | 369 ++++++++++++ .../server-framework/ws_upgrade_service.hpp | 102 ++++ example/websocket-server/CMakeLists.txt | 15 - example/websocket-server/Jamfile | 10 - example/websocket-server/main.cpp | 75 --- .../websocket_async_echo_server.hpp | 282 --------- .../websocket_sync_echo_server.hpp | 231 -------- test/CMakeLists.txt | 6 + test/Jamfile | 2 + test/http/doc_examples.cpp | 2 +- test/server/CMakeLists.txt | 28 + test/server/Jamfile | 23 + test/server/file_body.cpp | 10 + test/server/file_service.cpp | 10 + test/server/framework.cpp | 10 + test/server/http_async_port.cpp | 10 + test/server/http_base.cpp | 10 + test/server/http_sync_port.cpp | 10 + test/server/main.cpp | 10 + test/server/rfc7231.cpp | 10 + test/server/server.cpp | 10 + test/server/service_list.cpp | 10 + test/server/write_msg.cpp | 10 + test/server/ws_async_port.cpp | 10 + test/server/ws_sync_port.cpp | 10 + test/server/ws_upgrade_service.cpp | 10 + 60 files changed, 3701 insertions(+), 1376 deletions(-) rename doc/{5_09_custom_body.qbk => 5_08_custom_body.qbk} (100%) rename doc/{5_08_custom_parsers.qbk => 5_09_custom_parsers.qbk} (100%) create mode 100644 doc/images/server.png delete mode 100644 example/http-server/CMakeLists.txt delete mode 100644 example/http-server/http_async_server.hpp delete mode 100644 example/http-server/http_sync_server.hpp delete mode 100644 example/http-server/main.cpp delete mode 100644 example/http-server/mime_type.hpp create mode 100644 example/server-framework/CMakeLists.txt rename example/{http-server => server-framework}/Jamfile (91%) create mode 100644 example/server-framework/README.md rename example/{http-server => server-framework}/file_body.hpp (84%) create mode 100644 example/server-framework/file_service.hpp create mode 100644 example/server-framework/framework.hpp create mode 100644 example/server-framework/http_async_port.hpp create mode 100644 example/server-framework/http_base.hpp create mode 100644 example/server-framework/http_sync_port.hpp create mode 100644 example/server-framework/main.cpp create mode 100644 example/server-framework/rfc7231.hpp create mode 100644 example/server-framework/server.hpp create mode 100644 example/server-framework/service_list.hpp create mode 100644 example/server-framework/ssl/ssl_stream.hpp create mode 100644 example/server-framework/write_msg.hpp create mode 100644 example/server-framework/ws_async_port.hpp create mode 100644 example/server-framework/ws_sync_port.hpp create mode 100644 example/server-framework/ws_upgrade_service.hpp delete mode 100644 example/websocket-server/CMakeLists.txt delete mode 100644 example/websocket-server/Jamfile delete mode 100644 example/websocket-server/main.cpp delete mode 100644 example/websocket-server/websocket_async_echo_server.hpp delete mode 100644 example/websocket-server/websocket_sync_echo_server.hpp create mode 100644 test/server/CMakeLists.txt create mode 100644 test/server/Jamfile create mode 100644 test/server/file_body.cpp create mode 100644 test/server/file_service.cpp create mode 100644 test/server/framework.cpp create mode 100644 test/server/http_async_port.cpp create mode 100644 test/server/http_base.cpp create mode 100644 test/server/http_sync_port.cpp create mode 100644 test/server/main.cpp create mode 100644 test/server/rfc7231.cpp create mode 100644 test/server/server.cpp create mode 100644 test/server/service_list.cpp create mode 100644 test/server/write_msg.cpp create mode 100644 test/server/ws_async_port.cpp create mode 100644 test/server/ws_sync_port.cpp create mode 100644 test/server/ws_upgrade_service.cpp 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 0000000000000000000000000000000000000000..52f0c2e228ed2ba4e829b0ccfa5ca3efaee13f19 GIT binary patch literal 18586 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 Mp00i_>zopr05$b)ZU6uP literal 0 HcmV?d00001 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" +