diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb02ee9..d6c39482 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +Version 101: + +* Refactor all examples + +-------------------------------------------------------------------------------- + Version 100: * Fix doc convenience includes diff --git a/build/build-and-test.sh b/build/build-and-test.sh index c24317bb..456e7f9e 100755 --- a/build/build-and-test.sh +++ b/build/build-and-test.sh @@ -97,7 +97,9 @@ INC_DIR="$BOOST_ROOT/boost/beast" function build_bjam () { - if [[ $VARIANT == "coverage" ]]; then + if [[ $VARIANT == "coverage" ]] || \ + [[ $VARIANT == "valgrind" ]] || \ + [[ $VARIANT == "ubasan" ]]; then bjam \ libs/beast/test/beast/core//fat-tests \ libs/beast/test/beast/http//fat-tests \ diff --git a/doc/images/server.png b/doc/images/server.png deleted file mode 100644 index fc609800..00000000 Binary files a/doc/images/server.png and /dev/null differ diff --git a/doc/qbk/00_main.qbk b/doc/qbk/00_main.qbk index e138c0e3..45453063 100644 --- a/doc/qbk/00_main.qbk +++ b/doc/qbk/00_main.qbk @@ -85,8 +85,8 @@ [import ../../example/common/detect_ssl.hpp] [import ../../example/doc/http_examples.hpp] [import ../../example/echo-op/echo_op.cpp] -[import ../../example/http-client/http_client.cpp] -[import ../../example/websocket-client/websocket_client.cpp] +[import ../../example/http/client/sync/http_client_sync.cpp] +[import ../../example/websocket/client/sync/websocket_client_sync.cpp] [import ../../include/boost/beast/http/basic_file_body.hpp] diff --git a/doc/qbk/02_examples.qbk b/doc/qbk/02_examples.qbk index 1f01da8a..7ea8a8ab 100644 --- a/doc/qbk/02_examples.qbk +++ b/doc/qbk/02_examples.qbk @@ -14,13 +14,11 @@ These complete programs are intended to quickly impress upon readers the flavor of the library. Source code and build scripts for them are located in the example/ directory. - - [section HTTP Client] Use HTTP to make a GET request to a website and print the response: -File: [repo_file example/http-client/http_client.cpp] +File: [repo_file example/http/client/sync/http_client_sync.cpp] [example_http_client] @@ -30,7 +28,7 @@ File: [repo_file example/http-client/http_client.cpp] Establish a WebSocket connection, send a message and receive the reply: -File: [repo_file example/websocket-client/websocket_client.cpp] +File: [repo_file example/websocket/client/sync/websocket_client_sync.cpp] [example_websocket_client] @@ -44,85 +42,196 @@ File: [repo_file example/websocket-client/websocket_client.cpp] [block''''''] Source code and build scripts for these programs are located -in the example/ directory. +in the [repo_file example] directory. -[section HTTP Crawl] +[template example_src[path name] ''''''[name]''''''] -This example retrieves the page at each of the most popular domains -as measured by Alexa. +[heading Clients] -* [repo_file example/http-crawl/http_crawl.cpp] +These HTTP clients submit a GET request to a server specified on the command +line, and prints the resulting response. The crawl client asynchronously +fetches the document root of the 10,000 top ranked domains, this may be +used to evaluate robustness. -[endsect] +[table +[[Description] [Source File] [Source File (using SSL)]] +[ + [HTTP, synchronous] + [[example_src example/http/client/sync/http_client_sync.cpp http_client_sync.cpp]] + [[example_src example/http/client/sync-ssl/http_client_sync_ssl.cpp http_client_sync_ssl.cpp]] +][ + [HTTP, asynchronous] + [[example_src example/http/client/async/http_client_async.cpp http_client_async.cpp]] + [[example_src example/http/client/async-ssl/http_client_async_ssl.cpp http_client_async_ssl.cpp]] +][ + [HTTP, coroutine] + [[example_src example/http/client/coro/http_client_coro.cpp http_client_coro.cpp]] + [[example_src example/http/client/coro-ssl/http_client_coro_ssl.cpp http_client_coro_ssl.cpp]] +][ + [HTTP crawl (asynchronous)] + [[example_src example/http/client/crawl/http_crawl.cpp http_crawl.cpp]] + [] +]] +These WebSocket clients connect to a +server and send a message, then receive a message and print the response +before disconnecting. +[table +[[Description] [Source File] [Source File (using SSL)]] +[ + [WebSocket, synchronous] + [[example_src example/websocket/client/sync/websocket_client_sync.cpp websocket_client_sync.cpp]] + [[example_src example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp websocket_client_sync_ssl.cpp]] +][ + [WebSocket, asynchronous] + [[example_src example/websocket/client/async/websocket_client_async.cpp websocket_client_async.cpp]] + [[example_src example/websocket/client/async-ssl/websocket_client_async_ssl.cpp websocket_client_async_ssl.cpp]] +][ + [WebSocket, coroutine] + [[example_src example/websocket/client/coro/websocket_client_coro.cpp websocket_client_coro.cpp]] + [[example_src example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp websocket_client_coro_ssl.cpp]] +]] -[section HTTP Client (with SSL)] +[heading Servers] -This example demonstrates sending and receiving HTTP messages -over a TLS connection. Requires OpenSSL to build. +These HTTP servers deliver files from a root directory specified on the +command line. -* [repo_file example/http-client-ssl/http_client_ssl.cpp] +[table +[[Description] [Source File] [Source File (using SSL)]] +[ + [HTTP, synchronous] + [[example_src example/http/server/sync/http_server_sync.cpp http_server_sync.cpp]] + [[example_src example/http/server/sync-ssl/http_server_sync_ssl.cpp http_server_sync_ssl.cpp]] +][ + [HTTP, asynchronous] + [[example_src example/http/server/async/http_server_async.cpp http_server_async.cpp]] + [[example_src example/http/server/async-ssl/http_server_async_ssl.cpp http_server_async_ssl.cpp]] +][ + [HTTP, coroutine] + [[example_src example/http/server/coro/http_server_coro.cpp http_server_coro.cpp]] + [[example_src example/http/server/coro-ssl/http_server_coro_ssl.cpp http_server_coro_ssl.cpp]] +][ + [HTTP, stackless coroutine] + [[example_src example/http/server/stackless/http_server_stackless.cpp http_server_stackless.cpp]] + [[example_src example/http/server/stackless-ssl/http_server_stackless_ssl.cpp http_server_stackless_ssl.cpp]] +][ + [HTTP, fast (optimized for speed)] + [[example_src example/http/server/fast/http_server_fast.cpp http_server_fast.cpp]] + [] +][ + [HTTP, small (optimized for space)] + [[example_src example/http/server/small/http_server_small.cpp http_server_small.cpp]] + [] +][ + [HTTP, flex (plain + SSL)] + [] + [[example_src example/http/server/flex/http_server_flex.cpp http_server_flex.cpp]] +]] -[endsect] +These WebSocket servers echo back any message received, keeping the +session open until the client disconnects. +[table +[[Description] [Source File] [Source File (using SSL)]] +[ + [WebSocket, synchronous] + [[example_src example/websocket/server/sync/websocket_server_sync.cpp websocket_server_sync.cpp]] + [[example_src example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp websocket_server_sync_ssl.cpp]] +][ + [WebSocket, asynchronous] + [[example_src example/websocket/server/async/websocket_server_async.cpp websocket_server_async.cpp]] + [[example_src example/websocket/server/async-ssl/websocket_server_async_ssl.cpp websocket_server_async_ssl.cpp]] +][ + [WebSocket, coroutine] + [[example_src example/websocket/server/coro/websocket_server_coro.cpp websocket_server_coro.cpp]] + [[example_src example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp websocket_server_coro_ssl.cpp]] +][ + [WebSocket, stackless coroutine] + [[example_src example/websocket/server/stackless/websocket_server_stackless.cpp websocket_server_stackless.cpp]] + [[example_src example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp websocket_server_stackless_ssl.cpp]] +][ + [WebSocket, fast (suited for benchmarks)] + [[example_src example/websocket/server/fast/websocket_server_fast.cpp websocket_server_fast.cpp]] + [] +]] +[heading Advanced Servers] -[section HTTP Server (Fast)] +These servers offer both HTTP and WebSocket services on the same port, +and illustrate the implementation of advanced features. -This example implements a very simple HTTP server with -some optimizations suitable for calculating benchmarks. +[table +[[Description] [Features] [Source File]] +[ + [Advanced] + [[itemized_list + [HTTP pipelining] + [Asynchronous timeouts] + [Dual protocols: HTTP and WebSocket]]] + [[example_src example/advanced/server/advanced_server.cpp advanced_server.cpp]] +][ + [Advanced, flex (plain + SSL)] + [[itemized_list + [HTTP pipelining] + [Asynchronous timeouts] + [Dual protocols: HTTP and WebSocket] + [Flexible ports; plain and SSL on the same port]]] + [[example_src example/advanced/server-flex/advanced_server_flex.cpp advanced_server_flex.cpp]] +]] -* [repo_file example/http-server-fast/fields_alloc.hpp] -* [repo_file example/http-server-fast/http_server_fast.cpp] +[heading Common Files] -[endsect] +Some of the examples use one or more shared header files, they are +listed here along with a description of their use: - - -[section HTTP Server (Small)] - -This example implements a very simple HTTP server -suitable as a starting point on an embedded device. - -* [repo_file example/http-server-small/http_server_small.cpp] - -[endsect] - - - -[section HTTP Server (Threaded)] - -This example implements a very simple HTTP server using -synchronous interfaces and using one thread per connection: - -* [repo_file example/http-server-threaded/http_server_threaded.cpp] - -[endsect] - - - -[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/websocket-client-ssl/websocket_client_ssl.cpp] - -[endsect] - - - -[section WebSocket Server (Asynchronous)] - -This program implements a WebSocket echo server using asynchronous -interfaces and a configurable number of threads. - -* [repo_file example/websocket-server-async/websocket_server_async.cpp] - -[endsect] +[table +[[Source File] [Description]] +[ + [[repo_file example/common/detect_ssl.hpp]] + [ + This contains the detect SSL algorithm including the + synchronous and asynchronous initiating functions, described + in the documentation. It is used by the "flex" servers which + support both plain and SSL sessions on the same port. + ] +][ + [[repo_file example/common/root_certificates.hpp]] + [ + This contains the root SSL certificates used in the SSL client + examples. These certificates are used to verify the signatures + of SSL certificates presented by remote servers. They represent + a subset of the public keys usually installed as part of the + operating system or browser, so they may not identify every + possible server. + ] +][ + [[repo_file example/common/server_certificate.hpp]] + [ + This file contains a self-signed SSL certificate used by the + SSL server examples. It has not been validated by a Certificate + Authority ("CA") so connecting to an example HTTP server using + a browser may generate security warnings. + ] +][ + [[repo_file example/common/ssl_stream.hpp]] + [ + The `ssl_stream` is a replacement for `boost::asio::ssl::stream` + which supports construction from a moved-froms ocket and is also + itself move constructible. + ] +][ + [[repo_file example/common/write_msg.hpp]] + [ + The function `async_write_msg` takes ownership of an HTTP message + using the move constructor, and sends it asynchronously. It is + used as a convenience for managing the lifetime of temporary + objects, as well as for implementing HTTP pipelining. + ] +]] @@ -143,7 +252,8 @@ in your program without modification This program shows how to use Beast's network foundations to build a composable asynchronous initiation function with associated composed operation implementation. This is a complete, runnable version of -the example described in the Core Foundations document section. +the example described in +[link beast.using_io.writing_composed_operations.echo Writing Composed Operations: Echo]. * [repo_file example/echo-op/echo_op.cpp] @@ -151,46 +261,4 @@ the example described in the Core Foundations document section. -[section Common Code] - -This code is reused between some of the examples. The header files -stand alone can be directly included in your projects. - -* [repo_file example/common/detect_ssl.hpp] -* [repo_file example/common/helpers.hpp] -* [repo_file example/common/mime_types.hpp] -* [repo_file example/common/rfc7231.hpp] -* [repo_file example/common/ssl_stream.hpp] -* [repo_file example/common/write_msg.hpp] - -[endsect] - - - -[section Server Framework] - -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/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/https_ports.hpp] -* [repo_file example/server-framework/main.cpp] -* [repo_file example/server-framework/multi_port.hpp] -* [repo_file example/server-framework/server.hpp] -* [repo_file example/server-framework/service_list.hpp] -* [repo_file example/server-framework/ssl_certificate.hpp] -* [repo_file example/server-framework/ws_async_port.hpp] -* [repo_file example/server-framework/ws_sync_port.hpp] -* [repo_file example/server-framework/ws_upgrade_service.hpp] -* [repo_file example/server-framework/wss_ports.hpp] - -[endsect] - - - [endsect] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fde3a395..950c29db 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -7,17 +7,8 @@ # Official repository: https://github.com/boostorg/beast # -add_subdirectory (echo-op) -add_subdirectory (http-client) -add_subdirectory (http-crawl) -add_subdirectory (http-server-fast) -add_subdirectory (http-server-small) -add_subdirectory (http-server-threaded) -add_subdirectory (server-framework) -add_subdirectory (websocket-client) -add_subdirectory (websocket-server-async) +add_subdirectory (advanced) +add_subdirectory (http) +add_subdirectory (websocket) -if (OPENSSL_FOUND) - add_subdirectory (http-client-ssl) - add_subdirectory (websocket-client-ssl) -endif() +add_subdirectory (echo-op) diff --git a/example/Jamfile b/example/Jamfile index e7059521..cccbfe4b 100644 --- a/example/Jamfile +++ b/example/Jamfile @@ -7,16 +7,9 @@ # Official repository: https://github.com/boostorg/beast # -build-project echo-op ; -build-project http-client ; -build-project http-crawl ; -build-project http-server-fast ; -build-project http-server-small ; -build-project http-server-threaded ; -build-project server-framework ; -build-project websocket-client ; -build-project websocket-server-async ; +build-project advanced ; +build-project http ; +build-project websocket ; -# VFALCO How do I make this work on Windows and if OpenSSL is not available? -#build-project ssl-http-client ; -#build-project ssl-websocket-client ; +# legacy +build-project echo-op ; diff --git a/example/advanced/CMakeLists.txt b/example/advanced/CMakeLists.txt new file mode 100644 index 00000000..af83e043 --- /dev/null +++ b/example/advanced/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (server) +add_subdirectory (server-flex) diff --git a/example/advanced/Jamfile b/example/advanced/Jamfile new file mode 100644 index 00000000..2524238b --- /dev/null +++ b/example/advanced/Jamfile @@ -0,0 +1,13 @@ +# +# Copyright (c) 2013-2017 Vinnie Falco (vinnie dot falco at gmail dot com) +# +# Distributed under the Boost Software License, Version 1.0. (See accompanying +# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project server ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project server-flex ; diff --git a/example/advanced/server-flex/CMakeLists.txt b/example/advanced/server-flex/CMakeLists.txt new file mode 100644 index 00000000..fca97db8 --- /dev/null +++ b/example/advanced/server-flex/CMakeLists.txt @@ -0,0 +1,26 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/advanced/server-flex "/") + +add_executable (advanced-server-flex + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + ${PROJECT_SOURCE_DIR}/example/common/ssl_stream.hpp + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp + Jamfile + advanced_server_flex.cpp +) + +target_link_libraries (advanced-server-flex + ${OPENSSL_LIBRARIES} + ) diff --git a/example/advanced/server-flex/Jamfile b/example/advanced/server-flex/Jamfile new file mode 100644 index 00000000..dfa18256 --- /dev/null +++ b/example/advanced/server-flex/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe advanced-server-flex : + advanced_server_flex.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/advanced/server-flex/advanced_server_flex.cpp b/example/advanced/server-flex/advanced_server_flex.cpp new file mode 100644 index 00000000..fee47c78 --- /dev/null +++ b/example/advanced/server-flex/advanced_server_flex.cpp @@ -0,0 +1,1171 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: Advanced server, flex (plain + SSL) +// +//------------------------------------------------------------------------------ + +#include "example/common/detect_ssl.hpp" +#include "example/common/server_certificate.hpp" +#include "example/common/ssl_stream.hpp" +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from +namespace websocket = boost::beast::websocket; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages. +// This uses the Curiously Recurring Template Pattern so that +// the same code works with both SSL streams and regular sockets. +template +class websocket_session +{ + // Access the derived class, this is part of + // the Curiously Recurring Template Pattern idiom. + Derived& + derived() + { + return static_cast(*this); + } + + boost::beast::multi_buffer buffer_; + +protected: + boost::asio::io_service::strand strand_; + boost::asio::steady_timer timer_; + +public: + // Construct the session + explicit + websocket_session(boost::asio::io_service& ios) + : strand_(ios) + , timer_(ios, + (std::chrono::steady_clock::time_point::max)()) + { + } + + // Start the asynchronous operation + template + void + do_accept(http::request> req) + { + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Accept the websocket handshake + derived().ws().async_accept( + req, + strand_.wrap(std::bind( + &websocket_session::on_accept, + derived().shared_from_this(), + std::placeholders::_1))); + } + + // Called when the timer expires. + void + on_timer(boost::system::error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + return fail(ec, "timer"); + + // Verify that the timer really expired since the deadline may have moved. + if(timer_.expires_at() <= std::chrono::steady_clock::now()) + derived().do_timeout(); + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &websocket_session::on_timer, + derived().shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read a message into our buffer + derived().ws().async_read( + buffer_, + strand_.wrap(std::bind( + &websocket_session::on_read, + derived().shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + // This indicates that the websocket_session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + derived().ws().text(derived().ws().got_text()); + derived().ws().async_write( + buffer_.data(), + strand_.wrap(std::bind( + &websocket_session::on_write, + derived().shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +// Handles a plain WebSocket connection +class plain_websocket_session + : public websocket_session + , public std::enable_shared_from_this +{ + websocket::stream ws_; + bool close_ = false; + +public: + // Create the session + explicit + plain_websocket_session(tcp::socket socket) + : websocket_session( + socket.get_io_service()) + , ws_(std::move(socket)) + { + } + + // Called by the base class + websocket::stream& + ws() + { + return ws_; + } + + // Start the asynchronous operation + template + void + run(http::request> req) + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + // Accept the WebSocket upgrade request + do_accept(std::move(req)); + } + + void + do_timeout() + { + // This is so the close can have a timeout + if(close_) + return; + close_ = true; + + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Close the WebSocket Connection + ws_.async_close( + websocket::close_code::normal, + strand_.wrap(std::bind( + &plain_websocket_session::on_close, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_close(boost::system::error_code ec) + { + // Happens when close times out + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "close"); + + // At this point the connection is gracefully closed + } +}; + +// Handles an SSL WebSocket connection +class ssl_websocket_session + : public websocket_session + , public std::enable_shared_from_this +{ + websocket::stream> ws_; + boost::asio::io_service::strand strand_; + bool eof_ = false; + +public: + // Create the http_session + explicit + ssl_websocket_session(ssl_stream stream) + : websocket_session( + stream.get_io_service()) + , ws_(std::move(stream)) + , strand_(ws_.get_io_service()) + { + } + + // Called by the base class + websocket::stream>& + ws() + { + return ws_; + } + + // Start the asynchronous operation + template + void + run(http::request> req) + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + // Accept the WebSocket upgrade request + do_accept(std::move(req)); + } + + void + do_eof() + { + eof_ = true; + + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Perform the SSL shutdown + ws_.next_layer().async_shutdown( + strand_.wrap(std::bind( + &ssl_websocket_session::on_shutdown, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_shutdown(boost::system::error_code ec) + { + // Happens when the shutdown times out + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } + + void + do_timeout() + { + // If this is true it means we timed out performing the shutdown + if(eof_) + return; + + // Start the timer again + timer_.expires_at( + (std::chrono::steady_clock::time_point::max)()); + on_timer({}); + do_eof(); + } +}; + +template +void +make_websocket_session( + tcp::socket socket, + http::request> req) +{ + std::make_shared( + std::move(socket))->run(std::move(req)); +} + +template +void +make_websocket_session( + ssl_stream stream, + http::request> req) +{ + std::make_shared( + std::move(stream))->run(std::move(req)); +} + +//------------------------------------------------------------------------------ + +// Handles an HTTP server connection. +// This uses the Curiously Recurring Template Pattern so that +// the same code works with both SSL streams and regular sockets. +template +class http_session +{ + // Access the derived class, this is part of + // the Curiously Recurring Template Pattern idiom. + Derived& + derived() + { + return static_cast(*this); + } + + // This queue is used for HTTP pipelining. + class queue + { + enum + { + // Maximum number of responses we will queue + limit = 8 + }; + + // The type-erased, saved work item + struct work + { + virtual ~work() = default; + virtual void operator()() = 0; + }; + + bool busy_ = false; // true if a write is in progress + http_session& self_; + std::vector> items_; + + public: + explicit + queue(http_session& self) + : self_(self) + { + static_assert(limit > 0, "queue limit must be positive"); + items_.reserve(limit); + } + + // Returns `true` if we have reached the queue limit + bool + is_full() const + { + return items_.size() + (busy_ ? 1 : 0) >= limit; + } + + // Called when a message finishes sending + // Returns `true` if the caller should initiate a read + bool + on_write() + { + BOOST_ASSERT(busy_); + auto const do_read = items_.size() + (busy_ ? 1 : 0) >= limit; + if(! items_.empty()) + { + (*items_.back())(); + items_.erase(items_.begin()); + } + else + { + busy_ = false; + } + return do_read; + } + + // Called by the HTTP handler to send a response. + template + void + operator()(http::message&& msg) + { + // See if a write is in progress + if(! busy_) + { + // No write in progress so start one + busy_ = true; + + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + return async_write_msg( + self_.derived().stream(), + std::move(msg), + self_.strand_.wrap(std::bind( + &http_session::on_write, + self_.derived().shared_from_this(), + std::placeholders::_1))); + } + + // This holds a work item + struct work_impl : work + { + http_session& self_; + http::message msg_; + + work_impl( + http_session& self, + http::message&& msg) + : self_(self) + , msg_(std::move(msg)) + { + } + + void + operator()() + { + async_write_msg( + self_.derived().stream(), + std::move(msg_), + self_.strand_.wrap(std::bind( + &http_session::on_write, + self_.derived().shared_from_this(), + std::placeholders::_1))); + } + }; + + // A write is in progress, so allocate storage to + // save this work item so we can invoke it later. + items_.emplace_back(new work_impl(self_, std::move(msg))); + } + }; + + std::string const& doc_root_; + http::request req_; + queue queue_; + +protected: + boost::asio::steady_timer timer_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + +public: + // Construct the session + http_session( + boost::asio::io_service& ios, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : doc_root_(doc_root) + , queue_(*this) + , timer_(ios, + (std::chrono::steady_clock::time_point::max)()) + , strand_(ios) + , buffer_(std::move(buffer)) + { + } + + void + do_read() + { + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read a request + http::async_read( + derived().stream(), + buffer_, + req_, + strand_.wrap(std::bind( + &http_session::on_read, + derived().shared_from_this(), + std::placeholders::_1))); + } + + // Called when the timer expires. + void + on_timer(boost::system::error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + return fail(ec, "timer"); + + // Verify that the timer really expired since the deadline may have moved. + if(timer_.expires_at() <= std::chrono::steady_clock::now()) + return derived().do_timeout(); + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &http_session::on_timer, + derived().shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + // This means they closed the connection + if(ec == http::error::end_of_stream) + return derived().do_eof(); + + if(ec) + return fail(ec, "read"); + + // See if it is a WebSocket Upgrade + if(websocket::is_upgrade(req_)) + { + // Transfer the stream to a new WebSocket session + return make_websocket_session( + derived().release_stream(), + std::move(req_)); + } + + // Send the response + handle_request(doc_root_, std::move(req_), queue_); + + // If we aren't at the queue limit, try to pipeline another request + if(! queue_.is_full()) + do_read(); + } + + void + on_write(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return derived().do_eof(); + } + + if(ec) + return fail(ec, "write"); + + // Inform the queue that a write completed + if(queue_.on_write()) + { + // Read another request + do_read(); + } + } +}; + +// Handles a plain HTTP connection +class plain_http_session + : public http_session + , public std::enable_shared_from_this +{ + tcp::socket socket_; + boost::asio::io_service::strand strand_; + +public: + // Create the http_session + plain_http_session( + tcp::socket socket, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : http_session( + socket.get_io_service(), + std::move(buffer), + doc_root) + , socket_(std::move(socket)) + , strand_(socket_.get_io_service()) + { + } + + // Called by the base class + tcp::socket& + stream() + { + return socket_; + } + + // Called by the base class + tcp::socket + release_stream() + { + return std::move(socket_); + } + + // Start the asynchronous operation + void + run() + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + do_read(); + } + + void + do_eof() + { + // Send a TCP shutdown + boost::system::error_code ec; + socket_.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } + + void + do_timeout() + { + // Closing the socket cancels all outstanding operations. They + // will complete with boost::asio::error::operation_aborted + boost::system::error_code ec; + socket_.shutdown(tcp::socket::shutdown_both, ec); + socket_.close(ec); + } +}; + +// Handles an SSL HTTP connection +class ssl_http_session + : public http_session + , public std::enable_shared_from_this +{ + ssl_stream stream_; + boost::asio::io_service::strand strand_; + bool eof_ = false; + +public: + // Create the http_session + ssl_http_session( + tcp::socket socket, + ssl::context& ctx, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : http_session( + socket.get_io_service(), + std::move(buffer), + doc_root) + , stream_(std::move(socket), ctx) + , strand_(stream_.get_io_service()) + { + } + + // Called by the base class + ssl_stream& + stream() + { + return stream_; + } + + // Called by the base class + ssl_stream + release_stream() + { + return std::move(stream_); + } + + // Start the asynchronous operation + void + run() + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Perform the SSL handshake + // Note, this is the buffered version of the handshake. + stream_.async_handshake( + ssl::stream_base::server, + buffer_.data(), + strand_.wrap(std::bind( + &ssl_http_session::on_handshake, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + void + on_handshake( + boost::system::error_code ec, + std::size_t bytes_used) + { + // Happens when the handshake times out + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "handshake"); + + // Consume the portion of the buffer used by the handshake + buffer_.consume(bytes_used); + + do_read(); + } + + void + do_eof() + { + eof_ = true; + + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Perform the SSL shutdown + stream_.async_shutdown( + strand_.wrap(std::bind( + &ssl_http_session::on_shutdown, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_shutdown(boost::system::error_code ec) + { + // Happens when the shutdown times out + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } + + void + do_timeout() + { + // If this is true it means we timed out performing the shutdown + if(eof_) + return; + + // Start the timer again + timer_.expires_at( + (std::chrono::steady_clock::time_point::max)()); + on_timer({}); + do_eof(); + } +}; + +//------------------------------------------------------------------------------ + +// Detects SSL handshakes +class detect_session : public std::enable_shared_from_this +{ + tcp::socket socket_; + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + std::string const& doc_root_; + boost::beast::flat_buffer buffer_; + +public: + explicit + detect_session( + tcp::socket socket, + ssl::context& ctx, + std::string const& doc_root) + : socket_(std::move(socket)) + , ctx_(ctx) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + { + } + + // Launch the detector + void + run() + { + async_detect_ssl( + socket_, + buffer_, + strand_.wrap(std::bind( + &detect_session::on_detect, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + + } + + void + on_detect(boost::system::error_code ec, boost::tribool result) + { + if(ec) + return fail(ec, "detect"); + + if(result) + { + // Launch SSL session + std::make_shared( + std::move(socket_), + ctx_, + std::move(buffer_), + doc_root_)->run(); + return; + } + + // Launch plain session + std::make_shared( + std::move(socket_), + std::move(buffer_), + doc_root_)->run(); + } +}; + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + std::string const& doc_root) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the detector http_session and run it + std::make_shared( + std::move(socket_), + ctx_, + doc_root_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: advanced-server-flex
\n" << + "Example:\n" << + " advanced-server-flex 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared( + ios, + ctx, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-server-threaded/CMakeLists.txt b/example/advanced/server/CMakeLists.txt similarity index 72% rename from example/http-server-threaded/CMakeLists.txt rename to example/advanced/server/CMakeLists.txt index 0055c40d..315d6d5e 100644 --- a/example/http-server-threaded/CMakeLists.txt +++ b/example/advanced/server/CMakeLists.txt @@ -9,11 +9,11 @@ GroupSources(include/boost/beast beast) GroupSources(example/common common) -GroupSources(example/http-server-threaded "/") +GroupSources(example/advanced/server "/") -add_executable (http-server-threaded +add_executable (advanced-server ${BOOST_BEAST_INCLUDES} - ${COMMON_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp Jamfile - http_server_threaded.cpp + advanced_server.cpp ) diff --git a/example/server-framework/Jamfile b/example/advanced/server/Jamfile similarity index 88% rename from example/server-framework/Jamfile rename to example/advanced/server/Jamfile index d90044cd..5f9ceecd 100644 --- a/example/server-framework/Jamfile +++ b/example/advanced/server/Jamfile @@ -7,8 +7,8 @@ # Official repository: https://github.com/boostorg/beast # -exe server-framework : - main.cpp +exe advanced-server : + advanced_server.cpp : coverage:no ubasan:no diff --git a/example/advanced/server/advanced_server.cpp b/example/advanced/server/advanced_server.cpp new file mode 100644 index 00000000..fd199d85 --- /dev/null +++ b/example/advanced/server/advanced_server.cpp @@ -0,0 +1,732 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: Advanced server +// +//------------------------------------------------------------------------------ + +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from +namespace websocket = boost::beast::websocket; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class websocket_session : public std::enable_shared_from_this +{ + websocket::stream ws_; + boost::asio::io_service::strand strand_; + boost::asio::steady_timer timer_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + explicit + websocket_session(tcp::socket socket) + : ws_(std::move(socket)) + , strand_(ws_.get_io_service()) + , timer_(ws_.get_io_service(), + (std::chrono::steady_clock::time_point::max)()) + { + } + + // Start the asynchronous operation + template + void + run(http::request> req) + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Accept the websocket handshake + ws_.async_accept( + req, + strand_.wrap(std::bind( + &websocket_session::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + // Called when the timer expires. + void + on_timer(boost::system::error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + return fail(ec, "timer"); + + // Verify that the timer really expired since the deadline may have moved. + if(timer_.expires_at() <= std::chrono::steady_clock::now()) + { + // Closing the socket cancels all outstanding operations. They + // will complete with boost::asio::error::operation_aborted + ws_.next_layer().shutdown(tcp::socket::shutdown_both, ec); + ws_.next_layer().close(ec); + return; + } + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &websocket_session::on_timer, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read a message into our buffer + ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &websocket_session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + // This indicates that the websocket_session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &websocket_session::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +// Handles an HTTP server connection +class http_session : public std::enable_shared_from_this +{ + // This queue is used for HTTP pipelining. + class queue + { + enum + { + // Maximum number of responses we will queue + limit = 8 + }; + + // The type-erased, saved work item + struct work + { + virtual ~work() = default; + virtual void operator()() = 0; + }; + + bool busy_ = false; // true if a write is in progress + http_session& self_; + std::vector> items_; + + public: + explicit + queue(http_session& self) + : self_(self) + { + static_assert(limit > 0, "queue limit must be positive"); + items_.reserve(limit); + } + + // Returns `true` if we have reached the queue limit + bool + is_full() const + { + return items_.size() + (busy_ ? 1 : 0) >= limit; + } + + // Called when a message finishes sending + // Returns `true` if the caller should initiate a read + bool + on_write() + { + BOOST_ASSERT(busy_); + auto const do_read = items_.size() + (busy_ ? 1 : 0) >= limit; + if(! items_.empty()) + { + (*items_.back())(); + items_.erase(items_.begin()); + } + else + { + busy_ = false; + } + return do_read; + } + + // Called by the HTTP handler to send a response. + template + void + operator()(http::message&& msg) + { + // See if a write is in progress + if(! busy_) + { + // No write in progress so start one + busy_ = true; + + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + return async_write_msg( + self_.socket_, + std::move(msg), + self_.strand_.wrap(std::bind( + &http_session::on_write, + self_.shared_from_this(), + std::placeholders::_1))); + } + + // This holds a work item + struct work_impl : work + { + http_session& self_; + http::message msg_; + + work_impl( + http_session& self, + http::message&& msg) + : self_(self) + , msg_(std::move(msg)) + { + } + + void + operator()() + { + async_write_msg( + self_.socket_, + std::move(msg_), + self_.strand_.wrap(std::bind( + &http_session::on_write, + self_.shared_from_this(), + std::placeholders::_1))); + } + }; + + // A write is in progress, so allocate storage to + // save this work item so we can invoke it later. + items_.emplace_back(new work_impl(self_, std::move(msg))); + } + }; + + tcp::socket socket_; + boost::asio::io_service::strand strand_; + boost::asio::steady_timer timer_; + boost::beast::flat_buffer buffer_; + std::string const& doc_root_; + http::request req_; + queue queue_; + +public: + // Take ownership of the socket + explicit + http_session( + tcp::socket socket, + std::string const& doc_root) + : socket_(std::move(socket)) + , strand_(socket_.get_io_service()) + , timer_(socket_.get_io_service(), + (std::chrono::steady_clock::time_point::max)()) + , doc_root_(doc_root) + , queue_(*this) + { + } + + // Start the asynchronous operation + void + run() + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + do_read(); + } + + void + do_read() + { + // Set the timer + timer_.expires_from_now(std::chrono::seconds(15)); + + // Read a request + http::async_read(socket_, buffer_, req_, + strand_.wrap(std::bind( + &http_session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + // Called when the timer expires. + void + on_timer(boost::system::error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + return fail(ec, "timer"); + + // Verify that the timer really expired since the deadline may have moved. + if(timer_.expires_at() <= std::chrono::steady_clock::now()) + { + // Closing the socket cancels all outstanding operations. They + // will complete with boost::asio::error::operation_aborted + socket_.shutdown(tcp::socket::shutdown_both, ec); + socket_.close(ec); + return; + } + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &http_session::on_timer, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + // This means they closed the connection + if(ec == http::error::end_of_stream) + return do_close(); + + if(ec) + return fail(ec, "read"); + + // See if it is a WebSocket Upgrade + if(websocket::is_upgrade(req_)) + { + // Create a WebSocket websocket_session by transferring the socket + std::make_shared( + std::move(socket_))->run(std::move(req_)); + return; + } + + // Send the response + handle_request(doc_root_, std::move(req_), queue_); + + // If we aren't at the queue limit, try to pipeline another request + if(! queue_.is_full()) + do_read(); + } + + void + on_write(boost::system::error_code ec) + { + // Happens when the timer closes the socket + if(ec == boost::asio::error::operation_aborted) + return; + + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return do_close(); + } + + if(ec) + return fail(ec, "write"); + + // Inform the queue that a write completed + if(queue_.on_write()) + { + // Read another request + do_read(); + } + } + + void + do_close() + { + // Send a TCP shutdown + boost::system::error_code ec; + socket_.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + std::string const& doc_root) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the http_session and run it + std::make_shared( + std::move(socket_), + doc_root_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: advanced-server
\n" << + "Example:\n" << + " advanced-server 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create and launch a listening port + std::make_shared( + ios, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/common/detect_ssl.hpp b/example/common/detect_ssl.hpp index 00693d13..9018ebdc 100644 --- a/example/common/detect_ssl.hpp +++ b/example/common/detect_ssl.hpp @@ -21,7 +21,7 @@ //[example_core_detect_ssl_1 -#include +#include #include /** Return `true` if a buffer contains a TLS/SSL client handshake. @@ -52,8 +52,6 @@ is_ssl_handshake(ConstBufferSequence const& buffers); //] -using namespace boost::beast; - //[example_core_detect_ssl_2 template< @@ -63,7 +61,7 @@ is_ssl_handshake( ConstBufferSequence const& buffers) { // Make sure buffers meets the requirements - static_assert(is_const_buffer_sequence::value, + static_assert(boost::beast::is_const_buffer_sequence::value, "ConstBufferSequence requirements not met"); // We need at least one byte to really do anything @@ -130,12 +128,14 @@ boost::tribool detect_ssl( SyncReadStream& stream, DynamicBuffer& buffer, - error_code& ec) + boost::beast::error_code& ec) { + namespace beast = boost::beast; + // Make sure arguments meet the requirements - static_assert(is_sync_read_stream::value, + static_assert(beast::is_sync_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_dynamic_buffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); // Loop until an error occurs or we get a definitive answer @@ -148,15 +148,17 @@ detect_ssl( // If we got an answer, return it if(! boost::indeterminate(result)) { - ec = {}; // indicate no error + // This is a fast way to indicate success + // without retrieving the default category. + ec.assign(0, ec.category()); return result; } // The algorithm should never need more than 4 bytes BOOST_ASSERT(buffer.size() < 4); - // Create up to 4 bytes of space in the buffer's output area. - auto const mutable_buffer = buffer.prepare(4 - buffer.size()); + // Prepare the buffer's output area. + auto const mutable_buffer = buffer.prepare(beast::read_size(buffer, 1536)); // Try to fill our buffer by reading from the stream std::size_t const bytes_transferred = stream.read_some(mutable_buffer, ec); @@ -223,9 +225,9 @@ template< class AsyncReadStream, class DynamicBuffer, class CompletionToken> -async_return_type< /*< The [link beast.ref.boost__beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/ +boost::beast::async_return_type< /*< The [link beast.ref.boost__beast__async_return_type `async_return_type`] customizes the return value based on the completion token >*/ CompletionToken, - void(error_code, boost::tribool)> /*< This is the signature for the completion handler >*/ + void(boost::beast::error_code, boost::tribool)> /*< This is the signature for the completion handler >*/ async_detect_ssl( AsyncReadStream& stream, DynamicBuffer& buffer, @@ -247,26 +249,28 @@ template< class AsyncReadStream, class DynamicBuffer, class CompletionToken> -async_return_type< +boost::beast::async_return_type< CompletionToken, - void(error_code, boost::tribool)> + void(boost::beast::error_code, boost::tribool)> async_detect_ssl( AsyncReadStream& stream, DynamicBuffer& buffer, CompletionToken&& token) { + namespace beast = boost::beast; + // Make sure arguments meet the requirements - static_assert(is_async_read_stream::value, + static_assert(beast::is_async_read_stream::value, "SyncReadStream requirements not met"); - static_assert(is_dynamic_buffer::value, + static_assert(beast::is_dynamic_buffer::value, "DynamicBuffer requirements not met"); // This helper manages some of the handler's lifetime and // uses the result and handler specializations associated with // the completion token to help customize the return value. // - boost::beast::async_completion< - CompletionToken, void(boost::beast::error_code, boost::tribool)> init{token}; + beast::async_completion< + CompletionToken, void(beast::error_code, boost::tribool)> init{token}; // Create the composed operation and launch it. This is a constructor // call followed by invocation of operator(). We use handler_type @@ -274,10 +278,10 @@ async_detect_ssl( // allowing user defined specializations of the async result template // to take effect. // - detect_ssl_op>{ + detect_ssl_op>{ stream, buffer, init.completion_handler}( - boost::beast::error_code{}, 0); + beast::error_code{}, 0); // This hook lets the caller see a return value when appropriate. // For example this might return std::future if @@ -322,8 +326,10 @@ public: // The constructor just keeps references the callers varaibles. // template - detect_ssl_op(AsyncReadStream& stream, - DynamicBuffer& buffer, DeducedHandler&& handler) + detect_ssl_op( + AsyncReadStream& stream, + DynamicBuffer& buffer, + DeducedHandler&& handler) : stream_(stream) , buffer_(buffer) , handler_(std::forward(handler)) @@ -402,6 +408,8 @@ void detect_ssl_op:: operator()(boost::beast::error_code ec, std::size_t bytes_transferred) { + namespace beast = boost::beast; + // Execute the state machine switch(step_) { @@ -422,7 +430,7 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred) // original handler. step_ = 1; return stream_.get_io_service().post( - bind_handler(std::move(*this), ec, 0)); + beast::bind_handler(std::move(*this), ec, 0)); } // The algorithm should never need more than 4 bytes @@ -432,7 +440,7 @@ operator()(boost::beast::error_code ec, std::size_t bytes_transferred) do_read: // We need more bytes, but no more than four total. - return stream_.async_read_some(buffer_.prepare(4 - buffer_.size()), std::move(*this)); + return stream_.async_read_some(buffer_.prepare(beast::read_size(buffer_, 1536)), std::move(*this)); case 1: // Call the handler diff --git a/example/common/helpers.hpp b/example/common/helpers.hpp deleted file mode 100644 index df2b954d..00000000 --- a/example/common/helpers.hpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_COMMON_HELPERS_HPP -#define BOOST_BEAST_EXAMPLE_COMMON_HELPERS_HPP - -#include -#include -#include -#include - -/// Block until SIGINT or SIGTERM is received. -inline -void -sig_wait() -{ - boost::asio::io_service ios{1}; - boost::asio::signal_set signals(ios, SIGINT, SIGTERM); - signals.async_wait([&](boost::system::error_code const&, int){}); - ios.run(); -} - -namespace detail { - -inline -void -print_1(std::ostream&) -{ -} - -template -void -print_1(std::ostream& os, T1 const& t1, TN const&... tn) -{ - os << t1; - print_1(os, tn...); -} - -} // detail - -// compose a string to std::cout or std::cerr atomically -// -template -void -print(std::ostream& os, Args const&... args) -{ - std::stringstream ss; - detail::print_1(ss, args...); - os << ss.str() << std::endl; -} - -#endif diff --git a/example/common/mime_types.hpp b/example/common/mime_types.hpp deleted file mode 100644 index 8bb654c9..00000000 --- a/example/common/mime_types.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP -#define BOOST_BEAST_EXAMPLE_COMMON_MIME_TYPES_HPP - -#include -#include - -// Return a reasonable mime type based on the extension of a file. -// -template -boost::beast::string_view -mime_type(boost::filesystem::path const& path) -{ - using boost::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"; -} - -#endif diff --git a/example/common/rfc7231.hpp b/example/common/rfc7231.hpp deleted file mode 100644 index 50726079..00000000 --- a/example/common/rfc7231.hpp +++ /dev/null @@ -1,41 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_COMMON_RFC7231_HPP -#define BOOST_BEAST_EXAMPLE_COMMON_RFC7231_HPP - -#include -#include - -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(boost::beast::http::request< - Body, boost::beast::http::basic_fields> const& req) -{ - return boost::beast::iequals( - req[boost::beast::http::field::expect], "100-continue"); -} - -} // rfc7231 - -#endif diff --git a/example/common/root_certificates.hpp b/example/common/root_certificates.hpp index 706ca701..a5a4dbb0 100644 --- a/example/common/root_certificates.hpp +++ b/example/common/root_certificates.hpp @@ -110,6 +110,7 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) // gratuituous template argument; thus it appears // like a "normal" function. // + inline void load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) @@ -117,4 +118,14 @@ load_root_certificates(ssl::context& ctx, boost::system::error_code& ec) detail::load_root_certificates(ctx, ec); } +inline +void +load_root_certificates(ssl::context& ctx) +{ + boost::system::error_code ec; + detail::load_root_certificates(ctx, ec); + if(ec) + throw boost::system::system_error{ec}; +} + #endif diff --git a/example/server-framework/ssl_certificate.hpp b/example/common/server_certificate.hpp similarity index 86% rename from example/server-framework/ssl_certificate.hpp rename to example/common/server_certificate.hpp index 6bddcb47..fa0926bc 100644 --- a/example/server-framework/ssl_certificate.hpp +++ b/example/common/server_certificate.hpp @@ -7,48 +7,26 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_SSL_CERTIFICATE_HPP +#ifndef BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP +#define BOOST_BEAST_EXAMPLE_COMMON_SERVER_CERTIFICATE_HPP #include #include #include #include -namespace framework { +/* Load a signed certificate into the ssl context, and configure + the context for use with a server. -// This sets up the self-signed certificate that the server -// uses for its encrypted connections - -class ssl_certificate -{ - // The template argument is gratuitous, to - // make the definition header-only without - // also making it inline. - // - template - void - construct(); - - boost::asio::ssl::context ctx_; - -public: - ssl_certificate() - : ctx_(boost::asio::ssl::context::sslv23) - { - construct(); - } - - boost::asio::ssl::context& - get() - { - return ctx_; - } -}; - -template + For this to work with the browser or operating system, it is + necessary to import the "Beast Test CA" certificate into + the local certificate store, browser, or operating system + depending on your environment Please see the documentation + accompanying the Beast certificate for more details. +*/ +inline void -ssl_certificate::construct() +load_server_certificate(boost::asio::ssl::context& ctx) { /* The certificate was generated from CMD.EXE on Windows 10 using: @@ -120,29 +98,27 @@ ssl_certificate::construct() "QMUk26jPTIVTLfXmmwU0u8vUkpR7LQKkwwIBAg==\n" "-----END DH PARAMETERS-----\n"; - ctx_.set_password_callback( + ctx.set_password_callback( [](std::size_t, boost::asio::ssl::context_base::password_purpose) { return "test"; }); - ctx_.set_options( + ctx.set_options( boost::asio::ssl::context::default_workarounds | boost::asio::ssl::context::no_sslv2 | boost::asio::ssl::context::single_dh_use); - ctx_.use_certificate_chain( + ctx.use_certificate_chain( boost::asio::buffer(cert.data(), cert.size())); - ctx_.use_private_key( + ctx.use_private_key( boost::asio::buffer(key.data(), key.size()), boost::asio::ssl::context::file_format::pem); - ctx_.use_tmp_dh( + ctx.use_tmp_dh( boost::asio::buffer(dh.data(), dh.size())); } -} // framework - #endif diff --git a/example/common/ssl_stream.hpp b/example/common/ssl_stream.hpp index 2abbf06e..b4dabd2b 100644 --- a/example/common/ssl_stream.hpp +++ b/example/common/ssl_stream.hpp @@ -61,11 +61,13 @@ public: /// The type of the lowest layer. using lowest_layer_type = typename stream_type::lowest_layer_type; - ssl_stream(boost::asio::ip::tcp::socket&& sock, boost::asio::ssl::context& ctx) - : p_(new stream_type{sock.get_io_service(), ctx}) + ssl_stream( + boost::asio::ip::tcp::socket socket, + boost::asio::ssl::context& ctx) + : p_(new stream_type{socket.get_io_service(), ctx}) , ctx_(&ctx) { - p_->next_layer() = std::move(sock); + p_->next_layer() = std::move(socket); } ssl_stream(ssl_stream&& other) diff --git a/example/http-client-ssl/Jamfile b/example/http-client-ssl/Jamfile deleted file mode 100644 index 4d9f9fcc..00000000 --- a/example/http-client-ssl/Jamfile +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -import os ; - -if [ os.name ] = SOLARIS -{ - lib socket ; - lib nsl ; -} -else if [ os.name ] = NT -{ - lib ws2_32 ; - lib mswsock ; -} -else if [ os.name ] = HPUX -{ - lib ipv6 ; -} -else if [ os.name ] = HAIKU -{ - lib network ; -} - -if [ os.name ] = NT -{ - lib ssl : : ssleay32 ; - lib crypto : : libeay32 ; -} -else -{ - lib ssl ; - lib crypto ; -} - -project - : requirements - ssl - crypto - ; - -exe http-client-ssl : - http_client_ssl.cpp - : - coverage:no - ubasan:no - ; diff --git a/example/http-client-ssl/http_client_ssl.cpp b/example/http-client-ssl/http_client_ssl.cpp deleted file mode 100644 index 05be692a..00000000 --- a/example/http-client-ssl/http_client_ssl.cpp +++ /dev/null @@ -1,106 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "../common/root_certificates.hpp" - -#include -#include -#include -#include -#include -#include -#include - -using tcp = boost::asio::ip::tcp; // from -namespace ssl = boost::asio::ssl; // from -namespace http = boost::beast::http; // from - -int main() -{ - // A helper for reporting errors - auto const fail = - [](std::string what, boost::beast::error_code ec) - { - std::cerr << what << ": " << ec.message() << std::endl; - std::cerr.flush(); - return EXIT_FAILURE; - }; - - boost::system::error_code ec; - - // Normal boost::asio setup - boost::asio::io_service ios; - tcp::resolver r{ios}; - tcp::socket sock{ios}; - - // Look up the domain name - std::string const host = "www.example.com"; - auto const lookup = r.resolve({host, "https"}, ec); - if(ec) - return fail("resolve", ec); - - // Make the connection on the IP address we get from a lookup - boost::asio::connect(sock, lookup, ec); - if(ec) - return fail("connect", ec); - - // Create the required ssl context - ssl::context ctx{ssl::context::sslv23_client}; - - // This holds the root certificate used for verification - load_root_certificates(ctx, ec); - if(ec) - return fail("certificate", ec); - - // Wrap the now-connected socket in an SSL stream - ssl::stream stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_peer | ssl::verify_fail_if_no_peer_cert); - - // Perform SSL handshaking - stream.handshake(ssl::stream_base::client, ec); - if(ec) - return fail("handshake", ec); - - // Set up an HTTP GET request message - http::request req; - req.method(http::verb::get); - req.target("/"); - req.version = 11; - req.set(http::field::host, host + ":" + - std::to_string(sock.remote_endpoint().port())); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - req.prepare_payload(); - - // Write the HTTP request to the remote host - http::write(stream, req, ec); - if(ec) - return fail("write", ec); - - // This buffer is used for reading and must be persisted - boost::beast::flat_buffer b; - - // Declare a container to hold the response - http::response res; - - // Read the response - http::read(stream, b, res, ec); - if(ec) - return fail("read", ec); - - // Write the message to standard out - std::cout << res << std::endl; - - // Shut down SSL on the stream - stream.shutdown(ec); - if(ec && ec != boost::asio::error::eof) - fail("ssl_shutdown ", ec); - - // If we get here then the connection is closed gracefully - return EXIT_SUCCESS; -} diff --git a/example/http-client/http_client.cpp b/example/http-client/http_client.cpp deleted file mode 100644 index ff31e151..00000000 --- a/example/http-client/http_client.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast - -//[example_http_client - -#include -#include -#include -#include -#include -#include -#include - -using tcp = boost::asio::ip::tcp; // from -namespace http = boost::beast::http; // from - -int main() -{ - // A helper for reporting errors - auto const fail = - [](std::string what, boost::beast::error_code ec) - { - std::cerr << what << ": " << ec.message() << std::endl; - return EXIT_FAILURE; - }; - - boost::beast::error_code ec; - - // Set up an asio socket - boost::asio::io_service ios; - tcp::resolver r{ios}; - tcp::socket sock{ios}; - - // Look up the domain name - std::string const host = "www.example.com"; - auto const lookup = r.resolve({host, "http"}, ec); - if(ec) - return fail("resolve", ec); - - // Make the connection on the IP address we get from a lookup - boost::asio::connect(sock, lookup, ec); - if(ec) - return fail("connect", ec); - - // Set up an HTTP GET request message - http::request req{http::verb::get, "/", 11}; - req.set(http::field::host, host + ":" + - std::to_string(sock.remote_endpoint().port())); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - req.prepare_payload(); - - // Write the HTTP request to the remote host - http::write(sock, req, ec); - if(ec) - return fail("write", ec); - - // This buffer is used for reading and must be persisted - boost::beast::flat_buffer b; - - // Declare a container to hold the response - http::response res; - - // Read the response - http::read(sock, b, res, ec); - if(ec) - return fail("read", ec); - - // Write the message to standard out - std::cout << res << std::endl; - - // Gracefully close the socket - sock.shutdown(tcp::socket::shutdown_both, ec); - - // not_connected happens sometimes - // so don't bother reporting it. - // - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - - // If we get here then the connection is closed gracefully - return EXIT_SUCCESS; -} - -//] diff --git a/example/http-crawl/http_crawl.cpp b/example/http-crawl/http_crawl.cpp deleted file mode 100644 index 7883b566..00000000 --- a/example/http-crawl/http_crawl.cpp +++ /dev/null @@ -1,139 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "urls_large_data.hpp" - -#include -#include -#include -#include -#include -#include - -using tcp = boost::asio::ip::tcp; // from -namespace http = boost::beast::http; // from - -template -void -err(boost::beast::error_code const& ec, String const& what) -{ - std::cerr << what << ": " << ec.message() << std::endl; -} - -/* This simple program just visits a list with a few - thousand domain names and tries to retrieve and print - the home page of each site. -*/ -int -main(int, char const*[]) -{ - // A helper for reporting errors - auto const fail = - [](std::string what, boost::beast::error_code ec) - { - std::cerr << what << ": " << ec.message() << std::endl; - std::cerr.flush(); - return EXIT_FAILURE; - }; - - // Obligatory Asio variable - boost::asio::io_service ios; - - // Loop over all the URLs - for(auto const& host : urls_large_data()) - { - boost::beast::error_code ec; - - // Look up the domain name - tcp::resolver r(ios); - auto lookup = r.resolve({host, "http"}, ec); - if(ec) - { - fail("resolve", ec); - continue; - } - - // Now create a socket and connect - tcp::socket sock(ios); - boost::asio::connect(sock, lookup, ec); - if(ec) - { - fail("connect", ec); - continue; - } - - // Grab the remote endpoint - auto ep = sock.remote_endpoint(ec); - if(ec) - { - fail("remote_endpoint", ec); - continue; - } - - // Set up an HTTP GET request - http::request req{http::verb::get, "/", 11}; - req.set(http::field::host, host + std::string(":") + std::to_string(ep.port())); - req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); - - // Set the Connection: close field, this way the server will close - // the connection. This consumes less resources (no TIME_WAIT) because - // of the graceful close. It also makes things go a little faster. - // - req.set(http::field::connection, "close"); - - // Send the GET request - http::write(sock, req, ec); - if(ec == http::error::end_of_stream) - { - // This special error received on a write indicates that the - // semantics of the sent message are such that the connection - // should be closed after the response is done. We do a TCP/IP - // "half-close" here to shut down our end. - // - sock.shutdown(tcp::socket::shutdown_send, ec); - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - } - if(ec) - { - fail("write", ec); - continue; - } - - // This buffer is needed for reading - boost::beast::multi_buffer b; - - // The response will go into this object - http::response res; - - // Read the response - http::read(sock, b, res, ec); - if(ec == http::error::end_of_stream) - { - // This special error means that the other end closed the socket, - // which is what we want since we asked for Connection: close. - // However, we are going through a rather large number of servers - // and sometimes they misbehave. - ec = {}; - } - else if(ec) - { - fail("read", ec); - continue; - } - - // Now we do the other half of the close, - // which is to shut down the receiver. - sock.shutdown(tcp::socket::shutdown_receive, ec); - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - - std::cout << res << std::endl; - } -} diff --git a/example/http-server-threaded/http_server_threaded.cpp b/example/http-server-threaded/http_server_threaded.cpp deleted file mode 100644 index c22deab4..00000000 --- a/example/http-server-threaded/http_server_threaded.cpp +++ /dev/null @@ -1,229 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "../common/mime_types.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -//------------------------------------------------------------------------------ -// -// Example: HTTP server, synchronous, one thread per connection -// -//------------------------------------------------------------------------------ - -namespace ip = boost::asio::ip; // from -using tcp = boost::asio::ip::tcp; // from -namespace http = boost::beast::http; // from - -class connection - : public std::enable_shared_from_this -{ - tcp::socket sock_; - boost::beast::string_view root_; - -public: - explicit - connection(tcp::socket&& sock, boost::beast::string_view root) - : sock_(std::move(sock)) - , root_(root) - { - } - - void - run() - { - // Bind a shared_ptr to *this into the thread. - // When the thread exits, the connection object - // will be destroyed. - // - std::thread{&connection::do_run, shared_from_this()}.detach(); - } - -private: - // Send a client error response - http::response> - client_error(http::status result, boost::beast::string_view text) - { - http::response> res{result, 11}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/plain"); - res.set(http::field::connection, "close"); - res.body = text; - res.prepare_payload(); - return res; - } - - // Return an HTTP Not Found response - // - http::response - not_found() const - { - http::response res{http::status::not_found, 11}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.set(http::field::connection, "close"); - res.body = "The file was not found"; - res.prepare_payload(); - return res; - } - - // Return an HTTP Server Error - // - http::response - server_error(boost::beast::error_code const& ec) const - { - http::response res{http::status::internal_server_error, 11}; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, "text/html"); - res.set(http::field::connection, "close"); - res.body = "Error: " + ec.message(); - res.prepare_payload(); - return res; - } - - // Return a file response to an HTTP GET request - // - http::response - get(boost::filesystem::path const& full_path, - boost::beast::error_code& ec) const - { - http::response res; - res.set(http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(http::field::content_type, mime_type(full_path)); - res.set(http::field::connection, "close"); - res.body.open(full_path.string().c_str(), boost::beast::file_mode::scan, ec); - if(ec) - return res; - res.set(http::field::content_length, res.body.size()); - return res; - } - - // Handle a request - template - void - do_request(http::request const& req, boost::beast::error_code& ec) - { - // verb must be get - if(req.method() != http::verb::get) - { - http::write(sock_, client_error(http::status::bad_request, "Unsupported method"), ec); - return; - } - - // Request path must be absolute and not contain "..". - if( req.target().empty() || - req.target()[0] != '/' || - req.target().find("..") != std::string::npos) - { - http::write(sock_, client_error(http::status::not_found, "File not found"), ec); - return; - } - - auto full_path = root_.to_string(); - full_path.append(req.target().data(), req.target().size()); - - boost::beast::error_code file_ec; - auto res = get(full_path, file_ec); - - if(file_ec == boost::beast::errc::no_such_file_or_directory) - { - http::write(sock_, not_found(), ec); - } - else if(ec) - { - http::write(sock_, server_error(file_ec), ec); - } - else - { - http::serializer sr{res}; - http::write(sock_, sr, ec); - } - } - - void - do_run() - { - try - { - boost::beast::error_code ec; - boost::beast::flat_buffer buffer; - for(;;) - { - http::request_parser parser; - parser.header_limit(8192); - parser.body_limit(1024 * 1024); - http::read(sock_, buffer, parser, ec); - if(ec == http::error::end_of_stream) - break; - if(ec) - throw boost::beast::system_error{ec}; - do_request(parser.get(), ec); - if(ec) - { - if(ec != http::error::end_of_stream) - throw boost::beast::system_error{ec}; - break; - } - } - sock_.shutdown(tcp::socket::shutdown_both, ec); - if(ec && ec != boost::asio::error::not_connected) - throw boost::beast::system_error{ec}; - } - catch (const std::exception& e) - { - std::cerr << "Exception: " << e.what() << std::endl; - } - } -}; - -int main(int argc, char* argv[]) -{ - try - { - // Check command line arguments. - if (argc != 4) - { - std::cerr << "Usage: http_server
\n"; - std::cerr << " For IPv4, try:\n"; - std::cerr << " receiver 0.0.0.0 80 .\n"; - std::cerr << " For IPv6, try:\n"; - std::cerr << " receiver 0::0 80 .\n"; - return EXIT_FAILURE; - } - - auto address = ip::address::from_string(argv[1]); - unsigned short port = static_cast(std::atoi(argv[2])); - std::string doc_root = argv[3]; - - boost::asio::io_service ios{1}; - tcp::acceptor acceptor{ios, {address, port}}; - for(;;) - { - tcp::socket sock{ios}; - acceptor.accept(sock); - std::make_shared(std::move(sock), doc_root)->run(); - } - } - catch (const std::exception& e) - { - std::cerr << "Exception: " << e.what() << std::endl; - return EXIT_FAILURE; - } -} diff --git a/example/http/CMakeLists.txt b/example/http/CMakeLists.txt new file mode 100644 index 00000000..d38604dd --- /dev/null +++ b/example/http/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (client) +add_subdirectory (server) diff --git a/example/http/Jamfile b/example/http/Jamfile new file mode 100644 index 00000000..32f2c514 --- /dev/null +++ b/example/http/Jamfile @@ -0,0 +1,11 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project client ; +build-project server ; diff --git a/example/http/client/CMakeLists.txt b/example/http/client/CMakeLists.txt new file mode 100644 index 00000000..a8c09df0 --- /dev/null +++ b/example/http/client/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (async) +add_subdirectory (coro) +add_subdirectory (crawl) +add_subdirectory (sync) + +if (OPENSSL_FOUND) + add_subdirectory (async-ssl) + add_subdirectory (coro-ssl) + add_subdirectory (sync-ssl) +endif() diff --git a/example/http/client/Jamfile b/example/http/client/Jamfile new file mode 100644 index 00000000..c5d71aab --- /dev/null +++ b/example/http/client/Jamfile @@ -0,0 +1,18 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project async ; +build-project coro ; +build-project crawl ; +build-project sync ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project async-ssl ; +#build-project coro-ssl ; +#build-project sync-ssl ; diff --git a/example/http/client/async-ssl/CMakeLists.txt b/example/http/client/async-ssl/CMakeLists.txt new file mode 100644 index 00000000..c4bfec12 --- /dev/null +++ b/example/http/client/async-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/client/async-ssl "/") + +add_executable (http-client-async-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + http_client_async_ssl.cpp +) + +target_link_libraries (http-client-async-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/client/async-ssl/Jamfile b/example/http/client/async-ssl/Jamfile new file mode 100644 index 00000000..ed5596af --- /dev/null +++ b/example/http/client/async-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-client-async-ssl : + http_client_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/client/async-ssl/http_client_async_ssl.cpp b/example/http/client/async-ssl/http_client_async_ssl.cpp new file mode 100644 index 00000000..5c39086f --- /dev/null +++ b/example/http/client/async-ssl/http_client_async_ssl.cpp @@ -0,0 +1,212 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL client, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Performs an HTTP GET and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + ssl::stream stream_; + boost::beast::flat_buffer buffer_; // (Must persist between reads) + http::request req_; + http::response res_; + +public: + // Resolver and stream require an io_service + explicit + session(boost::asio::io_service& ios, ssl::context& ctx) + : resolver_(ios) + , stream_(ios, ctx) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* target) + { + // Set up an HTTP GET request message + req_.version = 11; + req_.method(http::verb::get); + req_.target(target); + req_.set(http::field::host, host); + req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Look up the domain name + resolver_.async_resolve({host, port}, + std::bind( + &session::on_resolve, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_resolve( + boost::system::error_code ec, + tcp::resolver::iterator result) + { + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect( + stream_.next_layer(), + result, + std::bind( + &session::on_connect, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_connect(boost::system::error_code ec) + { + if(ec) + return fail(ec, "connect"); + + // Perform the SSL handshake + stream_.async_handshake( + ssl::stream_base::client, + std::bind( + &session::on_handshake, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Send the HTTP request to the remote host + http::async_write(stream_, req_, + std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Receive the HTTP response + http::async_read(stream_, buffer_, res_, + std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_read(boost::system::error_code ec) + { + if(ec) + return fail(ec, "read"); + + // Write the message to standard out + std::cout << res_ << std::endl; + + // Gracefully close the stream + stream_.async_shutdown( + std::bind( + &session::on_shutdown, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_shutdown(boost::system::error_code ec) + { + if(ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } + if(ec) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-async-ssl \n" << + "Example:\n" << + " http-client-async-ssl www.example.com 443 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // Launch the asynchronous operation + std::make_shared(ios, ctx)->run(host, port, target); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/client/async/CMakeLists.txt b/example/http/client/async/CMakeLists.txt new file mode 100644 index 00000000..ec0935ec --- /dev/null +++ b/example/http/client/async/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/http/client/async "/") + +add_executable (http-client-async + ${BOOST_BEAST_INCLUDES} + Jamfile + http_client_async.cpp +) diff --git a/example/http-server-threaded/Jamfile b/example/http/client/async/Jamfile similarity index 86% rename from example/http-server-threaded/Jamfile rename to example/http/client/async/Jamfile index b11e5a41..abc9d4d6 100644 --- a/example/http-server-threaded/Jamfile +++ b/example/http/client/async/Jamfile @@ -7,8 +7,8 @@ # Official repository: https://github.com/boostorg/beast # -exe http-server-threaded : - http_server_threaded.cpp +exe http-client-async : + http_client_async.cpp : coverage:no ubasan:no diff --git a/example/http/client/async/http_client_async.cpp b/example/http/client/async/http_client_async.cpp new file mode 100644 index 00000000..1e191002 --- /dev/null +++ b/example/http/client/async/http_client_async.cpp @@ -0,0 +1,172 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP client, asynchronous +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Performs an HTTP GET and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + tcp::socket socket_; + boost::beast::flat_buffer buffer_; // (Must persist between reads) + http::request req_; + http::response res_; + +public: + // Resolver and socket require an io_service + explicit + session(boost::asio::io_service& ios) + : resolver_(ios) + , socket_(ios) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* target) + { + // Set up an HTTP GET request message + req_.version = 11; + req_.method(http::verb::get); + req_.target(target); + req_.set(http::field::host, host); + req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Look up the domain name + resolver_.async_resolve({host, port}, + std::bind( + &session::on_resolve, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_resolve( + boost::system::error_code ec, + tcp::resolver::iterator result) + { + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect(socket_, result, + std::bind( + &session::on_connect, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_connect(boost::system::error_code ec) + { + if(ec) + return fail(ec, "connect"); + + // Send the HTTP request to the remote host + http::async_write(socket_, req_, + std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Receive the HTTP response + http::async_read(socket_, buffer_, res_, + std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_read(boost::system::error_code ec) + { + if(ec) + return fail(ec, "read"); + + // Write the message to standard out + std::cout << res_ << std::endl; + + // Gracefully close the socket + socket_.shutdown(tcp::socket::shutdown_both, ec); + + // not_connected happens sometimes so don't bother reporting it. + if(ec && ec != boost::system::errc::not_connected) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-async \n" << + "Example:\n" << + " http-client-async www.example.com 80 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // Launch the asynchronous operation + std::make_shared(ios)->run(host, port, target); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/client/coro-ssl/CMakeLists.txt b/example/http/client/coro-ssl/CMakeLists.txt new file mode 100644 index 00000000..2a6b5564 --- /dev/null +++ b/example/http/client/coro-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/client/coro-ssl "/") + +add_executable (http-client-coro-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + http_client_coro_ssl.cpp +) + +target_link_libraries (http-client-coro-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/client/coro-ssl/Jamfile b/example/http/client/coro-ssl/Jamfile new file mode 100644 index 00000000..8ed87bf1 --- /dev/null +++ b/example/http/client/coro-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-client-coro-ssl : + http_client_coro_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/client/coro-ssl/http_client_coro_ssl.cpp b/example/http/client/coro-ssl/http_client_coro_ssl.cpp new file mode 100644 index 00000000..91526163 --- /dev/null +++ b/example/http/client/coro-ssl/http_client_coro_ssl.cpp @@ -0,0 +1,153 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL client, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Performs an HTTP GET and prints the response +void +do_session( + std::string const& host, + std::string const& port, + std::string const& target, + boost::asio::io_service& ios, + ssl::context& ctx, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + ssl::stream stream{ios, ctx}; + + // Look up the domain name + auto const lookup = resolver.async_resolve({host, port}, yield[ec]); + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect(stream.next_layer(), lookup, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // Perform the SSL handshake + stream.async_handshake(ssl::stream_base::client, yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Set up an HTTP GET request message + http::request req{http::verb::get, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Send the HTTP request to the remote host + http::async_write(stream, req, yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer b; + + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::async_read(stream, b, res, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the stream + stream.async_shutdown(yield[ec]); + if(ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } + if(ec) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-coro-ssl \n" << + "Example:\n" << + " http-client-coro-ssl www.example.com 443 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // Launch the asynchronous operation + boost::asio::spawn(ios, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(target), + std::ref(ios), + std::ref(ctx), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-client/CMakeLists.txt b/example/http/client/coro/CMakeLists.txt similarity index 78% rename from example/http-client/CMakeLists.txt rename to example/http/client/coro/CMakeLists.txt index 8a0f9357..af03491c 100644 --- a/example/http-client/CMakeLists.txt +++ b/example/http/client/coro/CMakeLists.txt @@ -8,10 +8,10 @@ # GroupSources(include/boost/beast beast) -GroupSources(example/http-client "/") +GroupSources(example/http/client/coro "/") -add_executable (http-client +add_executable (http-client-coro ${BOOST_BEAST_INCLUDES} Jamfile - http_client.cpp + http_client_coro.cpp ) diff --git a/example/websocket-client/Jamfile b/example/http/client/coro/Jamfile similarity index 88% rename from example/websocket-client/Jamfile rename to example/http/client/coro/Jamfile index 99947b3c..2cb6b00b 100644 --- a/example/websocket-client/Jamfile +++ b/example/http/client/coro/Jamfile @@ -7,8 +7,8 @@ # Official repository: https://github.com/boostorg/beast # -exe websocket-client : - websocket_client.cpp +exe http-client-coro : + http_client_coro.cpp : coverage:no ubasan:no diff --git a/example/http/client/coro/http_client_coro.cpp b/example/http/client/coro/http_client_coro.cpp new file mode 100644 index 00000000..8f5665a5 --- /dev/null +++ b/example/http/client/coro/http_client_coro.cpp @@ -0,0 +1,134 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP client, coroutine +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Performs an HTTP GET and prints the response +void +do_session( + std::string const& host, + std::string const& port, + std::string const& target, + boost::asio::io_service& ios, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + tcp::socket socket{ios}; + + // Look up the domain name + auto const lookup = resolver.async_resolve({host, port}, yield[ec]); + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect(socket, lookup, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // Set up an HTTP GET request message + http::request req{http::verb::get, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Send the HTTP request to the remote host + http::async_write(socket, req, yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer b; + + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::async_read(socket, b, res, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the socket + socket.shutdown(tcp::socket::shutdown_both, ec); + + // not_connected happens sometimes + // so don't bother reporting it. + // + if(ec && ec != boost::system::errc::not_connected) + return fail(ec, "shutdown"); + + // If we get here then the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-coro \n" << + "Example:\n" << + " http-client-coro www.example.com 80 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // Launch the asynchronous operation + boost::asio::spawn(ios, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(target), + std::ref(ios), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-crawl/CMakeLists.txt b/example/http/client/crawl/CMakeLists.txt similarity index 91% rename from example/http-crawl/CMakeLists.txt rename to example/http/client/crawl/CMakeLists.txt index bf85cc28..7133df27 100644 --- a/example/http-crawl/CMakeLists.txt +++ b/example/http/client/crawl/CMakeLists.txt @@ -8,7 +8,7 @@ # GroupSources(include/boost/beast beast) -GroupSources(example/http-crawl "/") +GroupSources(example/http/client/crawl "/") add_executable (http-crawl ${BOOST_BEAST_INCLUDES} diff --git a/example/http-crawl/Jamfile b/example/http/client/crawl/Jamfile similarity index 100% rename from example/http-crawl/Jamfile rename to example/http/client/crawl/Jamfile diff --git a/example/http/client/crawl/http_crawl.cpp b/example/http/client/crawl/http_crawl.cpp new file mode 100644 index 00000000..c8a1d8f6 --- /dev/null +++ b/example/http/client/crawl/http_crawl.cpp @@ -0,0 +1,450 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP crawl (asynchronous) +// +//------------------------------------------------------------------------------ + +#include "urls_large_data.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from +namespace chrono = std::chrono; // from + +//------------------------------------------------------------------------------ + +// This structure aggregates statistics on all the sites +class crawl_report +{ + boost::asio::io_service& ios_; + boost::asio::io_service::strand strand_; + std::atomic index_; + std::vector const& hosts_; + std::size_t count_ = 0; + +public: + crawl_report(boost::asio::io_service& ios) + : ios_(ios) + , strand_(ios_) + , index_(0) + , hosts_(urls_large_data()) + { + } + + // Run an aggregation function on the strand. + // This allows synchronization without a mutex. + template + void + aggregate(F const& f) + { + ios_.post(strand_.wrap( + [&, f] + { + f(*this); + if(count_ % 100 == 0) + { + std::cerr << + "Progress: " << count_ << " of " << hosts_.size() << "\n"; + //std::cerr << *this; + } + ++count_; + })); + } + + // Returns the next host to check + char const* + get_host() + { + auto const n = index_++; + if(n >= hosts_.size()) + return nullptr; + return hosts_[n]; + } + + // Counts the number of timer failures + std::size_t timer_failures = 0; + + // Counts the number of name resolution failures + std::size_t resolve_failures = 0; + + // Counts the number of connection failures + std::size_t connect_failures = 0; + + // Counts the number of write failures + std::size_t write_failures = 0; + + // Counts the number of read failures + std::size_t read_failures = 0; + + // Counts the number of success reads + std::size_t success = 0; + + // Counts the number received of each status code + std::map status_codes; +}; + +std::ostream& +operator<<(std::ostream& os, crawl_report const& report) +{ + // Print the report + os << + "Crawl report\n" << + " Failure counts\n" << + " Timer : " << report.timer_failures << "\n" << + " Resolve : " << report.resolve_failures << "\n" << + " Connect : " << report.connect_failures << "\n" << + " Write : " << report.write_failures << "\n" << + " Read : " << report.read_failures << "\n" << + " Success : " << report.success << "\n" << + " Status codes\n" + ; + for(auto const& result : report.status_codes) + os << + " " << std::setw(3) << result.first << ": " << result.second << + " (" << http::obsolete_reason(static_cast(result.first)) << ")\n"; + os.flush(); + return os; +} + +//------------------------------------------------------------------------------ + +// Performs HTTP GET requests and aggregates the results into a report +class worker : public std::enable_shared_from_this +{ + enum + { + // Use a small timeout to keep things lively + timeout = 5 + }; + + crawl_report& report_; + tcp::resolver resolver_; + tcp::socket socket_; + boost::asio::steady_timer timer_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; // (Must persist between reads) + http::request req_; + http::response res_; + +public: + worker(worker&&) = default; + + // Resolver and socket require an io_service + worker( + crawl_report& report, + boost::asio::io_service& ios) + : report_(report) + , resolver_(ios) + , socket_(ios) + , timer_(ios, + (chrono::steady_clock::time_point::max)()) + , strand_(ios) + { + // Set up the common fields of the request + req_.version = 11; + req_.method(http::verb::get); + req_.target("/"); + req_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + } + + // Start the asynchronous operation + void + run() + { + // Run the timer. The timer is operated + // continuously, this simplifies the code. + on_timer({}); + + do_get_host(); + } + + void + on_timer(boost::system::error_code ec) + { + if(ec && ec != boost::asio::error::operation_aborted) + { + // Should never happen + report_.aggregate( + [](crawl_report& rep) + { + ++rep.timer_failures; + }); + return; + } + + // Verify that the timer really expired since the deadline may have moved. + if(timer_.expires_at() <= chrono::steady_clock::now()) + { + socket_.shutdown(tcp::socket::shutdown_both, ec); + socket_.close(ec); + return; + } + + // Wait on the timer + timer_.async_wait( + strand_.wrap(std::bind( + &worker::on_timer, + shared_from_this(), + std::placeholders::_1))); + } + + void + do_get_host() + { + // Grab another host + auto const host = report_.get_host(); + + // nullptr means no more work + if(! host) + { + boost::system::error_code ec; + timer_.cancel(ec); + return; + } + + // The Host HTTP field is required + req_.set(http::field::host, host); + + // Set the timer + timer_.expires_from_now(chrono::seconds(timeout)); + + // Set up an HTTP GET request message + // Look up the domain name + resolver_.async_resolve( + tcp::resolver::query{host, "http"}, + strand_.wrap(std::bind( + &worker::on_resolve, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + + void + on_resolve( + boost::system::error_code ec, + tcp::resolver::iterator result) + { + if(ec) + { + report_.aggregate( + [](crawl_report& rep) + { + ++rep.resolve_failures; + }); + return do_get_host(); + } + + // Set the timer + timer_.expires_from_now(chrono::seconds(timeout)); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect( + socket_, + result, + strand_.wrap(std::bind( + &worker::on_connect, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_connect(boost::system::error_code ec) + { + if(ec) + { + report_.aggregate( + [](crawl_report& rep) + { + ++rep.connect_failures; + }); + return do_get_host(); + } + + // Set the timer + timer_.expires_from_now(chrono::seconds(timeout)); + + // Send the HTTP request to the remote host + http::async_write( + socket_, + req_, + strand_.wrap(std::bind( + &worker::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + { + report_.aggregate( + [](crawl_report& rep) + { + ++rep.write_failures; + }); + return do_get_host(); + } + + // Set the timer + timer_.expires_from_now(chrono::seconds(timeout)); + + // Receive the HTTP response + http::async_read( + socket_, + buffer_, + res_, + strand_.wrap(std::bind( + &worker::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + if(ec) + { + report_.aggregate( + [](crawl_report& rep) + { + ++rep.read_failures; + }); + return do_get_host(); + } + + auto const code = res_.result_int(); + report_.aggregate( + [code](crawl_report& rep) + { + ++rep.success; + ++rep.status_codes[code]; + }); + + // Gracefully close the socket + socket_.shutdown(tcp::socket::shutdown_both, ec); + socket_.close(ec); + + // If we get here then the connection is closed gracefully + + do_get_host(); + } +}; + +class timer +{ + using clock_type = chrono::system_clock; + + clock_type::time_point when_; + +public: + using duration = clock_type::duration; + + timer() + : when_(clock_type::now()) + { + } + + duration + elapsed() const + { + return clock_type::now() - when_; + } +}; + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 2) + { + std::cerr << + "Usage: http-crawl \n" << + "Example:\n" << + " http-crawl 100 1\n"; + return EXIT_FAILURE; + } + auto const threads = std::max(1, std::atoi(argv[1])); + + // The io_service is required for all I/O + boost::asio::io_service ios{1}; + + // The work keeps io_service::run from returning + boost::optional work{ios}; + + // The report holds the aggregated statistics + crawl_report report{ios}; + + timer t; + + // Create and launch the worker threads. + std::vector workers; + workers.reserve(threads + 1); + for(std::size_t i = 0; i < threads; ++i) + workers.emplace_back( + [&report] + { + // We use a separate io_service for each worker because + // the asio resolver simulates asynchronous operation using + // a dedicated worker thread per io_service, and we want to + // do a lot of name resolutions in parallel. + boost::asio::io_service ios{1}; + std::make_shared(report, ios)->run(); + ios.run(); + }); + + // Add another thread to run the main io_service which + // is used to aggregate the statistics + workers.emplace_back( + [&ios] + { + ios.run(); + }); + + // Now block until all threads exit + for(std::size_t i = 0; i < workers.size(); ++i) + { + auto& thread = workers[i]; + + // If this is the last thread, destroy the + // work object so that it can return from run. + //if(&thread == &workers.back()) + if(i == workers.size() - 1) + work = boost::none; + + // Wait for the thread to exit + thread.join(); + } + + std::cout << + "Elapsed time: " << chrono::duration_cast(t.elapsed()).count() << " seconds\n"; + std::cout << report; + + return EXIT_SUCCESS; +} diff --git a/example/http-crawl/urls_large_data.cpp b/example/http/client/crawl/urls_large_data.cpp similarity index 100% rename from example/http-crawl/urls_large_data.cpp rename to example/http/client/crawl/urls_large_data.cpp diff --git a/example/http-crawl/urls_large_data.hpp b/example/http/client/crawl/urls_large_data.hpp similarity index 74% rename from example/http-crawl/urls_large_data.hpp rename to example/http/client/crawl/urls_large_data.hpp index 57dff305..a05ae6b3 100644 --- a/example/http-crawl/urls_large_data.hpp +++ b/example/http/client/crawl/urls_large_data.hpp @@ -7,8 +7,8 @@ // Official repository: https://github.com/boostorg/beast // -#ifndef BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP -#define BOOST_BEAST_EXAMPLE_HTTP_CRAWL_URLS_LARGE_DATA_HPP +#ifndef BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP +#define BOOST_BEAST_EXAMPLE_HTTP_CLIENT_CRAWL_URLS_LARGE_DATA_HPP #include diff --git a/example/http/client/sync-ssl/CMakeLists.txt b/example/http/client/sync-ssl/CMakeLists.txt new file mode 100644 index 00000000..7c229366 --- /dev/null +++ b/example/http/client/sync-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/client/sync-ssl "/") + +add_executable (http-client-sync-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + http_client_sync_ssl.cpp +) + +target_link_libraries (http-client-sync-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/client/sync-ssl/Jamfile b/example/http/client/sync-ssl/Jamfile new file mode 100644 index 00000000..26fa582f --- /dev/null +++ b/example/http/client/sync-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-client-sync-ssl : + http_client_sync_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/client/sync-ssl/http_client_sync_ssl.cpp b/example/http/client/sync-ssl/http_client_sync_ssl.cpp new file mode 100644 index 00000000..9ed63742 --- /dev/null +++ b/example/http/client/sync-ssl/http_client_sync_ssl.cpp @@ -0,0 +1,112 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL client, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Performs an HTTP GET and prints the response +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-sync-ssl \n" << + "Example:\n" << + " http-client-sync-ssl www.example.com 443 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // These objects perform our I/O + tcp::resolver resolver{ios}; + ssl::stream stream{ios, ctx}; + + // Look up the domain name + auto const lookup = resolver.resolve({host, port}); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(stream.next_layer(), lookup); + + // Perform the SSL handshake + stream.handshake(ssl::stream_base::client); + + // Set up an HTTP GET request message + http::request req{http::verb::get, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Send the HTTP request to the remote host + http::write(stream, req); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::read(stream, buffer, res); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the stream + boost::system::error_code ec; + stream.shutdown(ec); + if(ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec.assign(0, ec.category()); + } + if(ec) + throw boost::system::system_error{ec}; + + // If we get here then the connection is closed gracefully + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/example/websocket-client/CMakeLists.txt b/example/http/client/sync/CMakeLists.txt similarity index 78% rename from example/websocket-client/CMakeLists.txt rename to example/http/client/sync/CMakeLists.txt index 75c7f3c6..9988affd 100644 --- a/example/websocket-client/CMakeLists.txt +++ b/example/http/client/sync/CMakeLists.txt @@ -8,10 +8,10 @@ # GroupSources(include/boost/beast beast) -GroupSources(example/websocket-client "/") +GroupSources(example/http/client/sync "/") -add_executable (websocket-client +add_executable (http-client-sync ${BOOST_BEAST_INCLUDES} Jamfile - websocket_client.cpp + http_client_sync.cpp ) diff --git a/example/http-client/Jamfile b/example/http/client/sync/Jamfile similarity index 88% rename from example/http-client/Jamfile rename to example/http/client/sync/Jamfile index 8f9f7ed6..969246f6 100644 --- a/example/http-client/Jamfile +++ b/example/http/client/sync/Jamfile @@ -7,8 +7,8 @@ # Official repository: https://github.com/boostorg/beast # -exe http-client : - http_client.cpp +exe http-client-sync : + http_client_sync.cpp : coverage:no ubasan:no diff --git a/example/http/client/sync/http_client_sync.cpp b/example/http/client/sync/http_client_sync.cpp new file mode 100644 index 00000000..e56fdc62 --- /dev/null +++ b/example/http/client/sync/http_client_sync.cpp @@ -0,0 +1,101 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP client, synchronous +// +//------------------------------------------------------------------------------ + +//[example_http_client + +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +// Performs an HTTP GET and prints the response +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: http-client-sync \n" << + "Example:\n" << + " http-client-sync www.example.com 80 /\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const target = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + tcp::socket socket{ios}; + + // Look up the domain name + auto const lookup = resolver.resolve({host, port}); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(socket, lookup); + + // Set up an HTTP GET request message + http::request req{http::verb::get, target, 11}; + req.set(http::field::host, host); + req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); + + // Send the HTTP request to the remote host + http::write(socket, req); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + http::response res; + + // Receive the HTTP response + http::read(socket, buffer, res); + + // Write the message to standard out + std::cout << res << std::endl; + + // Gracefully close the socket + boost::system::error_code ec; + socket.shutdown(tcp::socket::shutdown_both, ec); + + // not_connected happens sometimes + // so don't bother reporting it. + // + if(ec && ec != boost::system::errc::not_connected) + throw boost::system::system_error{ec}; + + // If we get here then the connection is closed gracefully + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +//] diff --git a/example/http/server/CMakeLists.txt b/example/http/server/CMakeLists.txt new file mode 100644 index 00000000..350b465a --- /dev/null +++ b/example/http/server/CMakeLists.txt @@ -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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (async) +add_subdirectory (coro) +add_subdirectory (fast) +add_subdirectory (small) +add_subdirectory (stackless) +add_subdirectory (sync) + +if (OPENSSL_FOUND) + add_subdirectory (async-ssl) + add_subdirectory (coro-ssl) + add_subdirectory (flex) + add_subdirectory (stackless-ssl) + add_subdirectory (sync-ssl) +endif() diff --git a/example/http/server/Jamfile b/example/http/server/Jamfile new file mode 100644 index 00000000..d5c0fdc7 --- /dev/null +++ b/example/http/server/Jamfile @@ -0,0 +1,22 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project async ; +build-project coro ; +build-project fast ; +build-project small ; +build-project stackless ; +build-project sync ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project async-ssl ; +#build-project coro-ssl ; +#build-project flex ; +#build-project stackless-ssl ; +#build-project sync-ssl ; diff --git a/example/http/server/async-ssl/CMakeLists.txt b/example/http/server/async-ssl/CMakeLists.txt new file mode 100644 index 00000000..3f328b78 --- /dev/null +++ b/example/http/server/async-ssl/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/async-ssl "/") + +add_executable (http-server-async-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp + Jamfile + http_server_async_ssl.cpp +) + +target_link_libraries (http-server-async-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/server/async-ssl/Jamfile b/example/http/server/async-ssl/Jamfile new file mode 100644 index 00000000..875bacd7 --- /dev/null +++ b/example/http/server/async-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-server-async-ssl : + http_server_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/async-ssl/http_server_async_ssl.cpp b/example/http/server/async-ssl/http_server_async_ssl.cpp new file mode 100644 index 00000000..af6583fd --- /dev/null +++ b/example/http/server/async-ssl/http_server_async_ssl.cpp @@ -0,0 +1,493 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL server, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handles an HTTP server connection +class session : public std::enable_shared_from_this +{ + // This is the C++11 equivalent of a generic lambda. + // The function object is used to send an HTTP message. + struct send_lambda + { + session& self_; + + explicit + send_lambda(session& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + async_write_msg( + self_.stream_, + std::move(msg), + self_.strand_.wrap(std::bind( + &session::on_write, + self_.shared_from_this(), + std::placeholders::_1))); + } + }; + + tcp::socket socket_; + ssl::stream stream_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + std::string const& doc_root_; + http::request req_; + send_lambda lambda_; + +public: + // Take ownership of the socket + explicit + session( + tcp::socket socket, + ssl::context& ctx, + std::string const& doc_root) + : socket_(std::move(socket)) + , stream_(socket_, ctx) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + , lambda_(*this) + { + } + + // Start the asynchronous operation + void + run() + { + // Perform the SSL handshake + stream_.async_handshake( + ssl::stream_base::server, + strand_.wrap(std::bind( + &session::on_handshake, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + do_read(); + } + + void + do_read() + { + // Read a request + http::async_read(stream_, buffer_, req_, + strand_.wrap(std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This means they closed the connection + if(ec == http::error::end_of_stream) + return do_close(); + + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root_, std::move(req_), lambda_); + } + + void + on_write(boost::system::error_code ec) + { + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return do_close(); + } + + if(ec) + return fail(ec, "write"); + + // Read another request + do_read(); + } + + void + do_close() + { + // Perform the SSL shutdown + stream_.async_shutdown( + strand_.wrap(std::bind( + &session::on_shutdown, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_shutdown(boost::system::error_code ec) + { + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + std::string const& doc_root) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared( + std::move(socket_), + ctx_, + doc_root_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-async-ssl
\n" << + "Example:\n" << + " http-server-async-ssl 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared( + ios, + ctx, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-client-ssl/CMakeLists.txt b/example/http/server/async/CMakeLists.txt similarity index 68% rename from example/http-client-ssl/CMakeLists.txt rename to example/http/server/async/CMakeLists.txt index 1bc4947d..71d1f0b4 100644 --- a/example/http-client-ssl/CMakeLists.txt +++ b/example/http/server/async/CMakeLists.txt @@ -9,15 +9,11 @@ GroupSources(include/boost/beast beast) GroupSources(example/common common) -GroupSources(example/http-client-ssl "/") +GroupSources(example/http/server/async "/") -add_executable (http-client-ssl +add_executable (http-server-async ${BOOST_BEAST_INCLUDES} - ${COMMON_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp Jamfile - http_client_ssl.cpp + http_server_async.cpp ) - -target_link_libraries (http-client-ssl - ${OPENSSL_LIBRARIES} - ) diff --git a/example/http/server/async/Jamfile b/example/http/server/async/Jamfile new file mode 100644 index 00000000..f36d1ad4 --- /dev/null +++ b/example/http/server/async/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe http-server-async : + http_server_async.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/async/http_server_async.cpp b/example/http/server/async/http_server_async.cpp new file mode 100644 index 00000000..9282573a --- /dev/null +++ b/example/http/server/async/http_server_async.cpp @@ -0,0 +1,451 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handles an HTTP server connection +class session : public std::enable_shared_from_this +{ + // This is the C++11 equivalent of a generic lambda. + // The function object is used to send an HTTP message. + struct send_lambda + { + session& self_; + + explicit + send_lambda(session& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + async_write_msg( + self_.socket_, + std::move(msg), + self_.strand_.wrap(std::bind( + &session::on_write, + self_.shared_from_this(), + std::placeholders::_1))); + } + }; + + tcp::socket socket_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + std::string const& doc_root_; + http::request req_; + send_lambda lambda_; + +public: + // Take ownership of the socket + explicit + session( + tcp::socket socket, + std::string const& doc_root) + : socket_(std::move(socket)) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + , lambda_(*this) + { + } + + // Start the asynchronous operation + void + run() + { + do_read(); + } + + void + do_read() + { + // Read a request + http::async_read(socket_, buffer_, req_, + strand_.wrap(std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This means they closed the connection + if(ec == http::error::end_of_stream) + return do_close(); + + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root_, std::move(req_), lambda_); + } + + void + on_write(boost::system::error_code ec) + { + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return do_close(); + } + + if(ec) + return fail(ec, "write"); + + // Read another request + do_read(); + } + + void + do_close() + { + // Send a TCP shutdown + boost::system::error_code ec; + socket_.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + std::string const& doc_root) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared( + std::move(socket_), + doc_root_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-async
\n" << + "Example:\n" << + " http-server-async 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create and launch a listening port + std::make_shared( + ios, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/server/coro-ssl/CMakeLists.txt b/example/http/server/coro-ssl/CMakeLists.txt new file mode 100644 index 00000000..5a37eeed --- /dev/null +++ b/example/http/server/coro-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/coro-ssl "/") + +add_executable (http-server-coro-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + http_server_coro_ssl.cpp +) + +target_link_libraries (http-server-coro-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/server/coro-ssl/Jamfile b/example/http/server/coro-ssl/Jamfile new file mode 100644 index 00000000..838e7cd7 --- /dev/null +++ b/example/http/server/coro-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-server-coro-ssl : + http_server_coro_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/coro-ssl/http_server_coro_ssl.cpp b/example/http/server/coro-ssl/http_server_coro_ssl.cpp new file mode 100644 index 00000000..cbecea5b --- /dev/null +++ b/example/http/server/coro-ssl/http_server_coro_ssl.cpp @@ -0,0 +1,393 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL server, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// This is the C++11 equivalent of a generic lambda. +// The function object is used to send an HTTP message. +template +struct send_lambda +{ + Stream& stream_; + boost::system::error_code& ec_; + boost::asio::yield_context yield_; + + explicit + send_lambda( + Stream& stream, + boost::system::error_code& ec, + boost::asio::yield_context yield) + : stream_(stream) + , ec_(ec) + , yield_(yield) + { + } + + template + void + operator()(http::message&& msg) const + { + // We need the serializer here because the serializer requires + // a non-const file_body, and the message oriented version of + // http::write only works with const messages. + http::serializer sr{msg}; + http::async_write(stream_, sr, yield_[ec_]); + } +}; + +// Handles an HTTP server connection +void +do_session( + tcp::socket& socket, + ssl::context& ctx, + std::string const& doc_root, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Construct the stream around the socket + ssl::stream stream{socket, ctx}; + + // Perform the SSL handshake + stream.async_handshake(ssl::stream_base::server, yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // This buffer is required to persist across reads + boost::beast::flat_buffer buffer; + + // This lambda is used to send messages + send_lambda> lambda{stream, ec, yield}; + + for(;;) + { + // Read a request + http::request req; + http::async_read(stream, buffer, req, yield[ec]); + if(ec == http::error::end_of_stream) + break; + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root, std::move(req), lambda); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Perform the SSL shutdown + stream.async_shutdown(yield[ec]); + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + std::string const& doc_root, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ios); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ios); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_io_service(), + std::bind( + &do_session, + std::move(socket), + std::ref(ctx), + doc_root, + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-coro-ssl
\n" << + "Example:\n" << + " http-server-coro-ssl 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + std::string const doc_root = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Spawn a listening port + boost::asio::spawn(ios, + std::bind( + &do_listen, + std::ref(ios), + std::ref(ctx), + tcp::endpoint{address, port}, + doc_root, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/server/coro/CMakeLists.txt b/example/http/server/coro/CMakeLists.txt new file mode 100644 index 00000000..5b5ba238 --- /dev/null +++ b/example/http/server/coro/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/http/server/coro "/") + +add_executable (http-server-coro + ${BOOST_BEAST_INCLUDES} + Jamfile + http_server_coro.cpp +) diff --git a/example/http/server/coro/Jamfile b/example/http/server/coro/Jamfile new file mode 100644 index 00000000..013c2a9f --- /dev/null +++ b/example/http/server/coro/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe http-server-coro : + http_server_coro.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/coro/http_server_coro.cpp b/example/http/server/coro/http_server_coro.cpp new file mode 100644 index 00000000..421f1f24 --- /dev/null +++ b/example/http/server/coro/http_server_coro.cpp @@ -0,0 +1,369 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, coroutine +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// This is the C++11 equivalent of a generic lambda. +// The function object is used to send an HTTP message. +template +struct send_lambda +{ + Stream& stream_; + boost::system::error_code& ec_; + boost::asio::yield_context yield_; + + explicit + send_lambda( + Stream& stream, + boost::system::error_code& ec, + boost::asio::yield_context yield) + : stream_(stream) + , ec_(ec) + , yield_(yield) + { + } + + template + void + operator()(http::message&& msg) const + { + // We need the serializer here because the serializer requires + // a non-const file_body, and the message oriented version of + // http::write only works with const messages. + http::serializer sr{msg}; + http::async_write(stream_, sr, yield_[ec_]); + } +}; + +// Handles an HTTP server connection +void +do_session( + tcp::socket& socket, + std::string const& doc_root, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // This buffer is required to persist across reads + boost::beast::flat_buffer buffer; + + // This lambda is used to send messages + send_lambda lambda{socket, ec, yield}; + + for(;;) + { + // Read a request + http::request req; + http::async_read(socket, buffer, req, yield[ec]); + if(ec == http::error::end_of_stream) + break; + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root, std::move(req), lambda); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Send a TCP shutdown + socket.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + std::string const& doc_root, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ios); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ios); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_io_service(), + std::bind( + &do_session, + std::move(socket), + doc_root, + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-coro
\n" << + "Example:\n" << + " http-server-coro 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + std::string const doc_root = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Spawn a listening port + boost::asio::spawn(ios, + std::bind( + &do_listen, + std::ref(ios), + tcp::endpoint{address, port}, + doc_root, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-server-fast/CMakeLists.txt b/example/http/server/fast/CMakeLists.txt similarity index 92% rename from example/http-server-fast/CMakeLists.txt rename to example/http/server/fast/CMakeLists.txt index ef7e6d91..8e205332 100644 --- a/example/http-server-fast/CMakeLists.txt +++ b/example/http/server/fast/CMakeLists.txt @@ -9,7 +9,7 @@ GroupSources(include/boost/beast beast) GroupSources(example/common common) -GroupSources(example/http-server-fast "/") +GroupSources(example/http/server/fast "/") add_executable (http-server-fast ${BOOST_BEAST_INCLUDES} diff --git a/example/http-server-fast/Jamfile b/example/http/server/fast/Jamfile similarity index 100% rename from example/http-server-fast/Jamfile rename to example/http/server/fast/Jamfile diff --git a/example/http-server-fast/fields_alloc.hpp b/example/http/server/fast/fields_alloc.hpp similarity index 100% rename from example/http-server-fast/fields_alloc.hpp rename to example/http/server/fast/fields_alloc.hpp diff --git a/example/http-server-fast/http_server_fast.cpp b/example/http/server/fast/http_server_fast.cpp similarity index 84% rename from example/http-server-fast/http_server_fast.cpp rename to example/http/server/fast/http_server_fast.cpp index 0b3751f0..5081d29d 100644 --- a/example/http-server-fast/http_server_fast.cpp +++ b/example/http/server/fast/http_server_fast.cpp @@ -7,9 +7,13 @@ // Official repository: https://github.com/boostorg/beast // -#include "fields_alloc.hpp" +//------------------------------------------------------------------------------ +// +// Example: HTTP server, fast +// +//------------------------------------------------------------------------------ -#include "../common/mime_types.hpp" +#include "fields_alloc.hpp" #include #include @@ -19,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -29,6 +32,42 @@ namespace ip = boost::asio::ip; // from using tcp = boost::asio::ip::tcp; // from namespace http = boost::beast::http; // from +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + class http_worker { public: @@ -306,7 +345,7 @@ int main(int argc, char* argv[]) } catch (const std::exception& e) { - std::cerr << "Exception: " << e.what() << std::endl; + std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } } diff --git a/example/http/server/flex/CMakeLists.txt b/example/http/server/flex/CMakeLists.txt new file mode 100644 index 00000000..5368e8ec --- /dev/null +++ b/example/http/server/flex/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/flex "/") + +add_executable (http-server-flex + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/detect_ssl.hpp + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp + Jamfile + http_server_flex.cpp +) + +target_link_libraries (http-server-flex + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/server/flex/Jamfile b/example/http/server/flex/Jamfile new file mode 100644 index 00000000..875bacd7 --- /dev/null +++ b/example/http/server/flex/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-server-async-ssl : + http_server_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/flex/http_server_flex.cpp b/example/http/server/flex/http_server_flex.cpp new file mode 100644 index 00000000..19cdc6ea --- /dev/null +++ b/example/http/server/flex/http_server_flex.cpp @@ -0,0 +1,657 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP flex server (plain and SSL), asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/detect_ssl.hpp" +#include "example/common/server_certificate.hpp" +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handles an HTTP server connection. +// This uses the Curiously Recurring Template Pattern so that +// the same code works with both SSL streams and regular sockets. +template +class session +{ + // Access the derived class, this is part of + // the Curiously Recurring Template Pattern idiom. + Derived& + derived() + { + return static_cast(*this); + } + + // This is the C++11 equivalent of a generic lambda. + // The function object is used to send an HTTP message. + struct send_lambda + { + session& self_; + + explicit + send_lambda(session& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + async_write_msg( + self_.derived().stream(), + std::move(msg), + self_.strand_.wrap(std::bind( + &session::on_write, + self_.derived().shared_from_this(), + std::placeholders::_1))); + } + }; + + std::string const& doc_root_; + http::request req_; + send_lambda lambda_; + +protected: + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + +public: + // Take ownership of the buffer + explicit + session( + boost::asio::io_service& ios, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : doc_root_(doc_root) + , lambda_(*this) + , strand_(ios) + , buffer_(std::move(buffer)) + { + } + + void + do_read() + { + // Read a request + http::async_read( + derived().stream(), + buffer_, + req_, + strand_.wrap(std::bind( + &session::on_read, + derived().shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This means they closed the connection + if(ec == http::error::end_of_stream) + return derived().do_eof(); + + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root_, std::move(req_), lambda_); + } + + void + on_write(boost::system::error_code ec) + { + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return derived().do_eof(); + } + + if(ec) + return fail(ec, "write"); + + // Read another request + do_read(); + } +}; + +// Handles a plain HTTP connection +class plain_session + : public session + , public std::enable_shared_from_this +{ + tcp::socket socket_; + boost::asio::io_service::strand strand_; + +public: + // Create the session + plain_session( + tcp::socket socket, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : session( + socket.get_io_service(), + std::move(buffer), + doc_root) + , socket_(std::move(socket)) + , strand_(socket_.get_io_service()) + { + } + + // Called by the base class + tcp::socket& + stream() + { + return socket_; + } + + // Start the asynchronous operation + void + run() + { + do_read(); + } + + void + do_eof() + { + // Send a TCP shutdown + boost::system::error_code ec; + socket_.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } +}; + +// Handles an SSL HTTP connection +class ssl_session + : public session + , public std::enable_shared_from_this +{ + tcp::socket socket_; + ssl::stream stream_; + boost::asio::io_service::strand strand_; + +public: + // Create the session + ssl_session( + tcp::socket socket, + ssl::context& ctx, + boost::beast::flat_buffer buffer, + std::string const& doc_root) + : session( + socket.get_io_service(), + std::move(buffer), + doc_root) + , socket_(std::move(socket)) + , stream_(socket_, ctx) + , strand_(stream_.get_io_service()) + { + } + + // Called by the base class + ssl::stream& + stream() + { + return stream_; + } + + // Start the asynchronous operation + void + run() + { + // Perform the SSL handshake + // Note, this is the buffered version of the handshake. + stream_.async_handshake( + ssl::stream_base::server, + buffer_.data(), + strand_.wrap(std::bind( + &ssl_session::on_handshake, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + } + void + on_handshake( + boost::system::error_code ec, + std::size_t bytes_used) + { + if(ec) + return fail(ec, "handshake"); + + // Consume the portion of the buffer used by the handshake + buffer_.consume(bytes_used); + + do_read(); + } + + void + do_eof() + { + // Perform the SSL shutdown + stream_.async_shutdown( + strand_.wrap(std::bind( + &ssl_session::on_shutdown, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_shutdown(boost::system::error_code ec) + { + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } +}; + +//------------------------------------------------------------------------------ + +// Detects SSL handshakes +class detect_session : public std::enable_shared_from_this +{ + tcp::socket socket_; + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + std::string const& doc_root_; + boost::beast::flat_buffer buffer_; + +public: + explicit + detect_session( + tcp::socket socket, + ssl::context& ctx, + std::string const& doc_root) + : socket_(std::move(socket)) + , ctx_(ctx) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + { + } + + // Launch the detector + void + run() + { + async_detect_ssl( + socket_, + buffer_, + strand_.wrap(std::bind( + &detect_session::on_detect, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2))); + + } + + void + on_detect(boost::system::error_code ec, boost::tribool result) + { + if(ec) + return fail(ec, "detect"); + + if(result) + { + // Launch SSL session + std::make_shared( + std::move(socket_), + ctx_, + std::move(buffer_), + doc_root_)->run(); + return; + } + + // Launch plain session + std::make_shared( + std::move(socket_), + std::move(buffer_), + doc_root_)->run(); + } +}; + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + std::string const& doc_root) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the detector session and run it + std::make_shared( + std::move(socket_), + ctx_, + doc_root_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-sync
\n" << + "Example:\n" << + " http-server-sync 0.0.0.0 8080 .\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared( + ios, + ctx, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http-server-small/CMakeLists.txt b/example/http/server/small/CMakeLists.txt similarity index 90% rename from example/http-server-small/CMakeLists.txt rename to example/http/server/small/CMakeLists.txt index 3f1be59a..cecc9622 100644 --- a/example/http-server-small/CMakeLists.txt +++ b/example/http/server/small/CMakeLists.txt @@ -8,7 +8,7 @@ # GroupSources(include/boost/beast beast) -GroupSources(example/http-server-small "/") +GroupSources(example/http/server/small "/") add_executable (http-server-small ${BOOST_BEAST_INCLUDES} diff --git a/example/http-server-small/Jamfile b/example/http/server/small/Jamfile similarity index 100% rename from example/http-server-small/Jamfile rename to example/http/server/small/Jamfile diff --git a/example/http-server-small/http_server_small.cpp b/example/http/server/small/http_server_small.cpp similarity index 96% rename from example/http-server-small/http_server_small.cpp rename to example/http/server/small/http_server_small.cpp index c30870fe..4aad278d 100644 --- a/example/http-server-small/http_server_small.cpp +++ b/example/http/server/small/http_server_small.cpp @@ -7,6 +7,12 @@ // Official repository: https://github.com/boostorg/beast // +//------------------------------------------------------------------------------ +// +// Example: HTTP server, small +// +//------------------------------------------------------------------------------ + #include #include #include @@ -236,7 +242,7 @@ main(int argc, char* argv[]) } catch(std::exception const& e) { - std::cerr << "Exception: " << e.what() << std::endl; + std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } } diff --git a/example/http/server/stackless-ssl/CMakeLists.txt b/example/http/server/stackless-ssl/CMakeLists.txt new file mode 100644 index 00000000..bc536fac --- /dev/null +++ b/example/http/server/stackless-ssl/CMakeLists.txt @@ -0,0 +1,24 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/stackless-ssl "/") + +add_executable (http-server-stackless-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp + Jamfile + http_server_stackless_ssl.cpp +) + +target_link_libraries (http-server-stackless-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/server/stackless-ssl/Jamfile b/example/http/server/stackless-ssl/Jamfile new file mode 100644 index 00000000..875bacd7 --- /dev/null +++ b/example/http/server/stackless-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-server-async-ssl : + http_server_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp new file mode 100644 index 00000000..d27a3c0f --- /dev/null +++ b/example/http/server/stackless-ssl/http_server_stackless_ssl.cpp @@ -0,0 +1,479 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handles an HTTP server connection +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + // This is the C++11 equivalent of a generic lambda. + // The function object is used to send an HTTP message. + struct send_lambda + { + session& self_; + + explicit + send_lambda(session& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + async_write_msg( + self_.stream_, + std::move(msg), + self_.strand_.wrap(std::bind( + &session::loop, + self_.shared_from_this(), + std::placeholders::_1))); + } + }; + + tcp::socket socket_; + ssl::stream stream_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + std::string const& doc_root_; + http::request req_; + send_lambda lambda_; + +public: + // Take ownership of the socket + explicit + session( + tcp::socket socket, + ssl::context& ctx, + std::string const& doc_root) + : socket_(std::move(socket)) + , stream_(socket_, ctx) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + , lambda_(*this) + { + } + + // Start the asynchronous operation + void + run() + { + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + // Perform the SSL handshake + yield stream_.async_handshake( + ssl::stream_base::server, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "handshake"); + + for(;;) + { + // Read a request + yield http::async_read(stream_, buffer_, req_, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec == http::error::end_of_stream) + { + // The remote host closed the connection + break; + } + if(ec) + return fail(ec, "read"); + + // Send the response + yield handle_request(doc_root_, std::move(req_), lambda_); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Perform the SSL shutdown + yield stream_.async_shutdown( + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully + } + } +#include +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + std::string const& doc_root) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared( + std::move(socket_), + ctx_, + doc_root_)->run(); + } + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-stackless-ssl
\n" << + "Example:\n" << + " http-server-stackless-ssl 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared( + ios, + ctx, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/server/stackless/CMakeLists.txt b/example/http/server/stackless/CMakeLists.txt new file mode 100644 index 00000000..c7a0b002 --- /dev/null +++ b/example/http/server/stackless/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/stackless "/") + +add_executable (http-server-stackless + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/write_msg.hpp + Jamfile + http_server_stackless.cpp +) diff --git a/example/http/server/stackless/Jamfile b/example/http/server/stackless/Jamfile new file mode 100644 index 00000000..f82934ff --- /dev/null +++ b/example/http/server/stackless/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe http-server-stackless : + http_server_stackless.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/stackless/http_server_stackless.cpp b/example/http/server/stackless/http_server_stackless.cpp new file mode 100644 index 00000000..796f96c1 --- /dev/null +++ b/example/http/server/stackless/http_server_stackless.cpp @@ -0,0 +1,445 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/write_msg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Handles an HTTP server connection +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + // This is the C++11 equivalent of a generic lambda. + // The function object is used to send an HTTP message. + struct send_lambda + { + session& self_; + + explicit + send_lambda(session& self) + : self_(self) + { + } + + template + void + operator()(http::message&& msg) const + { + // This function takes ownership of the message by moving + // it into a temporary buffer, otherwise we would have + // to manage the lifetime of the message and serializer. + async_write_msg( + self_.socket_, + std::move(msg), + self_.strand_.wrap(std::bind( + &session::loop, + self_.shared_from_this(), + std::placeholders::_1))); + } + }; + + tcp::socket socket_; + boost::asio::io_service::strand strand_; + boost::beast::flat_buffer buffer_; + std::string const& doc_root_; + http::request req_; + send_lambda lambda_; + +public: + // Take ownership of the socket + explicit + session( + tcp::socket socket, + std::string const& doc_root) + : socket_(std::move(socket)) + , strand_(socket_.get_io_service()) + , doc_root_(doc_root) + , lambda_(*this) + { + } + + // Start the asynchronous operation + void + run() + { + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + // Read a request + yield http::async_read(socket_, buffer_, req_, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec == http::error::end_of_stream) + { + // The remote host closed the connection + break; + } + if(ec) + return fail(ec, "read"); + + // Send the response + yield handle_request(doc_root_, std::move(req_), lambda_); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Send a TCP shutdown + socket_.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully + } + } +#include +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + std::string const& doc_root_; + +public: + listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + std::string const& doc_root) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + , doc_root_(doc_root) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared( + std::move(socket_), + doc_root_)->run(); + } + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 5) + { + std::cerr << + "Usage: http-server-stackless
\n" << + "Example:\n" << + " http-server-stackless 0.0.0.0 8080 . 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create and launch a listening port + std::make_shared( + ios, + tcp::endpoint{address, port}, + doc_root)->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/http/server/sync-ssl/CMakeLists.txt b/example/http/server/sync-ssl/CMakeLists.txt new file mode 100644 index 00000000..03824e51 --- /dev/null +++ b/example/http/server/sync-ssl/CMakeLists.txt @@ -0,0 +1,22 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/http/server/sync-ssl "/") + +add_executable (http-server-sync-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + http_server_sync_ssl.cpp +) +target_link_libraries (http-server-sync-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/http/server/sync-ssl/Jamfile b/example/http/server/sync-ssl/Jamfile new file mode 100644 index 00000000..d7a5ca72 --- /dev/null +++ b/example/http/server/sync-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe http-server-sync-ssl : + http_server_sync_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/sync-ssl/http_server_sync_ssl.cpp b/example/http/server/sync-ssl/http_server_sync_ssl.cpp new file mode 100644 index 00000000..15572b84 --- /dev/null +++ b/example/http/server/sync-ssl/http_server_sync_ssl.cpp @@ -0,0 +1,343 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP SSL server, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace http = boost::beast::http; // from + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// This is the C++11 equivalent of a generic lambda. +// The function object is used to send an HTTP message. +template +struct send_lambda +{ + Stream& stream_; + boost::system::error_code& ec_; + + explicit + send_lambda( + Stream& stream, + boost::system::error_code& ec) + : stream_(stream) + , ec_(ec) + { + } + + template + void + operator()(http::message&& msg) const + { + // We need the serializer here because the serializer requires + // a non-const file_body, and the message oriented version of + // http::write only works with const messages. + http::serializer sr{msg}; + http::write(stream_, sr, ec_); + } +}; + +// Handles an HTTP server connection +void +do_session( + tcp::socket& socket, + ssl::context& ctx, + std::string const& doc_root) +{ + boost::system::error_code ec; + + // Construct the stream around the socket + ssl::stream stream{socket, ctx}; + + // Perform the SSL handshake + stream.handshake(ssl::stream_base::server, ec); + if(ec) + return fail(ec, "handshake"); + + // This buffer is required to persist across reads + boost::beast::flat_buffer buffer; + + // This lambda is used to send messages + send_lambda> lambda{stream, ec}; + + for(;;) + { + // Read a request + http::request req; + http::read(stream, buffer, req, ec); + if(ec == http::error::end_of_stream) + break; + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root, std::move(req), lambda); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Perform the SSL shutdown + stream.shutdown(ec); + if(ec) + return fail(ec, "shutdown"); + + // At this point the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: http-server-sync-ssl
\n" << + "Example:\n" << + " http-server-sync-ssl 0.0.0.0 8080 .\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios{1}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ios}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket), + std::ref(ctx), + doc_root)}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/example/http/server/sync/CMakeLists.txt b/example/http/server/sync/CMakeLists.txt new file mode 100644 index 00000000..ab130761 --- /dev/null +++ b/example/http/server/sync/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/http/server/sync "/") + +add_executable (http-server-sync + ${BOOST_BEAST_INCLUDES} + Jamfile + http_server_sync.cpp +) diff --git a/example/http/server/sync/Jamfile b/example/http/server/sync/Jamfile new file mode 100644 index 00000000..30e4f026 --- /dev/null +++ b/example/http/server/sync/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe http-server-sync : + http_server_sync.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/http/server/sync/http_server_sync.cpp b/example/http/server/sync/http_server_sync.cpp new file mode 100644 index 00000000..bee474d7 --- /dev/null +++ b/example/http/server/sync/http_server_sync.cpp @@ -0,0 +1,323 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: HTTP server, synchronous +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from + +//------------------------------------------------------------------------------ + +// Return a reasonable mime type based on the extension of a file. +boost::beast::string_view +mime_type(boost::beast::string_view path) +{ + using boost::beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if(pos == boost::beast::string_view::npos) + return boost::beast::string_view{}; + return path.substr(pos); + }(); + 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, ".txt")) return "text/plain"; + 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"; +} + +// Append an HTTP rel-path to a local filesystem path. +// The returned path is normalized for the platform. +std::string +path_cat( + boost::beast::string_view base, + boost::beast::string_view path) +{ + if(base.empty()) + return path.to_string(); + std::string result = base.to_string(); +#if BOOST_MSVC + char constexpr path_separator = '\\'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); + for(auto& c : result) + if(c == '/') + c = path_separator; +#else + char constexpr path_separator = '/'; + if(result.back() == path_separator) + result.resize(result.size() - 1); + result.append(path.data(), path.size()); +#endif + return result; +} + +// This function produces an HTTP response for the given +// request. The type of the response object depends on the +// contents of the request, so the interface requires the +// caller to pass a generic lambda for receiving the response. +template< + class Body, class Allocator, + class Send> +void +handle_request( + boost::beast::string_view doc_root, + http::request>&& req, + Send&& send) +{ + // Returns a bad request response + auto const bad_request = + [&req](boost::beast::string_view why) + { + http::response res{http::status::bad_request, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = why.to_string(); + res.prepare_payload(); + return res; + }; + + // Returns a not found response + auto const not_found = + [&req](boost::beast::string_view target) + { + http::response res{http::status::not_found, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "The resource '" + target.to_string() + "' was not found."; + res.prepare_payload(); + return res; + }; + + // Returns a server error response + auto const server_error = + [&req](boost::beast::string_view what) + { + http::response res{http::status::internal_server_error, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, "text/html"); + res.keep_alive(req.keep_alive()); + res.body = "An error occurred: '" + what.to_string() + "'"; + res.prepare_payload(); + return res; + }; + + // Make sure we can handle the method + if( req.method() != http::verb::get && + req.method() != http::verb::head) + return send(bad_request("Unknown HTTP-method")); + + // Request path must be absolute and not contain "..". + if( req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != boost::beast::string_view::npos) + return send(bad_request("Illegal request-target")); + + // Build the path to the requested file + std::string path = path_cat(doc_root, req.target()); + if(req.target().back() == '/') + path.append("index.html"); + + // Attempt to open the file + boost::beast::error_code ec; + http::file_body::value_type body; + body.open(path.c_str(), boost::beast::file_mode::scan, ec); + + // Handle the case where the file doesn't exist + if(ec == boost::system::errc::no_such_file_or_directory) + return send(not_found(req.target())); + + // Handle an unknown error + if(ec) + return send(server_error(ec.message())); + + // Respond to HEAD request + if(req.method() == http::verb::head) + { + http::response res{http::status::ok, req.version}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); + } + + // Respond to HEAD request + http::response res{ + std::piecewise_construct, + std::make_tuple(std::move(body)), + std::make_tuple(http::status::ok, req.version)}; + res.set(http::field::server, BOOST_BEAST_VERSION_STRING); + res.set(http::field::content_type, mime_type(path)); + res.content_length(body.size()); + res.keep_alive(req.keep_alive()); + return send(std::move(res)); +} + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// This is the C++11 equivalent of a generic lambda. +// The function object is used to send an HTTP message. +template +struct send_lambda +{ + Stream& stream_; + boost::system::error_code& ec_; + + explicit + send_lambda( + Stream& stream, + boost::system::error_code& ec) + : stream_(stream) + , ec_(ec) + { + } + + template + void + operator()(http::message&& msg) const + { + // We need the serializer here because the serializer requires + // a non-const file_body, and the message oriented version of + // http::write only works with const messages. + http::serializer sr{msg}; + http::write(stream_, sr, ec_); + } +}; + +// Handles an HTTP server connection +void +do_session( + tcp::socket& socket, + std::string const& doc_root) +{ + boost::system::error_code ec; + + // This buffer is required to persist across reads + boost::beast::flat_buffer buffer; + + // This lambda is used to send messages + send_lambda lambda{socket, ec}; + + for(;;) + { + // Read a request + http::request req; + http::read(socket, buffer, req, ec); + if(ec == http::error::end_of_stream) + break; + if(ec) + return fail(ec, "read"); + + // Send the response + handle_request(doc_root, std::move(req), lambda); + if(ec == http::error::end_of_stream) + { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + break; + } + if(ec) + return fail(ec, "write"); + } + + // Send a TCP shutdown + socket.shutdown(tcp::socket::shutdown_send, ec); + + // At this point the connection is closed gracefully +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: http-server-sync
\n" << + "Example:\n" << + " http-server-sync 0.0.0.0 8080 .\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + std::string const doc_root = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios{1}; + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ios}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket), + doc_root)}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/example/server-framework/CMakeLists.txt b/example/server-framework/CMakeLists.txt deleted file mode 100644 index 0e1a43d4..00000000 --- a/example/server-framework/CMakeLists.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -GroupSources(include/boost/beast beast) -GroupSources(example/server-framework "/") -GroupSources(example/common "common") - -file(GLOB_RECURSE SERVER_INCLUDES - ${PROJECT_SOURCE_DIR}/example/server-framework/*.hpp - ) - -add_executable (server-framework - ${BOOST_BEAST_INCLUDES} - ${COMMON_INCLUDES} - ${SERVER_INCLUDES} - Jamfile - main.cpp -) - -if (OPENSSL_FOUND) - target_link_libraries(server-framework - ${OPENSSL_LIBRARIES} - ) -endif() diff --git a/example/server-framework/README.md b/example/server-framework/README.md deleted file mode 100644 index ffb5c1bd..00000000 --- a/example/server-framework/README.md +++ /dev/null @@ -1,159 +0,0 @@ -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 - - Plain or SSL (if OpenSSL available) - -* 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 - - Plain or SSL (if OpenSSL available) - -* Multi-Port: Plain, OpenSSL, HTTP, WebSocket **All on the same port!** - -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 a -total of *nine* port handlers! - -| Type | Plain | SSL | -| ----- | ----------------- | ------------------ | -| Sync | `http_sync_port` | `https_sync_port` | -| | `ws_sync_port` | `wss_sync_port` | -| Async | `http_async_port` | `https_async_port` | -| | `wss_sync_port` | `wss_async_port` | -| | `multi_port` | `multi_port` | - - -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. - -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 nine -ports created in the example program, and the HTTP services contained by -the HTTP ports: - -ServerFramework - -## PortHandler Requirements -```C++ -/** 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. -*/ -struct PortHandler -{ - /** Accept a TCP/IP socket. - - 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); -}; -``` - -## Service Requirements - -```C++ -struct Service -{ - /** Initialize the service - - @param ec Set to the error, if any occurred - */ - void - init(error_code& ec); - - /** Maybe respond to an HTTP request - - Upon handling the response, the service may optionally - take ownership of either the stream, the request, or both. - - @param stream The stream representing the connection - - @param ep The remote endpoint of the stream - - @param req The HTTP request - - @param send A function object which operates on a single - argument of type beast::http::message. The function object - has this equivalent signature: - @code - template - void send(beast::http::response&& res); - @endcode - - @return `true` if the service handled the response. - */ - 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 -}; -``` - -## Upgrade Service Requirements - -To work with the `ws_upgrade_service`, a port or handler needs -this signature: -```C++ - -struct UpgradePort -{ - template - void - on_upgrade( - Stream&& stream, - endpoint_type ep, - beast::http::request&& req); - -``` diff --git a/example/server-framework/file_service.hpp b/example/server-framework/file_service.hpp deleted file mode 100644 index d376ead8..00000000 --- a/example/server-framework/file_service.hpp +++ /dev/null @@ -1,281 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_FILE_SERVICE_HPP - -#include "framework.hpp" -#include "../common/mime_types.hpp" - -#include -#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, - boost::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 = {}; - } - - /** Try to handle a file request. - - @param stream The stream belonging to the connection. - Ownership is not transferred. - - @param ep The remote endpoint of the connection - corresponding to the stream. - - @param req The request message to attempt handling. - Ownership is not transferred. - - @param send The function to invoke with the response. - The function will 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 the service. - */ - template< - class Stream, - class Body, class Fields, - class Send> - bool - respond( - Stream&&, - endpoint_type const& ep, - boost::beast::http::request&& req, - Send const& send) const - { - boost::ignore_unused(ep); - - // Determine our action based on the method - switch(req.method()) - { - case boost::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; - - boost::beast::error_code ec; - auto res = get(req, full_path, ec); - - if(ec == boost::beast::errc::no_such_file_or_directory) - { - send(not_found(req, rel_path)); - } - else if(ec) - { - send(server_error(req, rel_path, ec)); - } - else - { - send(std::move(*res)); - } - - // Indicate that we handled the request - return true; - } - - case boost::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; - - boost::beast::error_code ec; - auto res = head(req, full_path, ec); - - if(ec == boost::beast::errc::no_such_file_or_directory) - { - send(not_found(req, rel_path)); - } - else if(ec) - { - send(server_error(req, rel_path, ec)); - } - else - { - send(std::move(*res)); - } - - // 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 an HTTP Not Found response - // - template - boost::beast::http::response - not_found( - boost::beast::http::request const& req, - boost::filesystem::path const& rel_path) const - { - boost::ignore_unused(rel_path); - boost::beast::http::response res; - res.version = req.version; - res.result(boost::beast::http::status::not_found); - res.set(boost::beast::http::field::server, server_); - res.set(boost::beast::http::field::content_type, "text/html"); - res.body = "The file was not found"; // VFALCO append rel_path - res.prepare_payload(); - return res; - } - - // Return an HTTP Server Error - // - template - boost::beast::http::response - server_error( - boost::beast::http::request const& req, - boost::filesystem::path const& rel_path, - error_code const& ec) const - { - boost::ignore_unused(rel_path); - boost::beast::http::response res; - res.version = req.version; - res.result(boost::beast::http::status::internal_server_error); - res.set(boost::beast::http::field::server, server_); - res.set(boost::beast::http::field::content_type, "text/html"); - res.body = "Error: " + ec.message(); - res.prepare_payload(); - return res; - } - - // Return a file response to an HTTP GET request - // - template - boost::optional> - get( - boost::beast::http::request const& req, - boost::filesystem::path const& full_path, - boost::beast::error_code& ec) const - { - boost::beast::http::response res; - res.version = req.version; - res.set(boost::beast::http::field::server, server_); - res.set(boost::beast::http::field::content_type, mime_type(full_path)); - res.body.open(full_path.string().c_str(), boost::beast::file_mode::scan, ec); - if(ec) - return boost::none; - res.set(boost::beast::http::field::content_length, res.body.size()); - return {std::move(res)}; - } - - // Return a response to an HTTP HEAD request - // - template - boost::optional> - head( - boost::beast::http::request const& req, - boost::filesystem::path const& full_path, - boost::beast::error_code& ec) const - { - boost::beast::http::response res; - res.version = req.version; - res.set(boost::beast::http::field::server, server_); - res.set(boost::beast::http::field::content_type, mime_type(full_path)); - - // Use a manual file body here - boost::beast::http::file_body::value_type body; - body.open(full_path.string().c_str(), boost::beast::file_mode::scan, ec); - if(ec) - return boost::none; - res.set(boost::beast::http::field::content_length, body.size()); - return {std::move(res)}; - } -}; - -} // framework - -#endif diff --git a/example/server-framework/framework.hpp b/example/server-framework/framework.hpp deleted file mode 100644 index 853f1972..00000000 --- a/example/server-framework/framework.hpp +++ /dev/null @@ -1,55 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_FRAMEWORK_HPP -#define BOOST_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 deleted file mode 100644 index b11ef03f..00000000 --- a/example/server-framework/http_async_port.hpp +++ /dev/null @@ -1,655 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_ASYNC_PORT_HPP - -#include "server.hpp" - -#include "http_base.hpp" -#include "service_list.hpp" - -#include "../common/rfc7231.hpp" -#include "../common/write_msg.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace framework { - -// Base class for a type-erased, queued asynchronous HTTP write operation -// -struct queued_http_write -{ - // Destructor must be virtual since we delete a - // derived class through a pointer to the base! - // - 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 -{ - // The stream to write to - Stream& stream_; - - // The message to send, which we acquire by move or copy - boost::beast::http::message msg_; - - // The handler to invoke when the send completes. - Handler handler_; - -public: - // Constructor. - // - // Ownership of the message is transferred into the object - // - template - queued_http_write_impl( - Stream& stream, - boost::beast::http::message&& msg, - DeducedHandler&& handler) - : stream_(stream) - , msg_(std::move(msg)) - , handler_(std::forward(handler)) - { - } - - // Writes the stored message. - // - // The caller must make sure this invocation represents - // a continuation of an asynchronous operation which is - // already in the right context. For example, already - // running on the associated strand. - // - 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, - boost::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 derived 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_base : public http_base -{ -protected: - // 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 - boost::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; - - // The strand makes sure that our data is - // accessed from only one thread at a time. - // - strand_type strand_; - -public: - // Constructor - async_http_con_base( - boost::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 to start the object after the listener accepts - // an incoming connection, when no bytes have been read yet. - // - void - run() - { - // Just call run with an empty buffer - run(boost::asio::null_buffers{}); - } - - // Called to start the object after the - // listener accepts an incoming connection. - // - template - void - run(ConstBufferSequence const& buffers) - { - // Copy the data into the buffer for performing - // HTTP reads, so that the bytes get used. - // - buffer_.commit(boost::asio::buffer_copy( - buffer_.prepare(boost::asio::buffer_size(buffers)), - buffers)); - - // Give the derived class a chance to do stuff - // - impl().do_handshake(); - } - -protected: - void - do_run() - { - do_read_header(); - } - - // Called when a failure occurs - // - void - fail(std::string what, error_code ec) - { - // Don't log operation aborted since those happen normally. - // - if(ec && ec != boost::asio::error::operation_aborted) - { - log_ << - "[#" << id_ << " " << ep_ << "] " << - what << ": " << ec.message() << std::endl; - } - } - - // 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(std::piecewise_construct, std::make_tuple(1024 * 1024)); - - // Read just the header - boost::beast::http::async_read_header( - impl().stream(), - buffer_, - *parser_, - strand_.wrap(std::bind( - &async_http_con_base::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_base& self_; - - public: - // capture "this" - explicit - send_lambda(async_http_con_base& self) - : self_(self) - { - } - - // sends a message - template - void - operator()(boost::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) - { - // This happens when the other end closes gracefully - // - if(ec == boost::beast::http::error::end_of_stream) - { - // VFALCO what about the write queue? - return impl().do_shutdown(); - } - - // On failure we just return, the shared_ptr that is bound - // into the completion will go out of scope and eventually - // this 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. - // - boost::beast::http::async_read( - impl().stream(), - buffer_, - *parser_, - strand_.wrap(std::bind( - &async_http_con_base::on_read, - impl().shared_from_this(), - std::placeholders::_1))); - } - - // Called when the message is complete - void - on_read(error_code ec) - { - // Shouldn't be getting end_of_stream here; - // that would mean that we got an incomplete - // message, counting as an error. - // - if(ec) - return fail("on_read", ec); - - // Grab a reference to the request again - auto& req = parser_->get(); - - // Create a variable for our send - // lambda since we use it more than once. - // - send_lambda send{*this}; - - // Give each service a chance to handle the request - // - if(! services_.respond( - std::move(impl().stream()), - ep_, - std::move(req), - send)) - { - // No service handled the request, - // send a Bad Request result to the client. - // - send(this->bad_request(req)); - } - 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_base 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(boost::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_base::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_base::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_); - - // This happens when we send an HTTP message - // whose semantics indicate that the connection - // should be closed afterwards. For example if - // we send a Connection: close. - // - if(ec == boost::beast::http::error::end_of_stream) - return impl().do_shutdown(); - - // 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()); - } -}; - -//------------------------------------------------------------------------------ - -// This class represents an asynchronous HTTP connection which -// uses a plain TCP/IP socket (no encryption) as the stream. -// -template -class async_http_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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> - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member - - // Constructs last, destroys first - // - , public async_http_con_base, Services...> -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - async_http_con( - socket_type&& sock, - Args&&... args) - : base_from_member(std::move(sock)) - , async_http_con_base, Services...>( - std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - socket_type& - stream() - { - return this->member; - } - -private: - // Base class needs to be a friend to call our private members - friend class async_http_con_base, Services...>; - - // This is called by the base before running the main loop. - // - void - do_handshake() - { - // Run the main loop right away - // - this->do_run(); - } - - // This is called when the other end closes the connection gracefully. - // - void - do_shutdown() - { - error_code ec; - stream().shutdown(socket_type::shutdown_both, ec); - - // not_connected happens under normal - // circumstances so don't bother reporting it. - // - if(ec && ec != boost::beast::errc::not_connected) - return this->fail("shutdown", ec); - } -}; - -//------------------------------------------------------------------------------ - -/* 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 an asynchronous 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 - - @param instance The server instance which owns this port - - @param log The stream to use for logging - */ - 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 deleted file mode 100644 index fa2aaa11..00000000 --- a/example/server-framework/http_base.hpp +++ /dev/null @@ -1,79 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_BASE_HPP -#define BOOST_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 -{ - boost::beast::string_view server_name_; - -public: - explicit - http_base(boost::beast::string_view server_name) - : server_name_(server_name) - { - } - -protected: - // Returns a bad request result response - // - template - boost::beast::http::response - bad_request(boost::beast::http::request const& req) const - { - boost::beast::http::response res; - - // Match the version to the request - res.version = req.version; - - res.result(boost::beast::http::status::bad_request); - res.set(boost::beast::http::field::server, server_name_); - res.set(boost::beast::http::field::content_type, "text/html"); - res.body = "Bad request"; - res.prepare_payload(); - return res; - } - - // Returns a 100 Continue result response - // - template - boost::beast::http::response - continue_100(boost::beast::http::request const& req) const - { - boost::beast::http::response res; - - // Match the version to the request - res.version = req.version; - - res.result(boost::beast::http::status::continue_); - res.set(boost::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 deleted file mode 100644 index 79c51926..00000000 --- a/example/server-framework/http_sync_port.hpp +++ /dev/null @@ -1,479 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_HTTP_SYNC_PORT_HPP - -#include "server.hpp" - -#include "http_base.hpp" -#include "service_list.hpp" - -#include "../common/rfc7231.hpp" -#include "../common/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 derived 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_base - : 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 - boost::beast::flat_buffer buffer_; - -public: - /// Constructor - sync_http_con_base( - boost::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) - { - } - - // This is called to start the connection after - // it is accepted. - // - void - run() - { - // Bind a shared pointer into the lambda for the - // thread, so the sync_http_con_base is destroyed after - // the thread function exits. - // - std::thread{ - &sync_http_con_base::do_run, - impl().shared_from_this() - }.detach(); - } - -protected: - // Called when a failure occurs - // - void - fail(std::string what, error_code ec) - { - if(ec) - { - log_ << - "[#" << id_ << " " << ep_ << "] " << - what << ": " << ec.message() << std::endl; - } - } - -private: - // This lambda is passed to the service list to handle - // the case of sending request objects of varying types. - // 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_base& self_; - - // holds the captured error code - error_code& ec_; - - public: - // Constructor - // - // Capture "this" and "ec" - // - send_lambda(sync_http_con_base& 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()( - boost::beast::http::response&& res) const - { - boost::beast::http::serializer sr{res}; - boost::beast::http::write(self_.impl().stream(), sr, ec_); - } - }; - - void - do_run() - { - error_code ec; - - // Give the derived class a chance to do stuff before we - // enter the main loop. This is for SSL connections really. - // - impl().do_handshake(ec); - - if(ec) - return fail("handshake", ec); - - // The main connection loop, we alternate between - // reading a request and sending a response. On - // error we log and return, which destroys the thread - // and the stream (thus closing the connection) - // - for(;;) - { - // 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. - // - boost::beast::http::request_parser parser( - std::piecewise_construct, std::make_tuple(1024* 1024)); - - // Read the header first - boost::beast::http::read_header(impl().stream(), buffer_, parser, ec); - - // This happens when the other end closes gracefully - // - if(ec == boost::beast::http::error::end_of_stream) - { - // Give the derived class a chance to do stuff - impl().do_shutdown(ec); - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - return; - } - - // Any other error and we fail the connection - if(ec) - return fail("read_header", 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)); - - // This happens when we send an HTTP message - // whose semantics indicate that the connection - // should be closed afterwards. For example if - // we send a Connection: close. - // - if(ec == boost::beast::http::error::end_of_stream) - { - // Give the derived class a chance to do stuff - impl().do_shutdown(ec); - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - return; - } - - // Have to check the error every time we call the lambda - // - if(ec) - return fail("write", ec); - } - - // Read the rest of the message, if any. - // - boost::beast::http::read(impl().stream(), buffer_, parser, ec); - - // Shouldn't be getting end_of_stream here; - // that would mean that we got an incomplete - // message, counting as an error. - // - if(ec) - return fail("read", ec); - - // Give each service a chance to handle the request - // - if(! services_.respond( - std::move(impl().stream()), - ep_, - std::move(req), - send)) - { - // No service handled the request, - // send a Bad Request result to the client. - // - send(this->bad_request(req)); - - // This happens when we send an HTTP message - // whose semantics indicate that the connection - // should be closed afterwards. For example if - // we send a Connection: close. - // - if(ec == boost::beast::http::error::end_of_stream) - { - // Give the derived class a chance to do stuff - impl().do_shutdown(ec); - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - return; - } - - // Have to check the error every time we call the lambda - // - if(ec) - return fail("write", ec); - } - else - { - // This happens when we send an HTTP message - // whose semantics indicate that the connection - // should be closed afterwards. For example if - // we send a Connection: close. - // - if(ec == boost::beast::http::error::end_of_stream) - { - // Give the derived class a chance to do stuff - if(ec && ec != boost::beast::errc::not_connected) - return fail("shutdown", ec); - return; - } - - // Have to check the error every time we call the lambda - // - if(ec) - return fail("write", ec); - - // See if the service that handled the - // response took ownership of the stream. - if(! impl().stream().lowest_layer().is_open()) - { - // They took ownership so just return and - // let this sync_http_con_base object get destroyed. - return; - } - } - - // Theres no pipelining possible in a synchronous server - // because we can't do reads and writes at the same time. - } - } -}; - -//------------------------------------------------------------------------------ - -// This class represents a synchronous HTTP connection which -// uses a plain TCP/IP socket (no encryption) as the stream. -// -template -class sync_http_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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> - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member - - // Constructs last, destroys first - // - , public sync_http_con_base, Services...> -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - sync_http_con( - socket_type&& sock, - Args&&... args) - : base_from_member(std::move(sock)) - , sync_http_con_base, Services...>( - std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - socket_type& - stream() - { - return this->member; - } - -private: - // Base class needs to be a friend to call our private members - friend class sync_http_con_base, Services...>; - - // This is called by the base before running the main loop. - // There's nothing to do for a plain connection. - // - void - do_handshake(error_code& ec) - { - // This is required by the specifications for error_code - // - ec = {}; - } - - // This is called when the other end closes the connection gracefully. - // - void - do_shutdown(error_code& ec) - { - stream().shutdown(socket_type::shutdown_both, ec); - } -}; - -//------------------------------------------------------------------------------ - -/* 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 - - @param instance The server instance which owns this port - - @param log The stream to use for logging - */ - 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/https_ports.hpp b/example/server-framework/https_ports.hpp deleted file mode 100644 index 6c6fb004..00000000 --- a/example/server-framework/https_ports.hpp +++ /dev/null @@ -1,428 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_HTTPS_PORTS_HPP - -#include "http_sync_port.hpp" -#include "http_async_port.hpp" - -#include "../common/ssl_stream.hpp" - -#include - -namespace framework { - -//------------------------------------------------------------------------------ - -// This class represents a synchronous HTTP connection which -// uses an OpenSSL socket as the stream. -// -template -class sync_https_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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> - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member> - - // Constructs last, destroys first - // - , public sync_http_con_base, Services...> -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - sync_https_con( - socket_type&& sock, - boost::asio::ssl::context& ctx, - Args&&... args) - : base_from_member>(std::move(sock), ctx) - , sync_http_con_base, Services...>( - std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - ssl_stream& - stream() - { - return this->member; - } - -private: - friend class sync_http_con_base, Services...>; - - // This is called by the base before running the main loop. - // - void - do_handshake(error_code& ec) - { - // Perform the SSL handshake - // - stream().handshake(boost::asio::ssl::stream_base::server, ec); - } - - // This is called when the other end closes the connection gracefully. - // - void - do_shutdown(error_code& ec) - { - // Note that this is an SSL shutdown - // - stream().shutdown(ec); - if(ec) - return this->fail("ssl_shutdown", ec); - } -}; - -//------------------------------------------------------------------------------ - -// This class represents an asynchronous HTTP connection which -// uses an OpenSSL socket as the stream. -// -template -class async_https_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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> - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member> - - // Constructs last, destroys first - // - , public async_http_con_base, Services...> -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - async_https_con( - socket_type&& sock, - boost::asio::ssl::context& ctx, - Args&&... args) - : base_from_member>(std::move(sock), ctx) - , async_http_con_base, Services...>( - std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - ssl_stream& - stream() - { - return this->member; - } - - // Called by the multi-port after reading some - // bytes from the stream and detecting SSL. - // - template - void - handshake(ConstBufferSequence const& buffers) - { - // Copy the caller's bytes into the buffer we - // use for reading HTTP messages, otherwise - // the memory pointed to by buffers will go out - // of scope. - // - this->buffer_.commit( - boost::asio::buffer_copy( - this->buffer_.prepare(boost::asio::buffer_size(buffers)), - buffers)); - - // Perform SSL handshake. We use the "buffered" - // overload which lets us pass those extra bytes. - // - stream().async_handshake( - boost::asio::ssl::stream_base::server, - buffers, - this->strand_.wrap( - std::bind( - &async_https_con::on_buffered_handshake, - this->shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); - } - -private: - friend class async_http_con_base, Services...>; - - // Called by the base class before starting the main loop. - // - void - do_handshake() - { - // This is SSL so perform the handshake - // - stream().async_handshake( - boost::asio::ssl::stream_base::server, - this->strand_.wrap( - std::bind( - &async_https_con::on_handshake, - this->shared_from_this(), - std::placeholders::_1))); - } - - // Called when the SSL handshake completes - void - on_handshake(error_code ec) - { - if(ec) - return this->fail("on_handshake", ec); - - // No error so run the main loop - this->do_run(); - } - - // Called when the buffered SSL handshake completes - void - on_buffered_handshake(error_code ec, std::size_t bytes_transferred) - { - if(ec) - return this->fail("on_handshake", ec); - - // Consume what was read but leave the rest - this->buffer_.consume(bytes_transferred); - - // No error so run the main loop - this->do_run(); - } - - // Called when the end of stream is reached - void - do_shutdown() - { - // This is an SSL shutdown - // - stream().async_shutdown( - this->strand_.wrap( - std::bind( - &async_https_con::on_shutdown, - this->shared_from_this(), - std::placeholders::_1))); - } - - // Called when the SSL shutdown completes - void - on_shutdown(error_code ec) - { - if(ec) - return this->fail("on_shutdown", ec); - } -}; - -//------------------------------------------------------------------------------ - -/* A synchronous HTTPS port handler - - This type meets the requirements of @b PortHandler. It supports - variable list of HTTP services in its template parameter list, - and provides a synchronous connection implementation to service -*/ -template -class https_sync_port -{ - // Reference to the server instance that made us - server& instance_; - - // The stream to log to - std::ostream& log_; - - // The list of services connections created from this port will support - service_list services_; - - // The SSL context containing the server's credentials - boost::asio::ssl::context& ctx_; - -public: - /** Constructor - - @param instance The server instance which owns this port - - @param log The stream to use for logging - - @param ctx The SSL context holding the SSL certificates to use - */ - https_sync_port( - server& instance, - std::ostream& log, - boost::asio::ssl::context& ctx) - : instance_(instance) - , log_(log) - , ctx_(ctx) - { - } - - /** Initialize a service - - Every service in the list must be initialized exactly once. - - @param args Optional arguments forwarded to the service - constructor. - - @tparam Index The 0-based index of the service to initialize. - - @return A reference to the service list. This permits - calls to be chained in a single expression. - */ - template - 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 an HTTPS connection object - // and transfer ownership of the socket. - // - std::make_shared>( - std::move(sock), - ctx_, - "https_sync_port", - log_, - services_, - instance_.next_id(), - ep)->run(); - } -}; - -//------------------------------------------------------------------------------ - -/* An asynchronous HTTPS port handler - - This type meets the requirements of @b PortHandler. It supports - variable list of HTTP services in its template parameter list, - and provides a synchronous connection implementation to service -*/ -template -class https_async_port -{ - // Reference to the server instance that made us - server& instance_; - - // The stream to log to - std::ostream& log_; - - // The list of services connections created from this port will support - service_list services_; - - // The SSL context containing the server's credentials - boost::asio::ssl::context& ctx_; - -public: - /** Constructor - - @param instance The server instance which owns this port - - @param log The stream to use for logging - */ - https_async_port( - server& instance, - std::ostream& log, - boost::asio::ssl::context& ctx) - : instance_(instance) - , log_(log) - , ctx_(ctx) - { - } - - /** Initialize a service - - Every service in the list must be initialized exactly once. - - @param args Optional arguments forwarded to the service - constructor. - - @tparam Index The 0-based index of the service to initialize. - - @return A reference to the service list. This permits - calls to be chained in a single expression. - */ - template - 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 an SSL connection object - // and transfer ownership of the socket. - // - std::make_shared>( - std::move(sock), - ctx_, - "https_async_port", - log_, - services_, - instance_.next_id(), - ep)->run(); - } -}; - -} // framework - -#endif diff --git a/example/server-framework/main.cpp b/example/server-framework/main.cpp deleted file mode 100644 index bdbfabec..00000000 --- a/example/server-framework/main.cpp +++ /dev/null @@ -1,431 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "server.hpp" - -#include "http_async_port.hpp" -#include "http_sync_port.hpp" -#include "ws_async_port.hpp" -#include "ws_sync_port.hpp" - -#if BOOST_BEAST_USE_OPENSSL -#include "https_ports.hpp" -#include "multi_port.hpp" -#include "wss_ports.hpp" -#include "ssl_certificate.hpp" -#endif - -#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 -{ - boost::beast::websocket::permessage_deflate pmd_; - -public: - set_ws_options(boost::beast::websocket::permessage_deflate const& pmd) - : pmd_(pmd) - { - } - - template - void - operator()(boost::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[]) -{ - using namespace framework; - using namespace boost::beast::http; - - // Helper for reporting failures - // - auto const fail = - [&]( - std::string const& what, - error_code const& ec) - { - std::cerr << - av[0] << ": " << - what << " failed, " << - ec.message() << - std::endl; - return EXIT_FAILURE; - }; - - // Check command line arguments. - if(ac != 5) - { - std::cerr << - "Usage: " << av[0] << - "
"; - return EXIT_FAILURE; - } - - auto const addr = boost::asio::ip::address::from_string(av[1]); - auto const port = static_cast(std::atoi(av[2])); - auto const threads = static_cast(std::atoi(av[3])); - auto const root = std::string(av[4]); - - // These settings will be applied to all new websocket connections - boost::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}; - - //-------------------------------------------------------------------------- - // - // Synchronous WebSocket HTTP - // - // port + 0 port + 1 - // - //-------------------------------------------------------------------------- - { - // Create a WebSocket port - // - auto wsp = instance.make_port( - ec, - endpoint_type{addr,static_cast(port + 0)}, - instance, - std::cout, - set_ws_options{pmd}); - - if(ec) - return fail("ws_sync_port", ec); - - // Create an HTTP port - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr,static_cast(port + 1)}, - instance, - std::cout); - - if(ec) - return fail("http_sync_port", ec); - - // Init the ws_upgrade_service to - // forward upgrades to the WebSocket port. - // - sp->template init<0>( - ec, - *wsp // The WebSocket port handler - ); - - if(ec) - return fail("http_sync_port/ws_upgrade_service", ec); - - // Init the file_service to point to the root path. - // - sp->template init<1>( - ec, - root, // The root path - "http_sync_port" // The value for the Server field - ); - - if(ec) - return fail("http_sync_port/file_service", ec); - } - - //-------------------------------------------------------------------------- - // - // Asynchronous WebSocket HTTP - // - // port + 2 port + 3 - // - //-------------------------------------------------------------------------- - { - // Create a WebSocket port - // - 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_async_port", ec); - - // Create an HTTP port - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr, - static_cast(port + 3)}, - instance, - std::cout); - - if(ec) - return fail("http_async_port", ec); - - // Init the ws_upgrade_service to - // forward upgrades to the WebSocket port. - // - sp->template init<0>( - ec, - *wsp // The websocket port handler - ); - - if(ec) - return fail("http_async_port/ws_upgrade_service", ec); - - // Init 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); - } - - // - // The next section supports encrypted connections and requires - // an installed and configured OpenSSL as part of the build. - // - -#if BOOST_BEAST_USE_OPENSSL - - ssl_certificate cert; - - //-------------------------------------------------------------------------- - // - // Synchronous Secure WebSocket HTTPS - // - // port + 4 port + 5 - // - //-------------------------------------------------------------------------- - { - // Create a WebSocket port - // - auto wsp = instance.make_port( - ec, - endpoint_type{addr, - static_cast(port + 4)}, - instance, - std::cout, - cert.get(), - set_ws_options{pmd}); - - if(ec) - return fail("wss_sync_port", ec); - - // Create an HTTP port - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr, - static_cast(port + 5)}, - instance, - std::cout, - cert.get()); - - if(ec) - return fail("https_sync_port", ec); - - // Init the ws_upgrade_service to - // forward upgrades to the WebSocket port. - // - sp->template init<0>( - ec, - *wsp // The websocket port handler - ); - - if(ec) - return fail("http_sync_port/ws_upgrade_service", ec); - - // Init the file_service to point to the root path. - // - sp->template init<1>( - ec, - root, // The root path - "http_sync_port" // The value for the Server field - ); - - if(ec) - return fail("https_sync_port/file_service", ec); - } - - //-------------------------------------------------------------------------- - // - // Asynchronous Secure WebSocket HTTPS - // - // port + 6 port + 7 - // - //-------------------------------------------------------------------------- - { - // Create a WebSocket port - // - auto wsp = instance.make_port( - ec, - endpoint_type{addr, - static_cast(port + 6)}, - instance, - std::cout, - cert.get(), - set_ws_options{pmd} - ); - - if(ec) - return fail("ws_async_port", ec); - - // Create an HTTP port - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr, - static_cast(port + 7)}, - instance, - std::cout, - cert.get()); - - if(ec) - return fail("https_async_port", ec); - - // Init the ws_upgrade_service to - // forward upgrades to the WebSocket port. - // - sp->template init<0>( - ec, - *wsp // The websocket port handler - ); - - if(ec) - return fail("https_async_port/ws_upgrade_service", ec); - - // Init the file_service to point to the root path. - // - sp->template init<1>( - ec, - root, // The root path - "https_async_port" // The value for the Server field - ); - - if(ec) - return fail("https_async_port/file_service", ec); - } - - //-------------------------------------------------------------------------- - // - // Multi-Port HTTP, WebSockets, - // HTTPS Secure WebSockets - // - // Asynchronous, all on the same port! - // - // port + 8 - // - //-------------------------------------------------------------------------- - { - // Create a multi_port - // - auto sp = instance.make_port, - file_service - >>( - ec, - endpoint_type{addr, - static_cast(port + 8)}, - instance, - std::cout, - cert.get(), - set_ws_options{pmd}); - - if(ec) - return fail("multi_port", ec); - - // Init the ws_upgrade_service to forward requests to the multi_port. - // - sp->template init<0>( - ec, - *sp // The websocket port handler - ); - - if(ec) - return fail("multi_port/ws_upgrade_service", ec); - - // Init the ws_upgrade_service to - // forward upgrades to the Multi port. - // - sp->template init<1>( - ec, - root, // The root path - "multi_port" // The value for the Server field - ); - - if(ec) - return fail("multi_port/file_service", ec); - } - -#endif - - sig_wait(); -} diff --git a/example/server-framework/multi_port.hpp b/example/server-framework/multi_port.hpp deleted file mode 100644 index 6913b0cd..00000000 --- a/example/server-framework/multi_port.hpp +++ /dev/null @@ -1,399 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_MULTI_PORT_HPP - -#include "ws_async_port.hpp" -#include "http_async_port.hpp" -#include "https_ports.hpp" -#include "wss_ports.hpp" - -#include "../common/detect_ssl.hpp" - -#include - -#include - -namespace framework { - -// A connection that detects an opening SSL handshake -// -// If the SSL handshake is detected, then an HTTPS connection object -// is move constructed from this object. Otherwise, this object continues -// as a normal unencrypted HTTP connection. If the underlying port has -// the ws_upgrade_service configured, the connection may be optionally -// be upgraded to WebSocket by the client. -// -template -class multi_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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> - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member - - // Constructs last, destroys first - // - , public async_http_con_base, Services...> -{ - // Context to use if we get an SSL handshake - boost::asio::ssl::context& ctx_; - - // Holds the data we read during ssl detection - boost::beast::flat_static_buffer<6> buffer_; - -public: - // Constructor - // - // Additional arguments are simply forwarded to the base class - // - template - multi_con( - socket_type&& sock, - boost::asio::ssl::context& ctx, - Args&&... args) - : base_from_member(std::move(sock)) - , async_http_con_base, Services...>(std::forward(args)...) - , ctx_(ctx) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - socket_type& - stream() - { - return this->member; - } - - // Called by the port to launch the connection in detect mode - void - detect() - { - // The detect function operates asynchronously by reading - // in some data from the stream to figure out if its an SSL - // handshake. When it completes, it informs us of the result - // and also stores the bytes it read in the buffer. - // - async_detect_ssl( - stream(), - buffer_, - this->strand_.wrap( - std::bind( - &multi_con::on_detect, - this->shared_from_this(), - std::placeholders::_1, - std::placeholders::_2))); - } - -private: - // Base class needs to be a friend to call our private members - friend class async_http_con_base, Services...>; - - // Called when the handshake detection is complete - // - void - on_detect( - error_code ec, - boost::tribool result) - { - // Report failures if any - if(ec) - return this->fail("on_detect", ec); - - // Was an SSL handshake detected? - if(result) - { - // Yes, get the remote endpoint since it is - // needed to construct the new connection. - // - endpoint_type ep = stream().remote_endpoint(ec); - if(ec) - return this->fail("remote_endpoint", ec); - - // Now launch our new connection object - // - std::make_shared>( - std::move(stream()), - ctx_, - "multi_port", - this->log_, - this->services_, - this->id_, - ep)->handshake(buffer_.data()); - - // When we return the last shared pointer to this - // object will go away and `*this` will be destroyed. - // - return; - } - - // No SSL handshake, so start the HTTP connection normally. - // - // Since we read some bytes from the connection that might - // contain an HTTP request, we pass the buffer holding those - // bytes to the base class so it can use them. - // - this->run(buffer_.data()); - } - - // This is called by the base before running the main loop. - // - void - do_handshake() - { - // Just run the main loop right away. - // - this->do_run(); - } - - // This is called when the other end closes the connection gracefully. - // - void - do_shutdown() - { - // Attempt a clean TCP/IP shutdown - // - error_code ec; - stream().shutdown( - socket_type::shutdown_both, - ec); - - // not_connected happens under normal - // circumstances so don't bother reporting it. - // - if(ec && ec != boost::beast::errc::not_connected) - return this->fail("shutdown", ec); - } -}; - -//------------------------------------------------------------------------------ - -/* An asynchronous HTTP and WebSocket port handler, plain or SSL - - This type meets the requirements of @b PortHandler. It supports a - variable list of HTTP services in its template parameter list, - and provides a synchronous connection implementation to service. - - The port will automatically detect OpenSSL handshakes and establish - encrypted connections, otherwise will use a plain unencrypted - connection. This all happens through the same port. - - In addition this port can process WebSocket upgrade requests by - launching them as a new asynchronous WebSocket connection using - either plain or OpenSSL transport. - - This class is split up into two parts, the multi_port_base, - and the multi_port, to avoid a recursive type reference when - we name the type of the ws_upgrade_service. -*/ -class multi_port_base -{ -protected: - // VFALCO We use boost::function to work around a compiler - // crash with gcc and clang using libstdc++ - - // The types of the on_stream callback - using on_new_stream_cb1 = boost::function&)>; - using on_new_stream_cb2 = boost::function>&)>; - - // Reference to the server instance that made us - server& instance_; - - // The stream to log to - std::ostream& log_; - - // The context holds the SSL certificates the server uses - boost::asio::ssl::context& ctx_; - - // Called for each new websocket stream - on_new_stream_cb1 cb1_; - on_new_stream_cb2 cb2_; - -public: - /** Constructor - - @param instance The server instance which owns this port - - @param log The stream to use for logging - - @param ctx The SSL context holding the SSL certificates to use - - @param cb A callback which will be invoked for every new - WebSocket connection. This provides an opportunity to change - the settings on the stream before it is used. The callback - should have this equivalent signature: - @code - template - void callback(boost::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 - multi_port_base( - server& instance, - std::ostream& log, - boost::asio::ssl::context& ctx, - Callback const& cb) - : instance_(instance) - , log_(log) - , ctx_(ctx) - , cb1_(cb) - , cb2_(cb) - { - } - - /** Accept a 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 - on_upgrade( - socket_type&& sock, - endpoint_type ep, - boost::beast::http::request&& req) - { - // Create the connection and call the version of - // run that takes the request since we have it already - // - std::make_shared( - std::move(sock), - "multi_port", - log_, - instance_.next_id(), - ep, - cb1_ - )->run(std::move(req)); - } - - /** 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 - on_upgrade( - ssl_stream&& stream, - endpoint_type ep, - boost::beast::http::request&& req) - { - std::make_shared( - std::move(stream), - "multi_port", - log_, - instance_.next_id(), - ep, - cb2_)->run(std::move(req)); - } -}; - -/* An asynchronous HTTP and WebSocket port handler, plain or SSL - - This class is the other half of multi_port_base. It gets the - Services... variadic type list and owns the service list. -*/ -template -class multi_port : public multi_port_base -{ - // The list of services connections created from this port will support - service_list services_; - -public: - /** Constructor - - All arguments are forwarded to the multi_port_base constructor. - */ - template - multi_port(Args&&... args) - : multi_port_base(std::forward(args)...) - { - } - - /** 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 by transferring - // ownership of the socket, then launch it to perform - // the SSL handshake detection. - // - std::make_shared>( - std::move(sock), - ctx_, - "multi_port", - log_, - services_, - instance_.next_id(), - ep)->detect(); - } -}; - -} // framework - -#endif diff --git a/example/server-framework/server.hpp b/example/server-framework/server.hpp deleted file mode 100644 index 7fa222c9..00000000 --- a/example/server-framework/server.hpp +++ /dev/null @@ -1,268 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_FRAMEWORK_SERVER_HPP -#define BOOST_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); -}; - -//------------------------------------------------------------------------------ - -/* This implementation class wraps the PortHandler and - manages the listening socket. Upon an incoming connection - it transfers ownership of the socket to the PortHandler. -*/ -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) - { - // Transfer ownership of the socket to the PortHandler - // - 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 deleted file mode 100644 index 1cbcc423..00000000 --- a/example/server-framework/service_list.hpp +++ /dev/null @@ -1,194 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_SERVICE_LIST_HPP -#define BOOST_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. - - @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 ownership 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 Send> - bool - respond( - Stream&& stream, - endpoint_type const& ep, - boost::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 Send> - bool - try_respond( - Stream&&, - endpoint_type const&, - boost::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 Send, - std::size_t I> - bool - try_respond( - Stream&& stream, - endpoint_type const& ep, - boost::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/ws_async_port.hpp b/example/server-framework/ws_async_port.hpp deleted file mode 100644 index 5bf54078..00000000 --- a/example/server-framework/ws_async_port.hpp +++ /dev/null @@ -1,376 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_ASYNC_PORT_HPP -#define BOOST_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. -// -// -template -class async_ws_con_base -{ - // This function lets us access members of the derived class - 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 - boost::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_base( - boost::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().stream().get_io_service()) - { - cb(impl().stream()); - } - - // Run the connection - // - void - run() - { - impl().do_handshake(); - } - - // Run the connection. - // - // This overload handles the case where we - // already have the WebSocket Upgrade request. - // - template - void - run(boost::beast::http::request const& req) - { - // Call the overload of accept() which takes - // the request by parameter, instead of reading - // it from the network. - // - impl().stream().async_accept_ex(req, - [&](boost::beast::websocket::response_type& res) - { - res.set(boost::beast::http::field::server, server_name_); - }, - strand_.wrap(std::bind( - &async_ws_con_base::on_accept, - impl().shared_from_this(), - std::placeholders::_1))); - } - -protected: - // Performs the WebSocket handshake - void - do_accept() - { - // Read the WebSocket upgrade request and attempt - // to send back the response. - // - impl().stream().async_accept_ex( - [&](boost::beast::websocket::response_type& res) - { - res.set(boost::beast::http::field::server, server_name_); - }, - strand_.wrap(std::bind( - &async_ws_con_base::on_accept, - impl().shared_from_this(), - std::placeholders::_1))); - } - - // This helper reports failures - // - void - fail(std::string what, error_code ec) - { - if(ec != boost::beast::websocket::error::closed) - log_ << - "[#" << id_ << " " << ep_ << "] " << - what << ": " << ec.message() << std::endl; - } - -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().stream().async_read( - buffer_, - strand_.wrap(std::bind( - &async_ws_con_base::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().stream().binary(impl().stream().got_binary()); - - // Now echo back the message - // - impl().stream().async_write( - buffer_.data(), - strand_.wrap(std::bind( - &async_ws_con_base::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 class represents an asynchronous WebSocket connection -// which uses a plain TCP/IP socket (no encryption) as the stream. -// -class async_ws_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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 - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member> - - // Constructs last, destroys first - // - , public async_ws_con_base -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - explicit - async_ws_con( - socket_type&& sock, - Args&&... args) - : base_from_member>(std::move(sock)) - , async_ws_con_base(std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - boost::beast::websocket::stream& - stream() - { - return this->member; - } - -private: - // Base class needs to be a friend to call our private members - friend async_ws_con_base; - - void - do_handshake() - { - do_accept(); - } -}; - -//------------------------------------------------------------------------------ - -/** An asynchronous WebSocket @b PortHandler which implements echo. - - This is a port handler which accepts WebSocket upgrade HTTP - requests and implements the echo protocol. All received - WebSocket messages will be echoed back to the remote host. -*/ -class ws_async_port -{ - // The type of the on_new_stream callback - // - using on_new_stream_cb = - boost::function&)>; - - 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(boost::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 connection. - - This function is called when the server has accepted an - incoming connection. - - @param sock The connected socket. - - @param ep The endpoint of the remote host. - */ - void - on_accept( - socket_type&& sock, - endpoint_type ep) - { - std::make_shared( - std::move(sock), - "ws_async_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 - on_upgrade( - socket_type&& sock, - endpoint_type ep, - boost::beast::http::request&& req) - { - std::make_shared( - std::move(sock), - "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 deleted file mode 100644 index 4a4606db..00000000 --- a/example/server-framework/ws_sync_port.hpp +++ /dev/null @@ -1,434 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_WS_SYNC_PORT_HPP - -#include "server.hpp" - -#include -#include -#include -#include -#include -#include - -namespace framework { - -/** A synchronous WebSocket connection. - - This base class implements a WebSocket connection object using - synchronous calls over an unencrypted connection. - - It uses the Curiously Recurring Template pattern (CRTP) where - we refer to the derived 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. -*/ -template -class sync_ws_con_base -{ - // This function lets us access members of the derived class - 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 - // - // Additional arguments are forwarded to the base class - // - template - sync_ws_con_base( - boost::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().stream()); - } - - // Run the connection. This is called for the case - // where we have not received the upgrade request yet. - // - void - run() - { - // We run the do_run function in its own thread, - // and bind a shared pointer to the connection object - // into the function. The last reference to the shared - // pointer will go away when the thread exits, thus - // destroying the connection object. - // - std::thread{ - &sync_ws_con_base::do_accept, - impl().shared_from_this() - }.detach(); - } - - // Run the connection from an already-received Upgrade request. - // - template - void - run(boost::beast::http::request&& req) - { - BOOST_ASSERT(boost::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(); - } - -protected: - // Called when a failure occurs - // - void - fail(std::string what, error_code ec) - { - // Don't report the "closed" error since that - // happens under normal circumstances. - // - if(ec && ec != boost::beast::websocket::error::closed) - { - log_ << - "[#" << id_ << " " << ep_ << "] " << - what << ": " << ec.message() << std::endl; - log_.flush(); - } - } - -private: - // This function performs the WebSocket handshake - // and runs the main loop upon success. - void - do_accept() - { - error_code ec; - - // Give the derived class a chance to do stuff before we - // enter the main loop. This is for SSL connections really. - // - impl().do_handshake(ec); - - if(ec) - return fail("handshake", ec); - - // Read the WebSocket upgrade request and attempt - // to send back the response. - // - impl().stream().accept_ex( - [&](boost::beast::websocket::response_type& res) - { - res.insert(boost::beast::http::field::server, server_name_); - }, - ec); - - if(ec) - return fail("accept", ec); - - // Run the connection - // - do_run(); - } - - // This is the lambda used when launching a connection from - // an already-received request. In C++14 we could simply use - // a lambda capture but this example requires only C++11 so - // we write out the lambda ourselves. This is similar to what - // the compiler would generate anyway. - // - template - class lambda - { - std::shared_ptr self_; - boost::beast::http::request req_; - - public: - // Constructor - // - // This is the equivalent of the capture section of the lambda. - // - lambda( - std::shared_ptr self, - boost::beast::http::request&& req) - : self_(std::move(self)) - , req_(std::move(req)) - { - BOOST_ASSERT(boost::beast::websocket::is_upgrade(req_)); - } - - // Invoke the lambda - // - void - operator()() - { - BOOST_ASSERT(boost::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().stream().accept_ex(req, - [&](boost::beast::websocket::response_type& res) - { - res.insert(boost::beast::http::field::server, self_->server_name_); - }, - ec); - } - - if(ec) - return self_->fail("accept", ec); - - self_->do_run(); - } - }; - - void - do_run() - { - error_code ec; - - // 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. - // - boost::beast::multi_buffer buffer{1024*1024}; - - // Read the message - // - impl().stream().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().stream().binary(impl().stream().got_binary()); - - // Now echo back the message - // - impl().stream().write(buffer.data(), ec); - - if(ec) - return fail("write", ec); - } - } -}; - -//------------------------------------------------------------------------------ - -// This class represents a synchronous WebSocket connection -// which uses a plain TCP/IP socket (no encryption) as the stream. -// -class sync_ws_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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 - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member> - - // Constructs last, destroys first - // - , public sync_ws_con_base -{ -public: - // Construct the plain connection. - // - template - explicit - sync_ws_con( - socket_type&& sock, - Args&&... args) - : base_from_member>(std::move(sock)) - , sync_ws_con_base(std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - boost::beast::websocket::stream& - stream() - { - return this->member; - } - -private: - // Base class needs to be a friend to call our private members - friend class sync_ws_con_base; - - // This is called by the base before running the main loop. - // There's nothing to do for a plain connection. - // - void - do_handshake(error_code& ec) - { - // This is required by the specifications for error_code - // - ec = {}; - } -}; - -//------------------------------------------------------------------------------ - -/** 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_new_stream callback - // - using on_new_stream_cb = - boost::function&)>; - - 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(boost::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 - on_upgrade( - socket_type&& sock, - endpoint_type ep, - boost::beast::http::request&& req) - { - // Create the connection object and run it, - // transferring ownership 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 deleted file mode 100644 index 28b4e1d5..00000000 --- a/example/server-framework/ws_upgrade_service.hpp +++ /dev/null @@ -1,103 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_WS_UPGRADE_SERVICE_HPP -#define BOOST_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 -{ - PortHandler& handler_; - -public: - /** Constructor - - @param handler A shared pointer to the @b PortHandler to - handle WebSocket upgrade requests. - */ - explicit - ws_upgrade_service(PortHandler& handler) - : handler_(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 Send> - bool - respond( - Stream&& stream, - endpoint_type const& ep, - boost::beast::http::request&& req, - Send const&) const - { - // If its not an upgrade request, return `false` - // to indicate that we are not handling it. - // - if(! boost::beast::websocket::is_upgrade(req)) - return false; - - // Its an ugprade request, so transfer ownership - // of the stream and request to the port handler. - // - handler_.on_upgrade( - std::move(stream), - ep, - std::move(req)); - - // Tell the service list that we handled the request. - // - return true; - } -}; - -} // framework - -#endif diff --git a/example/server-framework/wss_ports.hpp b/example/server-framework/wss_ports.hpp deleted file mode 100644 index 7214f3df..00000000 --- a/example/server-framework/wss_ports.hpp +++ /dev/null @@ -1,440 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#ifndef BOOST_BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP -#define BOOST_BEAST_EXAMPLE_SERVER_WSS_PORTS_HPP - -#include "ws_sync_port.hpp" -#include "ws_async_port.hpp" - -#include "../common/ssl_stream.hpp" - -#include -#include - -namespace framework { - -//------------------------------------------------------------------------------ - -// A synchronous WebSocket connection over an SSL connection -// -class sync_wss_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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 - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member>> - - // Constructs last, destroys first - // - , public sync_ws_con_base -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - explicit - sync_wss_con( - socket_type&& sock, - boost::asio::ssl::context& ctx, - Args&&... args) - : base_from_member>>(std::move(sock), ctx) - , sync_ws_con_base(std::forward(args)...) - { - } - - // Construct from an existing, handshaked SSL stream - // - template - sync_wss_con( - ssl_stream&& stream, - Args&&... args) - : base_from_member>>(std::move(stream)) - , sync_ws_con_base(std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - boost::beast::websocket::stream>& - stream() - { - return this->member; - } - -private: - friend class sync_ws_con_base; - - // This is called by the base before running the main loop. - // - void - do_handshake(error_code& ec) - { - // Perform the SSL handshake - // - // We use next_layer() to get at the underlying ssl_stream - // - stream().next_layer().handshake(boost::asio::ssl::stream_base::server, ec); - } -}; - -//------------------------------------------------------------------------------ - -// An asynchronous WebSocket connection over an SSL connection -// -class async_wss_con - - // Give this object the enable_shared_from_this, and have - // the base class call impl().shared_from_this(). The reason - // 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 - - // The stream should be created before the base class so - // use the "base from member" idiom. - // - , public base_from_member>> - - // Constructs last, destroys first - // - , public async_ws_con_base -{ -public: - // Constructor - // - // Additional arguments are forwarded to the base class - // - template - async_wss_con( - socket_type&& sock, - boost::asio::ssl::context& ctx, - Args&&... args) - : base_from_member>>(std::move(sock), ctx) - , async_ws_con_base(std::forward(args)...) - { - } - - // Construct from an existing, handshaked SSL stream - // - template - async_wss_con( - ssl_stream&& stream, - Args&&... args) - : base_from_member>>(std::move(stream)) - , async_ws_con_base(std::forward(args)...) - { - } - - // Returns the stream. - // - // The base class calls this to obtain the object to use for - // reading and writing HTTP messages. This allows the same base - // class to work with different return types for `stream()` such - // as a `boost::asio::ip::tcp::socket&` or a `boost::asio::ssl::stream&` - // - boost::beast::websocket::stream>& - stream() - { - return this->member; - } - -private: - friend class async_ws_con_base; - - // Called by the port to start the connection - // after creating the object - // - void - do_handshake() - { - // This is SSL so perform the handshake first - // - stream().next_layer().async_handshake( - boost::asio::ssl::stream_base::server, - this->strand_.wrap( - std::bind( - &async_wss_con::on_handshake, - this->shared_from_this(), - std::placeholders::_1))); - } - - // Called when the SSL handshake completes - // - void - on_handshake(error_code ec) - { - if(ec) - return this->fail("on_handshake", ec); - - // Move on to accepting the WebSocket handshake - // - this->do_accept(); - } -}; - -//------------------------------------------------------------------------------ - -/** A synchronous Secure WebSocket @b PortHandler which implements echo. - - This is a port handler which accepts Secure WebSocket upgrade - HTTP requests and implements the echo protocol. All received - WebSocket messages will be echoed back to the remote host. -*/ -class wss_sync_port -{ - // VFALCO We use boost::function to work around a compiler - // crash with gcc and clang using libstdc++ - - // The types of the on_new_stream callbacks - // - using on_new_stream_cb1 = - boost::function&)>; - - using on_new_stream_cb2 = - boost::function>&)>; - - server& instance_; - std::ostream& log_; - boost::asio::ssl::context& ctx_; - on_new_stream_cb1 cb1_; - on_new_stream_cb2 cb2_; - -public: - /** Constructor - - @param instance The server instance which owns this port - - @param log The stream to use for logging - - @param ctx The SSL context holding the SSL certificates to use - - @param cb A callback which will be invoked for every new - WebSocket connection. This provides an opportunity to change - the settings on the stream before it is used. The callback - should have this equivalent signature: - @code - template - void callback(boost::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 - wss_sync_port( - server& instance, - std::ostream& log, - boost::asio::ssl::context& ctx, - Callback const& cb) - : instance_(instance) - , log_(log) - , ctx_(ctx) - , cb1_(cb) - , cb2_(cb) - { - } - - /** Accept a TCP/IP connection. - - This function is called when the server has accepted an - incoming connection. - - @param sock The connected socket. - - @param ep The endpoint of the remote host. - */ - void - on_accept(socket_type&& sock, endpoint_type ep) - { - // Create our connection object and run it - // - std::make_shared( - std::move(sock), - ctx_, - "wss_sync_port", - log_, - instance_.next_id(), - ep, - cb2_)->run(); - } - - /** Accept a WebSocket upgrade request. - - This is used to accept a connection that has already - delivered the handshake. - - @param stream The stream corresponding to the connection. - - @param ep The remote endpoint. - - @param req The upgrade request. - */ - template - void - on_upgrade( - ssl_stream&& stream, - endpoint_type ep, - boost::beast::http::request&& req) - { - // Create the connection object and run it, - // transferring ownership of the ugprade request. - // - std::make_shared( - std::move(stream), - "wss_sync_port", - log_, - instance_.next_id(), - ep, - cb2_)->run(std::move(req)); - } -}; - -//------------------------------------------------------------------------------ - -/** An asynchronous WebSocket @b PortHandler which implements echo. - - This is a port handler which accepts WebSocket upgrade HTTP - requests and implements the echo protocol. All received - WebSocket messages will be echoed back to the remote host. -*/ -class wss_async_port -{ - // VFALCO We use boost::function to work around a compiler - // crash with gcc and clang using libstdc++ - - // The types of the on_new_stream callbacks - // - using on_new_stream_cb1 = - boost::function&)>; - - using on_new_stream_cb2 = - boost::function>&)>; - - // Reference to the server instance that made us - server& instance_; - - // The stream to log to - std::ostream& log_; - - // The context holds the SSL certificates the server uses - boost::asio::ssl::context& ctx_; - - // Called for each new websocket stream - on_new_stream_cb1 cb1_; - on_new_stream_cb2 cb2_; - -public: - /** Constructor - - @param instance The server instance which owns this port - - @param log The stream to use for logging - - @param ctx The SSL context holding the SSL certificates to use - - @param cb A callback which will be invoked for every new - WebSocket connection. This provides an opportunity to change - the settings on the stream before it is used. The callback - should have this equivalent signature: - @code - template - void callback(boost::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 - wss_async_port( - server& instance, - std::ostream& log, - boost::asio::ssl::context& ctx, - Callback const& cb) - : instance_(instance) - , log_(log) - , ctx_(ctx) - , cb1_(cb) - , cb2_(cb) - { - } - - /** Accept a TCP/IP connection. - - This function is called when the server has accepted an - incoming connection. - - @param sock The connected socket. - - @param ep The endpoint of the remote host. - */ - void - on_accept( - socket_type&& sock, - endpoint_type ep) - { - std::make_shared( - std::move(sock), - ctx_, - "wss_async_port", - log_, - instance_.next_id(), - ep, - cb2_)->run(); - } - - /** Accept a WebSocket upgrade request. - - This is used to accept a connection that has already - delivered the handshake. - - @param stream The stream corresponding to the connection. - - @param ep The remote endpoint. - - @param req The upgrade request. - */ - template - void - on_upgrade( - ssl_stream&& stream, - endpoint_type ep, - boost::beast::http::request&& req) - { - std::make_shared( - std::move(stream), - "wss_async_port", - log_, - instance_.next_id(), - ep, - cb2_)->run(std::move(req)); - } -}; - -} // framework - -#endif diff --git a/example/websocket-client-ssl/CMakeLists.txt b/example/websocket-client-ssl/CMakeLists.txt deleted file mode 100644 index de78396b..00000000 --- a/example/websocket-client-ssl/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -GroupSources(include/boost/beast beast) -GroupSources(example/websocket-client-ssl "/") - -add_executable (websocket-client-ssl - ${BOOST_BEAST_INCLUDES} - Jamfile - websocket_client_ssl.cpp -) - -target_link_libraries(websocket-client-ssl - ${OPENSSL_LIBRARIES} - ) diff --git a/example/websocket-client-ssl/Jamfile b/example/websocket-client-ssl/Jamfile deleted file mode 100644 index d1f78dbb..00000000 --- a/example/websocket-client-ssl/Jamfile +++ /dev/null @@ -1,53 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -import os ; - -if [ os.name ] = SOLARIS -{ - lib socket ; - lib nsl ; -} -else if [ os.name ] = NT -{ - lib ws2_32 ; - lib mswsock ; -} -else if [ os.name ] = HPUX -{ - lib ipv6 ; -} -else if [ os.name ] = HAIKU -{ - lib network ; -} - -if [ os.name ] = NT -{ - lib ssl : : ssleay32 ; - lib crypto : : libeay32 ; -} -else -{ - lib ssl ; - lib crypto ; -} - -project - : requirements - ssl - crypto - ; - -exe ssl-websocket-client : - ssl_websocket_client.cpp - : - coverage:no - ubasan:no - ; diff --git a/example/websocket-client-ssl/websocket_client_ssl.cpp b/example/websocket-client-ssl/websocket_client_ssl.cpp deleted file mode 100644 index 5338c23a..00000000 --- a/example/websocket-client-ssl/websocket_client_ssl.cpp +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "../common/root_certificates.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -using tcp = boost::asio::ip::tcp; // from -namespace ssl = boost::asio::ssl; // from -namespace websocket = boost::beast::websocket; // from - -int main() -{ - // A helper for reporting errors - auto const fail = - [](std::string what, boost::beast::error_code ec) - { - std::cerr << what << ": " << ec.message() << std::endl; - std::cerr.flush(); - return EXIT_FAILURE; - }; - - boost::system::error_code ec; - - // Set up an asio socket to connect to a remote host - boost::asio::io_service ios; - tcp::resolver r{ios}; - tcp::socket sock{ios}; - - // Look up the domain name - std::string const host = "echo.websocket.org"; - auto const lookup = r.resolve({host, "https"}, ec); - if(ec) - return fail("resolve", ec); - - // Make the connection on the IP address we get from a lookup - boost::asio::connect(sock, lookup, ec); - if(ec) - return fail("connect", ec); - - // Create the required ssl context - ssl::context ctx{ssl::context::sslv23_client}; - - // This holds the root certificate used for verification - load_root_certificates(ctx, ec); - if(ec) - return fail("certificate", ec); - - // Wrap the now-connected socket in an SSL stream - using stream_type = ssl::stream; - stream_type stream{sock, ctx}; - stream.set_verify_mode(ssl::verify_none); - - // Perform SSL handshaking - stream.handshake(ssl::stream_base::client, ec); - if(ec) - return fail("ssl handshake", ec); - - // Now wrap the handshaked SSL stream in a websocket stream - websocket::stream ws{stream}; - - // Perform the websocket handshake - ws.handshake(host, "/", ec); - if(ec) - return fail("handshake", ec); - - // Send a message - ws.write(boost::asio::buffer("Hello, world!"), ec); - if(ec) - return fail("write", ec); - - // This buffer will hold the incoming message - boost::beast::multi_buffer b; - - // Read the message into our buffer - ws.read(b, ec); - if(ec) - return fail("read", ec); - - // Close the WebSocket connection - ws.close(websocket::close_code::normal, ec); - if(ec) - return fail("close", ec); - - // The buffers() function helps print a ConstBufferSequence - std::cout << boost::beast::buffers(b.data()) << std::endl; - - return EXIT_SUCCESS; -} diff --git a/example/websocket-client/websocket_client.cpp b/example/websocket-client/websocket_client.cpp deleted file mode 100644 index c3f2ce2f..00000000 --- a/example/websocket-client/websocket_client.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -//[example_websocket_client - -#include -#include -#include -#include -#include -#include - -using tcp = boost::asio::ip::tcp; // from -namespace websocket = boost::beast::websocket; // from - -int main() -{ - // A helper for reporting errors - auto const fail = - [](std::string what, boost::beast::error_code ec) - { - std::cerr << what << ": " << ec.message() << std::endl; - std::cerr.flush(); - return EXIT_FAILURE; - }; - - boost::system::error_code ec; - - // Set up an asio socket - boost::asio::io_service ios; - tcp::resolver r{ios}; - tcp::socket sock{ios}; - - // Look up the domain name - std::string const host = "echo.websocket.org"; - auto const lookup = r.resolve({host, "http"}, ec); - if(ec) - return fail("resolve", ec); - - // Make the connection on the IP address we get from a lookup - boost::asio::connect(sock, lookup, ec); - if(ec) - return fail("connect", ec); - - // Wrap the now-connected socket in a websocket stream - websocket::stream ws{sock}; - - // Perform the websocket handshake - ws.handshake(host, "/", ec); - if(ec) - return fail("handshake", ec); - - // Send a message - ws.write(boost::asio::buffer(std::string("Hello, world!")), ec); - if(ec) - return fail("write", ec); - - // This buffer will hold the incoming message - boost::beast::multi_buffer b; - - // Read the message into our buffer - ws.read(b, ec); - if(ec) - return fail("read", ec); - - // Close the WebSocket connection - ws.close(websocket::close_code::normal, ec); - if(ec) - return fail("close", ec); - - // The buffers() function helps print a ConstBufferSequence - std::cout << boost::beast::buffers(b.data()) << std::endl; - - // If we get here the connection was cleanly closed - return EXIT_SUCCESS; -} - -//] diff --git a/example/websocket-server-async/websocket_server_async.cpp b/example/websocket-server-async/websocket_server_async.cpp deleted file mode 100644 index 2bcde5b4..00000000 --- a/example/websocket-server-async/websocket_server_async.cpp +++ /dev/null @@ -1,412 +0,0 @@ -// -// Copyright (c) 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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "../common/helpers.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace http = boost::beast::http; // from -namespace websocket = boost::beast::websocket; // from -namespace ip = boost::asio::ip; // from -using tcp = boost::asio::ip::tcp; // from - -//------------------------------------------------------------------------------ -// -// Example: WebSocket echo server, asynchronous -// -//------------------------------------------------------------------------------ - -/** WebSocket asynchronous echo server - - The server holds the listening socket, the io_service, and - the threads calling io_service::run -*/ -class server -{ - using error_code = boost::beast::error_code; // Saves typing - using clock_type = - std::chrono::steady_clock; // For the timer - using stream_type = - websocket::stream; // The type of our websocket stream - std::ostream* log_; // Used for diagnostic output, may be null - boost::asio::io_service ios_; // The io_service, required - tcp::socket sock_; // Holds accepted connections - tcp::endpoint ep_; // The remote endpoint during accept - std::vector thread_; // Threads for the io_service - boost::asio::ip::tcp::acceptor acceptor_; // The listening socket - std::function mod_; // Called on new stream - boost::optional< - boost::asio::io_service::work> work_; // Keeps io_service::run from returning - - //-------------------------------------------------------------------------- - - class connection : public std::enable_shared_from_this - { - std::ostream* log_; // Where to log, may be null - tcp::endpoint ep_; // The remote endpoing - stream_type ws_; // The websocket stream - boost::asio::basic_waitable_timer< - clock_type> timer_; // Needed for timeouts - boost::asio::io_service::strand strand_;// Needed when threads > 1 - boost::beast::multi_buffer buffer_; // Stores the current message - std::size_t id_; // A small unique id - - public: - /// Constructor - connection( - server& parent, - tcp::endpoint const& ep, - tcp::socket&& sock) - : log_(parent.log_) - , ep_(ep) - , ws_(std::move(sock)) - , timer_(ws_.get_io_service(), (clock_type::time_point::max)()) - , strand_(ws_.get_io_service()) - , id_([] - { - static std::atomic n{0}; - return ++n; - }()) - { - // Invoke the callback for new connections if set. - // This allows the settings on the websocket stream - // to be adjusted. For example to turn compression - // on or off or adjust the read and write buffer sizes. - // - if(parent.mod_) - parent.mod_(ws_); - } - - // Called immediately after the connection is created. - // We keep this separate from the constructor because - // shared_from_this may not be called from constructors. - void run() - { - // Run the timer - on_timer({}); - - // Put the handshake on the timer - timer_.expires_from_now(std::chrono::seconds(15)); - - // Read the websocket handshake and send the response - ws_.async_accept_ex( - [](websocket::response_type& res) - { - res.insert(http::field::server, "websocket-server-async"); - }, - strand_.wrap(std::bind( - &connection::on_accept, - shared_from_this(), - std::placeholders::_1))); - } - - private: - // Called when the timer expires. - // We operate the timer continuously this simplifies the code. - // - void on_timer(error_code ec) - { - if(ec && ec != boost::asio::error::operation_aborted) - return fail("timer", ec); - - // Verify that the timer really expired - // since the deadline may have moved. - // - if(timer_.expires_at() <= clock_type::now()) - { - // Closing the socket cancels all outstanding - // operations. They will complete with - // boost::asio::error::operation_aborted - // - ws_.next_layer().close(ec); - return; - } - - // Wait on the timer - timer_.async_wait( - strand_.wrap(std::bind( - &connection::on_timer, - shared_from_this(), - std::placeholders::_1))); - } - - // Called after the handshake is performed - void on_accept(error_code ec) - { - if(ec) - return fail("accept", ec); - do_read(); - } - - // Read a message from the websocket stream - void do_read() - { - // Put the read on the timer - timer_.expires_from_now(std::chrono::seconds(15)); - - // Read a message - ws_.async_read(buffer_, - strand_.wrap(std::bind( - &connection::on_read, - shared_from_this(), - std::placeholders::_1))); - } - - // Called after the message read completes - void on_read(error_code ec) - { - // This error means the other side - // closed the websocket stream. - if(ec == websocket::error::closed) - return; - - if(ec) - return fail("read", ec); - - // Put the write on the timer - timer_.expires_from_now(std::chrono::seconds(15)); - - // Write the received message back - ws_.binary(ws_.got_binary()); - ws_.async_write(buffer_.data(), - strand_.wrap(std::bind( - &connection::on_write, - shared_from_this(), - std::placeholders::_1))); - } - - // Called after the message write completes - void on_write(error_code ec) - { - if(ec) - return fail("write", ec); - - // Empty out the buffer. This is - // needed if we want to do another read. - // - buffer_.consume(buffer_.size()); - - do_read(); - } - - // Pretty-print an error to the log - void fail(std::string what, error_code ec) - { - if(log_) - if(ec != boost::asio::error::operation_aborted) - print(*log_, "[#", id_, " ", ep_, "] ", what, ": ", ec.message()); - } - }; - - //-------------------------------------------------------------------------- - - // Pretty-print an error to the log - void fail(std::string what, error_code ec) - { - if(log_) - print(*log_, what, ": ", ec.message()); - } - - // Initiates an accept - void do_accept() - { - acceptor_.async_accept(sock_, ep_, - std::bind(&server::on_accept, this, - std::placeholders::_1)); - } - - // Called when receiving an incoming connection - void on_accept(error_code ec) - { - // This can happen during exit - if(! acceptor_.is_open()) - return; - - // This can happen during exit - if(ec == boost::asio::error::operation_aborted) - return; - - if(ec) - fail("accept", ec); - - // Create the connection and run it - std::make_shared(*this, ep_, std::move(sock_))->run(); - - // Initiate another accept - do_accept(); - } - -public: - /** 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. - */ - 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. - ~server() - { - work_ = boost::none; - ios_.dispatch([&] - { - error_code ec; - acceptor_.close(ec); - }); - for(auto& t : thread_) - t.join(); - } - - /// Return the listening endpoint. - tcp::endpoint - 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(tcp::endpoint 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); - do_accept(); - } -}; - -//------------------------------------------------------------------------------ - -// This helper will apply some settings to a WebSocket -// stream. The server applies it to all new connections. -// -class set_stream_options -{ - websocket::permessage_deflate pmd_; - -public: - set_stream_options(set_stream_options const&) = default; - - explicit - set_stream_options( - websocket::permessage_deflate const& pmd) - : pmd_(pmd) - { - } - - template - void - operator()(websocket::stream& ws) const - { - ws.set_option(pmd_); - - // Turn off the auto-fragment option. - // This improves Autobahn performance. - // - ws.auto_fragment(false); - - // 64MB message size limit. - // The high limit is needed for Autobahn. - ws.read_message_max(64 * 1024 * 1024); - } -}; - -int main(int argc, char* argv[]) -{ - // Check command line arguments. - if(argc != 4) - { - std::cerr << - "Usage: " << argv[0] << "
\n" - " For IPv4, try: " << argv[0] << " 0.0.0.0 8080 1\n" - " For IPv6, try: " << argv[0] << " 0::0 8080 1\n" - ; - return EXIT_FAILURE; - } - - // Decode command line options - auto address = ip::address::from_string(argv[1]); - unsigned short port = static_cast(std::atoi(argv[2])); - unsigned short threads = static_cast(std::atoi(argv[3])); - - // Allow permessage-deflate - // compression on all connections - websocket::permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - pmd.compLevel = 3; - - // Create our server - server s{&std::cout, threads}; - s.on_new_stream(set_stream_options{pmd}); - - // Open the listening port - boost::beast::error_code ec; - s.open(tcp::endpoint{address, port}, ec); - if(ec) - { - std::cerr << "Error: " << ec.message(); - return EXIT_FAILURE; - } - - // Wait for CTRL+C. After receiving CTRL+C, - // the server should shut down cleanly. - // - sig_wait(); - - return EXIT_SUCCESS; -} diff --git a/example/websocket/CMakeLists.txt b/example/websocket/CMakeLists.txt new file mode 100644 index 00000000..d38604dd --- /dev/null +++ b/example/websocket/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (client) +add_subdirectory (server) diff --git a/example/websocket/Jamfile b/example/websocket/Jamfile new file mode 100644 index 00000000..32f2c514 --- /dev/null +++ b/example/websocket/Jamfile @@ -0,0 +1,11 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project client ; +build-project server ; diff --git a/example/websocket/client/CMakeLists.txt b/example/websocket/client/CMakeLists.txt new file mode 100644 index 00000000..5955ca18 --- /dev/null +++ b/example/websocket/client/CMakeLists.txt @@ -0,0 +1,18 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (async) +add_subdirectory (coro) +add_subdirectory (sync) + +if (OPENSSL_FOUND) + add_subdirectory (async-ssl) + add_subdirectory (coro-ssl) + add_subdirectory (sync-ssl) +endif() diff --git a/example/websocket/client/Jamfile b/example/websocket/client/Jamfile new file mode 100644 index 00000000..f2a7a337 --- /dev/null +++ b/example/websocket/client/Jamfile @@ -0,0 +1,17 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project async ; +build-project coro ; +build-project sync ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project async-ssl ; +#build-project coro-ssl ; +#build-project sync-ssl ; diff --git a/example/websocket/client/async-ssl/CMakeLists.txt b/example/websocket/client/async-ssl/CMakeLists.txt new file mode 100644 index 00000000..e7ceb413 --- /dev/null +++ b/example/websocket/client/async-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/client/async-ssl "/") + +add_executable (websocket-client-async-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_async_ssl.cpp +) + +target_link_libraries (websocket-client-async-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/client/async-ssl/Jamfile b/example/websocket/client/async-ssl/Jamfile new file mode 100644 index 00000000..35d1b50a --- /dev/null +++ b/example/websocket/client/async-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-client-async-ssl : + websocket_client_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp b/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp new file mode 100644 index 00000000..f1823b5a --- /dev/null +++ b/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp @@ -0,0 +1,219 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL client, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Sends a WebSocket message and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + websocket::stream> ws_; + boost::beast::multi_buffer buffer_; + std::string host_; + std::string text_; + +public: + // Resolver and socket require an io_service + explicit + session(boost::asio::io_service& ios, ssl::context& ctx) + : resolver_(ios) + , ws_(ios, ctx) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* text) + { + // Save these for later + host_ = host; + text_ = text; + + // Look up the domain name + resolver_.async_resolve({host, port}, + std::bind( + &session::on_resolve, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_resolve( + boost::system::error_code ec, + tcp::resolver::iterator result) + { + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect( + ws_.next_layer().next_layer(), + result, + std::bind( + &session::on_connect, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_connect(boost::system::error_code ec) + { + if(ec) + return fail(ec, "connect"); + + // Perform the SSL handshake + ws_.next_layer().async_handshake( + ssl::stream_base::client, + std::bind( + &session::on_ssl_handshake, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_ssl_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "ssl_handshake"); + + // Perform the websocket handshake + ws_.async_handshake(host_, "/", + std::bind( + &session::on_handshake, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws_.async_write( + boost::asio::buffer(text_), + std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Read a message into our buffer + ws_.async_read( + buffer_, + std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_read(boost::system::error_code ec) + { + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws_.async_close(websocket::close_code::normal, + std::bind( + &session::on_close, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_close(boost::system::error_code ec) + { + if(ec) + return fail(ec, "close"); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(buffer_.data()) << std::endl; + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-async-ssl \n" << + "Example:\n" << + " websocket-client-async-ssl echo.websocket.org 443 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // Launch the asynchronous operation + std::make_shared(ios, ctx)->run(host, port, text); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/client/async/CMakeLists.txt b/example/websocket/client/async/CMakeLists.txt new file mode 100644 index 00000000..7f4b4b39 --- /dev/null +++ b/example/websocket/client/async/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/async "/") + +add_executable (websocket-client-async + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_client_async.cpp +) diff --git a/example/websocket/client/async/Jamfile b/example/websocket/client/async/Jamfile new file mode 100644 index 00000000..e5359db3 --- /dev/null +++ b/example/websocket/client/async/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-client-async : + websocket_client_async.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/async/websocket_client_async.cpp b/example/websocket/client/async/websocket_client_async.cpp new file mode 100644 index 00000000..66d53fbd --- /dev/null +++ b/example/websocket/client/async/websocket_client_async.cpp @@ -0,0 +1,193 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket client, asynchronous +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Sends a WebSocket message and prints the response +class session : public std::enable_shared_from_this +{ + tcp::resolver resolver_; + websocket::stream ws_; + boost::beast::multi_buffer buffer_; + std::string host_; + std::string text_; + +public: + // Resolver and socket require an io_service + explicit + session(boost::asio::io_service& ios) + : resolver_(ios) + , ws_(ios) + { + } + + // Start the asynchronous operation + void + run( + char const* host, + char const* port, + char const* text) + { + // Save these for later + host_ = host; + text_ = text; + + // Look up the domain name + resolver_.async_resolve({host, port}, + std::bind( + &session::on_resolve, + shared_from_this(), + std::placeholders::_1, + std::placeholders::_2)); + } + + void + on_resolve( + boost::system::error_code ec, + tcp::resolver::iterator result) + { + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect( + ws_.next_layer(), + result, + std::bind( + &session::on_connect, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_connect(boost::system::error_code ec) + { + if(ec) + return fail(ec, "connect"); + + // Perform the websocket handshake + ws_.async_handshake(host_, "/", + std::bind( + &session::on_handshake, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws_.async_write( + boost::asio::buffer(text_), + std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Read a message into our buffer + ws_.async_read( + buffer_, + std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_read(boost::system::error_code ec) + { + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws_.async_close(websocket::close_code::normal, + std::bind( + &session::on_close, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_close(boost::system::error_code ec) + { + if(ec) + return fail(ec, "close"); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(buffer_.data()) << std::endl; + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-async \n" << + "Example:\n" << + " websocket-client-async echo.websocket.org 80 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // Launch the asynchronous operation + std::make_shared(ios)->run(host, port, text); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/client/coro-ssl/CMakeLists.txt b/example/websocket/client/coro-ssl/CMakeLists.txt new file mode 100644 index 00000000..c9f9bbcf --- /dev/null +++ b/example/websocket/client/coro-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/client/coro-ssl "/") + +add_executable (websocket-client-coro-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_coro_ssl.cpp +) + +target_link_libraries (websocket-client-coro-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/client/coro-ssl/Jamfile b/example/websocket/client/coro-ssl/Jamfile new file mode 100644 index 00000000..5a48576c --- /dev/null +++ b/example/websocket/client/coro-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-client-coro-ssl : + websocket_client_coro_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp b/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp new file mode 100644 index 00000000..066d7f49 --- /dev/null +++ b/example/websocket/client/coro-ssl/websocket_client_coro_ssl.cpp @@ -0,0 +1,144 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL client, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Sends a WebSocket message and prints the response +void +do_session( + std::string const& host, + std::string const& port, + std::string const& text, + boost::asio::io_service& ios, + ssl::context& ctx, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + websocket::stream> ws{ios, ctx}; + + // Look up the domain name + auto const lookup = resolver.async_resolve({host, port}, yield[ec]); + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect(ws.next_layer().next_layer(), lookup, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // Perform the SSL handshake + ws.next_layer().async_handshake(ssl::stream_base::client, yield[ec]); + if(ec) + return fail(ec, "ssl_handshake"); + + // Perform the websocket handshake + ws.async_handshake(host, "/", yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws.async_write(boost::asio::buffer(std::string(text)), yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer will hold the incoming message + boost::beast::multi_buffer b; + + // Read a message into our buffer + ws.async_read(b, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws.async_close(websocket::close_code::normal, yield[ec]); + if(ec) + return fail(ec, "close"); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(b.data()) << std::endl; +} + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-coro-ssl \n" << + "Example:\n" << + " websocket-client-coro-ssl echo.websocket.org 443 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // Launch the asynchronous operation + boost::asio::spawn(ios, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(text), + std::ref(ios), + std::ref(ctx), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/client/coro/CMakeLists.txt b/example/websocket/client/coro/CMakeLists.txt new file mode 100644 index 00000000..a04c7e16 --- /dev/null +++ b/example/websocket/client/coro/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/coro "/") + +add_executable (websocket-client-coro + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_client_coro.cpp +) diff --git a/example/websocket/client/coro/Jamfile b/example/websocket/client/coro/Jamfile new file mode 100644 index 00000000..4fc30887 --- /dev/null +++ b/example/websocket/client/coro/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-client-coro : + websocket_client_coro.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/coro/websocket_client_coro.cpp b/example/websocket/client/coro/websocket_client_coro.cpp new file mode 100644 index 00000000..d12b298a --- /dev/null +++ b/example/websocket/client/coro/websocket_client_coro.cpp @@ -0,0 +1,126 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket client, coroutine +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Sends a WebSocket message and prints the response +void +do_session( + std::string const& host, + std::string const& port, + std::string const& text, + boost::asio::io_service& ios, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + websocket::stream ws{ios}; + + // Look up the domain name + auto const lookup = resolver.async_resolve({host, port}, yield[ec]); + if(ec) + return fail(ec, "resolve"); + + // Make the connection on the IP address we get from a lookup + boost::asio::async_connect(ws.next_layer(), lookup, yield[ec]); + if(ec) + return fail(ec, "connect"); + + // Perform the websocket handshake + ws.async_handshake(host, "/", yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Send the message + ws.async_write(boost::asio::buffer(std::string(text)), yield[ec]); + if(ec) + return fail(ec, "write"); + + // This buffer will hold the incoming message + boost::beast::multi_buffer b; + + // Read a message into our buffer + ws.async_read(b, yield[ec]); + if(ec) + return fail(ec, "read"); + + // Close the WebSocket connection + ws.async_close(websocket::close_code::normal, yield[ec]); + if(ec) + return fail(ec, "close"); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(b.data()) << std::endl; +} + +//------------------------------------------------------------------------------ + +int main(int argc, char** argv) +{ + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-coro \n" << + "Example:\n" << + " websocket-client-coro echo.websocket.org 80 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // Launch the asynchronous operation + boost::asio::spawn(ios, std::bind( + &do_session, + std::string(host), + std::string(port), + std::string(text), + std::ref(ios), + std::placeholders::_1)); + + // Run the I/O service. The call will return when + // the get operation is complete. + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/client/sync-ssl/CMakeLists.txt b/example/websocket/client/sync-ssl/CMakeLists.txt new file mode 100644 index 00000000..a7658b7e --- /dev/null +++ b/example/websocket/client/sync-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/client/sync-ssl "/") + +add_executable (websocket-client-sync-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/root_certificates.hpp + Jamfile + websocket_client_sync_ssl.cpp +) + +target_link_libraries (websocket-client-sync-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/client/sync-ssl/Jamfile b/example/websocket/client/sync-ssl/Jamfile new file mode 100644 index 00000000..dca65102 --- /dev/null +++ b/example/websocket/client/sync-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-client-sync-ssl : + websocket_client_sync_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp b/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp new file mode 100644 index 00000000..fe388a17 --- /dev/null +++ b/example/websocket/client/sync-ssl/websocket_client_sync_ssl.cpp @@ -0,0 +1,98 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL client, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/root_certificates.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +// Sends a WebSocket message and prints the response +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-sync-ssl \n" << + "Example:\n" << + " websocket-client-sync-ssl echo.websocket.org 443 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // These objects perform our I/O + tcp::resolver resolver{ios}; + websocket::stream> ws{ios, ctx}; + + // Look up the domain name + auto const lookup = resolver.resolve({host, port}); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(ws.next_layer().next_layer(), lookup); + + // Perform the SSL handshake + ws.next_layer().handshake(ssl::stream_base::client); + + // Perform the websocket handshake + ws.handshake(host, "/"); + + // Send the message + ws.write(boost::asio::buffer(std::string(text))); + + // This buffer will hold the incoming message + boost::beast::multi_buffer b; + + // Read a message into our buffer + ws.read(b); + + // Close the WebSocket connection + ws.close(websocket::close_code::normal); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(b.data()) << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} diff --git a/example/websocket/client/sync/CMakeLists.txt b/example/websocket/client/sync/CMakeLists.txt new file mode 100644 index 00000000..a449f6fb --- /dev/null +++ b/example/websocket/client/sync/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/client/sync "/") + +add_executable (websocket-client-sync + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_client_sync.cpp +) diff --git a/example/websocket/client/sync/Jamfile b/example/websocket/client/sync/Jamfile new file mode 100644 index 00000000..ea71a0ce --- /dev/null +++ b/example/websocket/client/sync/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-client-sync : + websocket_client_sync.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/client/sync/websocket_client_sync.cpp b/example/websocket/client/sync/websocket_client_sync.cpp new file mode 100644 index 00000000..690630a2 --- /dev/null +++ b/example/websocket/client/sync/websocket_client_sync.cpp @@ -0,0 +1,88 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket client, synchronous +// +//------------------------------------------------------------------------------ + +//[example_websocket_client + +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +// Sends a WebSocket message and prints the response +int main(int argc, char** argv) +{ + try + { + // Check command line arguments. + if(argc != 4) + { + std::cerr << + "Usage: websocket-client-sync \n" << + "Example:\n" << + " websocket-client-sync echo.websocket.org 80 \"Hello, world!\"\n"; + return EXIT_FAILURE; + } + auto const host = argv[1]; + auto const port = argv[2]; + auto const text = argv[3]; + + // The io_service is required for all I/O + boost::asio::io_service ios; + + // These objects perform our I/O + tcp::resolver resolver{ios}; + websocket::stream ws{ios}; + + // Look up the domain name + auto const lookup = resolver.resolve({host, port}); + + // Make the connection on the IP address we get from a lookup + boost::asio::connect(ws.next_layer(), lookup); + + // Perform the websocket handshake + ws.handshake(host, "/"); + + // Send the message + ws.write(boost::asio::buffer(std::string(text))); + + // This buffer will hold the incoming message + boost::beast::multi_buffer buffer; + + // Read a message into our buffer + ws.read(buffer); + + // Close the WebSocket connection + ws.close(websocket::close_code::normal); + + // If we get here then the connection is closed gracefully + + // The buffers() function helps print a ConstBufferSequence + std::cout << boost::beast::buffers(buffer.data()) << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +//] diff --git a/example/websocket/server/CMakeLists.txt b/example/websocket/server/CMakeLists.txt new file mode 100644 index 00000000..23a8bdf1 --- /dev/null +++ b/example/websocket/server/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +add_subdirectory (async) +add_subdirectory (coro) +add_subdirectory (fast) +add_subdirectory (stackless) +add_subdirectory (sync) + +if (OPENSSL_FOUND) + add_subdirectory (async-ssl) + add_subdirectory (coro-ssl) + add_subdirectory (stackless-ssl) + add_subdirectory (sync-ssl) +endif() diff --git a/example/websocket/server/Jamfile b/example/websocket/server/Jamfile new file mode 100644 index 00000000..9c7565c5 --- /dev/null +++ b/example/websocket/server/Jamfile @@ -0,0 +1,20 @@ +# +# 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) +# +# Official repository: https://github.com/boostorg/beast +# + +build-project async ; +build-project coro ; +build-project fast ; +build-project stackless ; +build-project sync ; + +# VFALCO How do I make this work on Windows and if OpenSSL is not available? +#build-project async-ssl ; +#build-project coro-ssl ; +#build-project stackless-ssl ; +#build-project sync-ssl ; diff --git a/example/websocket/server/async-ssl/CMakeLists.txt b/example/websocket/server/async-ssl/CMakeLists.txt new file mode 100644 index 00000000..408f39bf --- /dev/null +++ b/example/websocket/server/async-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/server/async-ssl "/") + +add_executable (websocket-server-async-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_async_ssl.cpp +) + +target_link_libraries (websocket-server-async-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/server/async-ssl/Jamfile b/example/websocket/server/async-ssl/Jamfile new file mode 100644 index 00000000..16bc5dea --- /dev/null +++ b/example/websocket/server/async-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-server-async-ssl : + websocket_server_async_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp b/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp new file mode 100644 index 00000000..bb07ed4a --- /dev/null +++ b/example/websocket/server/async-ssl/websocket_server_async_ssl.cpp @@ -0,0 +1,273 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL server, asynchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session : public std::enable_shared_from_this +{ + tcp::socket socket_; + websocket::stream> ws_; + boost::asio::io_service::strand strand_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + session(tcp::socket socket, ssl::context& ctx) + : socket_(std::move(socket)) + , ws_(socket_, ctx) + , strand_(ws_.get_io_service()) + { + } + + // Start the asynchronous operation + void + run() + { + // Perform the SSL handshake + ws_.next_layer().async_handshake( + ssl::stream_base::server, + strand_.wrap(std::bind( + &session::on_handshake, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_handshake(boost::system::error_code ec) + { + if(ec) + return fail(ec, "handshake"); + + // Accept the websocket handshake + ws_.async_accept( + strand_.wrap(std::bind( + &session::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Read a message into our buffer + ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This indicates that the session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1)); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared(std::move(socket_), ctx_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async-ssl
\n" << + "Example:\n" << + " websocket-server-async-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared(ios, ctx, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket-server-async/CMakeLists.txt b/example/websocket/server/async/CMakeLists.txt similarity index 80% rename from example/websocket-server-async/CMakeLists.txt rename to example/websocket/server/async/CMakeLists.txt index 5dfbcee0..36f4ea87 100644 --- a/example/websocket-server-async/CMakeLists.txt +++ b/example/websocket/server/async/CMakeLists.txt @@ -8,12 +8,10 @@ # GroupSources(include/boost/beast beast) -GroupSources(example/common common) -GroupSources(example/websocket-server-async "/") +GroupSources(example/websocket/server/async "/") add_executable (websocket-server-async ${BOOST_BEAST_INCLUDES} - ${COMMON_INCLUDES} Jamfile websocket_server_async.cpp ) diff --git a/example/websocket-server-async/Jamfile b/example/websocket/server/async/Jamfile similarity index 100% rename from example/websocket-server-async/Jamfile rename to example/websocket/server/async/Jamfile diff --git a/example/websocket/server/async/websocket_server_async.cpp b/example/websocket/server/async/websocket_server_async.cpp new file mode 100644 index 00000000..7e1bbbc1 --- /dev/null +++ b/example/websocket/server/async/websocket_server_async.cpp @@ -0,0 +1,243 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket server, asynchronous +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session : public std::enable_shared_from_this +{ + websocket::stream ws_; + boost::asio::io_service::strand strand_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + explicit + session(tcp::socket socket) + : ws_(std::move(socket)) + , strand_(ws_.get_io_service()) + { + } + + // Start the asynchronous operation + void + run() + { + // Accept the websocket handshake + ws_.async_accept( + strand_.wrap(std::bind( + &session::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Read a message into our buffer + ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This indicates that the session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &session::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener : public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + strand_.wrap(std::bind( + &listener::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared(std::move(socket_))->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async
\n" << + "Example:\n" << + " websocket-server-async 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create and launch a listening port + std::make_shared(ios, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/coro-ssl/CMakeLists.txt b/example/websocket/server/coro-ssl/CMakeLists.txt new file mode 100644 index 00000000..9a5e6a53 --- /dev/null +++ b/example/websocket/server/coro-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/server/coro-ssl "/") + +add_executable (websocket-server-coro-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_coro_ssl.cpp +) + +target_link_libraries (websocket-server-coro-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/server/coro-ssl/Jamfile b/example/websocket/server/coro-ssl/Jamfile new file mode 100644 index 00000000..f967994f --- /dev/null +++ b/example/websocket/server/coro-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-server-coro-ssl : + websocket_server_coro_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp b/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp new file mode 100644 index 00000000..ef522ace --- /dev/null +++ b/example/websocket/server/coro-ssl/websocket_server_coro_ssl.cpp @@ -0,0 +1,181 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL server, coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +void +do_session( + tcp::socket& socket, + ssl::context& ctx, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Construct the stream by moving in the socket + websocket::stream> ws{socket, ctx}; + + // Perform the SSL handshake + ws.next_layer().async_handshake(ssl::stream_base::server, yield[ec]); + if(ec) + return fail(ec, "handshake"); + + // Accept the websocket handshake + ws.async_accept(yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // This buffer will hold the incoming message + boost::beast::multi_buffer buffer; + + // Read a message + ws.async_read(buffer, yield[ec]); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + break; + + if(ec) + return fail(ec, "read"); + + // Echo the message back + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ios); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ios); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_io_service(), + std::bind( + &do_session, + std::move(socket), + std::ref(ctx), + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-coro-ssl
\n" << + "Example:\n" << + " websocket-server-coro-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Spawn a listening port + boost::asio::spawn(ios, + std::bind( + &do_listen, + std::ref(ios), + std::ref(ctx), + tcp::endpoint{address, port}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/coro/CMakeLists.txt b/example/websocket/server/coro/CMakeLists.txt new file mode 100644 index 00000000..58638fe2 --- /dev/null +++ b/example/websocket/server/coro/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/coro "/") + +add_executable (websocket-server-coro + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_server_coro.cpp +) diff --git a/example/websocket/server/coro/Jamfile b/example/websocket/server/coro/Jamfile new file mode 100644 index 00000000..37c83090 --- /dev/null +++ b/example/websocket/server/coro/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-server-coro : + websocket_server_coro.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/coro/websocket_server_coro.cpp b/example/websocket/server/coro/websocket_server_coro.cpp new file mode 100644 index 00000000..ee36a2ec --- /dev/null +++ b/example/websocket/server/coro/websocket_server_coro.cpp @@ -0,0 +1,159 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket server, coroutine +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +void +do_session(tcp::socket& socket, boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Construct the stream by moving in the socket + websocket::stream ws{std::move(socket)}; + + // Accept the websocket handshake + ws.async_accept(yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // This buffer will hold the incoming message + boost::beast::multi_buffer buffer; + + // Read a message + ws.async_read(buffer, yield[ec]); + + // This indicates that the session was closed + if(ec == websocket::error::closed) + break; + + if(ec) + return fail(ec, "read"); + + // Echo the message back + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +void +do_listen( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + // Open the acceptor + tcp::acceptor acceptor(ios); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + // Bind to the server address + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + // Start listening for connections + acceptor.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ios); + acceptor.async_accept(socket, yield[ec]); + if(ec) + fail(ec, "accept"); + else + boost::asio::spawn( + acceptor.get_io_service(), + std::bind( + &do_session, + std::move(socket), + std::placeholders::_1)); + } +} + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-coro
\n" << + "Example:\n" << + " websocket-server-coro 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Spawn a listening port + boost::asio::spawn(ios, + std::bind( + &do_listen, + std::ref(ios), + tcp::endpoint{address, port}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/fast/CMakeLists.txt b/example/websocket/server/fast/CMakeLists.txt new file mode 100644 index 00000000..d7308592 --- /dev/null +++ b/example/websocket/server/fast/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/fast "/") + +add_executable (websocket-server-fast + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_server_fast.cpp +) diff --git a/example/websocket/server/fast/Jamfile b/example/websocket/server/fast/Jamfile new file mode 100644 index 00000000..06dc26c6 --- /dev/null +++ b/example/websocket/server/fast/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-server-fast : + websocket_server_fast.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/fast/websocket_server_fast.cpp b/example/websocket/server/fast/websocket_server_fast.cpp new file mode 100644 index 00000000..0c704707 --- /dev/null +++ b/example/websocket/server/fast/websocket_server_fast.cpp @@ -0,0 +1,449 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket server, fast +// +//------------------------------------------------------------------------------ + +/* This server contains the following ports: + + Synchronous + Asynchronous + Coroutine + + This program is optimized for the Autobahn|Testsuite + benchmarking and WebSocket compliants testing program. + + See: + https://github.com/crossbario/autobahn-testsuite +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace http = boost::beast::http; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << (std::string(what) + ": " + ec.message() + "\n"); +} + +// Adjust settings on the stream +template +void +setup_stream(websocket::stream& ws) +{ + // These values are tuned for Autobahn|Testsuite, and + // should also be generally helpful for increased performance. + + websocket::permessage_deflate pmd; + pmd.client_enable = true; + pmd.server_enable = true; + pmd.compLevel = 3; + ws.set_option(pmd); + + ws.auto_fragment(false); + + // Autobahn|Testsuite needs this + ws.read_message_max(64 * 1024 * 1024); +} + +//------------------------------------------------------------------------------ + +void +do_sync_session(tcp::socket& socket) +{ + boost::system::error_code ec; + + websocket::stream ws{std::move(socket)}; + setup_stream(ws); + + ws.accept_ex( + [](websocket::response_type& res) + { + res.set(http::field::server, + "Boost.Beast.sync/" + std::to_string(BOOST_BEAST_VERSION)); + }, + ec); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + boost::beast::multi_buffer buffer; + + ws.read(buffer, ec); + if(ec == websocket::error::closed) + break; + if(ec) + return fail(ec, "read"); + + ws.text(ws.got_text()); + ws.write(buffer.data(), ec); + if(ec) + return fail(ec, "write"); + } +} + +void +do_sync_listen( + boost::asio::io_service& ios, + tcp::endpoint endpoint) +{ + boost::system::error_code ec; + tcp::acceptor acceptor{ios, endpoint}; + for(;;) + { + tcp::socket socket{ios}; + + acceptor.accept(socket, ec); + if(ec) + return fail(ec, "accept"); + + std::thread{std::bind( + &do_sync_session, + std::move(socket))}.detach(); + } +} + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +class async_session : public std::enable_shared_from_this +{ + websocket::stream ws_; + boost::asio::io_service::strand strand_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + explicit + async_session(tcp::socket socket) + : ws_(std::move(socket)) + , strand_(ws_.get_io_service()) + { + setup_stream(ws_); + } + + // Start the asynchronous operation + void + run() + { + // Accept the websocket handshake + ws_.async_accept_ex( + [](websocket::response_type& res) + { + res.set(http::field::server, + "Boost.Beast.async/" + std::to_string(BOOST_BEAST_VERSION)); + }, + strand_.wrap(std::bind( + &async_session::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + return fail(ec, "accept"); + + // Read a message + do_read(); + } + + void + do_read() + { + // Read a message into our buffer + ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &async_session::on_read, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_read(boost::system::error_code ec) + { + // This indicates that the async_session was closed + if(ec == websocket::error::closed) + return; + + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &async_session::on_write, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_write(boost::system::error_code ec) + { + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + + // Do another read + do_read(); + } +}; + +// Accepts incoming connections and launches the sessions +class async_listener : public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + async_listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + do_accept(); + } + + void + do_accept() + { + acceptor_.async_accept( + socket_, + strand_.wrap(std::bind( + &async_listener::on_accept, + shared_from_this(), + std::placeholders::_1))); + } + + void + on_accept(boost::system::error_code ec) + { + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the async_session and run it + std::make_shared(std::move(socket_))->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +void +do_coro_session(tcp::socket& socket, boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + websocket::stream ws{std::move(socket)}; + setup_stream(ws); + + ws.async_accept_ex( + [&](websocket::response_type& res) + { + res.set(http::field::server, + "Boost.Beast.coro/" + std::to_string(BOOST_BEAST_VERSION)); + }, + yield[ec]); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + boost::beast::multi_buffer buffer; + + ws.async_read(buffer, yield[ec]); + if(ec == websocket::error::closed) + break; + if(ec) + return fail(ec, "read"); + + ws.text(ws.got_text()); + ws.async_write(buffer.data(), yield[ec]); + if(ec) + return fail(ec, "write"); + } +} + +void +do_coro_listen( + boost::asio::io_service& ios, + tcp::endpoint endpoint, + boost::asio::yield_context yield) +{ + boost::system::error_code ec; + + tcp::acceptor acceptor(ios); + acceptor.open(endpoint.protocol(), ec); + if(ec) + return fail(ec, "open"); + + acceptor.bind(endpoint, ec); + if(ec) + return fail(ec, "bind"); + + acceptor.listen(boost::asio::socket_base::max_connections, ec); + if(ec) + return fail(ec, "listen"); + + for(;;) + { + tcp::socket socket(ios); + + acceptor.async_accept(socket, yield[ec]); + if(ec) + { + fail(ec, "accept"); + continue; + } + + boost::asio::spawn( + acceptor.get_io_service(), + std::bind( + &do_coro_session, + std::move(socket), + std::placeholders::_1)); + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-fast
\n" << + "Example:\n" + " websocket-server-fast 0.0.0.0 8080 1\n" + " Connect to:\n" + " starting-port+0 for synchronous,\n" + " starting-port+1 for asynchronous,\n" + " starting-port+2 for coroutine.\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create sync port + std::thread(std::bind( + &do_sync_listen, + std::ref(ios), + tcp::endpoint{ + address, + static_cast(port + 0u)} + )).detach(); + + // Create async port + std::make_shared( + ios, + tcp::endpoint{ + address, + static_cast(port + 1u)})->run(); + + // Create coro port + boost::asio::spawn(ios, + std::bind( + &do_coro_listen, + std::ref(ios), + tcp::endpoint{ + address, + static_cast(port + 2u)}, + std::placeholders::_1)); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/stackless-ssl/CMakeLists.txt b/example/websocket/server/stackless-ssl/CMakeLists.txt new file mode 100644 index 00000000..6dfedbd9 --- /dev/null +++ b/example/websocket/server/stackless-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/server/stackless-ssl "/") + +add_executable (websocket-server-stackless-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_stackless_ssl.cpp +) + +target_link_libraries (websocket-server-stackless-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/server/stackless-ssl/Jamfile b/example/websocket/server/stackless-ssl/Jamfile new file mode 100644 index 00000000..fb56a416 --- /dev/null +++ b/example/websocket/server/stackless-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-server-stackless-ssl : + websocket_server_stackless_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp b/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp new file mode 100644 index 00000000..21b4fcbd --- /dev/null +++ b/example/websocket/server/stackless-ssl/websocket_server_stackless_ssl.cpp @@ -0,0 +1,263 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + tcp::socket socket_; + websocket::stream> ws_; + boost::asio::io_service::strand strand_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + session(tcp::socket socket, ssl::context& ctx) + : socket_(std::move(socket)) + , ws_(socket_, ctx) + , strand_(ws_.get_io_service()) + { + } + + // Start the asynchronous operation + void + run() + { + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + // Perform the SSL handshake + yield ws_.next_layer().async_handshake( + ssl::stream_base::server, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "handshake"); + + // Accept the websocket handshake + yield ws_.async_accept( + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // Read a message into our buffer + yield ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec == websocket::error::closed) + { + // This indicates that the session was closed + return; + } + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + yield ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + ssl::context& ctx_; + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + boost::asio::io_service& ios, + ssl::context& ctx, + tcp::endpoint endpoint) + : ctx_(ctx) + , strand_(ios) + , acceptor_(ios) + , socket_(ios) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared(std::move(socket_), ctx_)->run(); + } + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-async-ssl
\n" << + "Example:\n" << + " websocket-server-async-ssl 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // Create and launch a listening port + std::make_shared(ios, ctx, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/stackless/CMakeLists.txt b/example/websocket/server/stackless/CMakeLists.txt new file mode 100644 index 00000000..7fe08f54 --- /dev/null +++ b/example/websocket/server/stackless/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/stackless "/") + +add_executable (websocket-server-stackless + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_server_stackless.cpp +) diff --git a/example/websocket/server/stackless/Jamfile b/example/websocket/server/stackless/Jamfile new file mode 100644 index 00000000..ce9ab898 --- /dev/null +++ b/example/websocket/server/stackless/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-server-stackless : + websocket_server_stackless.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/stackless/websocket_server_stackless.cpp b/example/websocket/server/stackless/websocket_server_stackless.cpp new file mode 100644 index 00000000..eeaf5414 --- /dev/null +++ b/example/websocket/server/stackless/websocket_server_stackless.cpp @@ -0,0 +1,238 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket server, stackless coroutine +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Report a failure +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + +// Echoes back all received WebSocket messages +class session + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + websocket::stream ws_; + boost::asio::io_service::strand strand_; + boost::beast::multi_buffer buffer_; + +public: + // Take ownership of the socket + explicit + session(tcp::socket socket) + : ws_(std::move(socket)) + , strand_(ws_.get_io_service()) + { + } + + // Start the asynchronous operation + void + run() + { + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + // Accept the websocket handshake + yield ws_.async_accept( + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "accept"); + + for(;;) + { + // Read a message into our buffer + yield ws_.async_read( + buffer_, + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec == websocket::error::closed) + { + // This indicates that the session was closed + return; + } + if(ec) + fail(ec, "read"); + + // Echo the message + ws_.text(ws_.got_text()); + yield ws_.async_write( + buffer_.data(), + strand_.wrap(std::bind( + &session::loop, + shared_from_this(), + std::placeholders::_1))); + if(ec) + return fail(ec, "write"); + + // Clear the buffer + buffer_.consume(buffer_.size()); + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +// Accepts incoming connections and launches the sessions +class listener + : public boost::asio::coroutine + , public std::enable_shared_from_this +{ + boost::asio::io_service::strand strand_; + tcp::acceptor acceptor_; + tcp::socket socket_; + +public: + listener( + boost::asio::io_service& ios, + tcp::endpoint endpoint) + : strand_(ios) + , acceptor_(ios) + , socket_(ios) + { + boost::system::error_code ec; + + // Open the acceptor + acceptor_.open(endpoint.protocol(), ec); + if(ec) + { + fail(ec, "open"); + return; + } + + // Bind to the server address + acceptor_.bind(endpoint, ec); + if(ec) + { + fail(ec, "bind"); + return; + } + + // Start listening for connections + acceptor_.listen( + boost::asio::socket_base::max_connections, ec); + if(ec) + { + fail(ec, "listen"); + return; + } + } + + // Start accepting incoming connections + void + run() + { + if(! acceptor_.is_open()) + return; + loop(); + } + +#include + void + loop(boost::system::error_code ec = {}) + { + reenter(*this) + { + for(;;) + { + yield acceptor_.async_accept( + socket_, + std::bind( + &listener::loop, + shared_from_this(), + std::placeholders::_1)); + if(ec) + { + fail(ec, "accept"); + } + else + { + // Create the session and run it + std::make_shared(std::move(socket_))->run(); + } + } + } + } +#include +}; + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + // Check command line arguments. + if (argc != 4) + { + std::cerr << + "Usage: websocket-server-stackless
\n" << + "Example:\n" << + " websocket-server-stackless 0.0.0.0 8080 1\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + auto const threads = std::max(1, std::atoi(argv[3])); + + // The io_service is required for all I/O + boost::asio::io_service ios{threads}; + + // Create and launch a listening port + std::make_shared(ios, tcp::endpoint{address, port})->run(); + + // Run the I/O service on the requested number of threads + std::vector v; + v.reserve(threads - 1); + for(auto i = threads - 1; i > 0; --i) + v.emplace_back( + [&ios] + { + ios.run(); + }); + ios.run(); + + return EXIT_SUCCESS; +} diff --git a/example/websocket/server/sync-ssl/CMakeLists.txt b/example/websocket/server/sync-ssl/CMakeLists.txt new file mode 100644 index 00000000..3ee83df2 --- /dev/null +++ b/example/websocket/server/sync-ssl/CMakeLists.txt @@ -0,0 +1,23 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/common common) +GroupSources(example/websocket/server/sync-ssl "/") + +add_executable (websocket-server-sync-ssl + ${BOOST_BEAST_INCLUDES} + ${PROJECT_SOURCE_DIR}/example/common/server_certificate.hpp + Jamfile + websocket_server_sync_ssl.cpp +) + +target_link_libraries (websocket-server-sync-ssl + ${OPENSSL_LIBRARIES} + ) diff --git a/example/websocket/server/sync-ssl/Jamfile b/example/websocket/server/sync-ssl/Jamfile new file mode 100644 index 00000000..ccdca116 --- /dev/null +++ b/example/websocket/server/sync-ssl/Jamfile @@ -0,0 +1,21 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +project + : requirements + ssl + crypto + ; + +exe websocket-server-sync-ssl : + websocket_server_sync_ssl.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp b/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp new file mode 100644 index 00000000..8328ec78 --- /dev/null +++ b/example/websocket/server/sync-ssl/websocket_server_sync_ssl.cpp @@ -0,0 +1,124 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket SSL server, synchronous +// +//------------------------------------------------------------------------------ + +#include "example/common/server_certificate.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace ssl = boost::asio::ssl; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +void +do_session(tcp::socket& socket, ssl::context& ctx) +{ + try + { + // Construct the websocket stream around the socket + websocket::stream> ws{socket, ctx}; + + // Perform the SSL handshake + ws.next_layer().handshake(ssl::stream_base::server); + + // Accept the websocket handshake + ws.accept(); + + for(;;) + { + // This buffer will hold the incoming message + boost::beast::multi_buffer buffer; + + // Read a message + ws.read(buffer); + + // Echo the message back + ws.text(ws.got_text()); + ws.write(buffer.data()); + } + } + catch(boost::system::system_error const& se) + { + // This indicates that the session was closed + if(se.code() != websocket::error::closed) + std::cerr << "Error: " << se.code().message() << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 3) + { + std::cerr << + "Usage: websocket-server-sync-ssl
\n" << + "Example:\n" << + " websocket-server-sync-ssl 0.0.0.0 8080\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + + // The io_service is required for all I/O + boost::asio::io_service ios{1}; + + // The SSL context is required, and holds certificates + ssl::context ctx{ssl::context::sslv23}; + + // This holds the self-signed certificate used by the server + load_server_certificate(ctx); + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ios}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket), + std::ref(ctx))}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/example/websocket/server/sync/CMakeLists.txt b/example/websocket/server/sync/CMakeLists.txt new file mode 100644 index 00000000..df2dff1d --- /dev/null +++ b/example/websocket/server/sync/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +GroupSources(include/boost/beast beast) +GroupSources(example/websocket/server/sync "/") + +add_executable (websocket-server-sync + ${BOOST_BEAST_INCLUDES} + Jamfile + websocket_server_sync.cpp +) diff --git a/example/websocket/server/sync/Jamfile b/example/websocket/server/sync/Jamfile new file mode 100644 index 00000000..7fd14ce4 --- /dev/null +++ b/example/websocket/server/sync/Jamfile @@ -0,0 +1,15 @@ +# +# Copyright (c) 2016-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) +# +# Official repository: https://github.com/boostorg/beast +# + +exe websocket-server-sync : + websocket_server_sync.cpp + : + coverage:no + ubasan:no + ; diff --git a/example/websocket/server/sync/websocket_server_sync.cpp b/example/websocket/server/sync/websocket_server_sync.cpp new file mode 100644 index 00000000..8ba0f3f6 --- /dev/null +++ b/example/websocket/server/sync/websocket_server_sync.cpp @@ -0,0 +1,109 @@ +// +// Copyright (c) 2016-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) +// +// Official repository: https://github.com/boostorg/beast +// + +//------------------------------------------------------------------------------ +// +// Example: WebSocket server, synchronous +// +//------------------------------------------------------------------------------ + +#include +#include +#include +#include +#include +#include +#include +#include + +using tcp = boost::asio::ip::tcp; // from +namespace websocket = boost::beast::websocket; // from + +//------------------------------------------------------------------------------ + +// Echoes back all received WebSocket messages +void +do_session(tcp::socket& socket) +{ + try + { + // Construct the stream by moving in the socket + websocket::stream ws{std::move(socket)}; + + // Accept the websocket handshake + ws.accept(); + + for(;;) + { + // This buffer will hold the incoming message + boost::beast::multi_buffer buffer; + + // Read a message + ws.read(buffer); + + // Echo the message back + ws.text(ws.got_text()); + ws.write(buffer.data()); + } + } + catch(boost::system::system_error const& se) + { + // This indicates that the session was closed + if(se.code() != websocket::error::closed) + std::cerr << "Error: " << se.code().message() << std::endl; + } + catch(std::exception const& e) + { + std::cerr << "Error: " << e.what() << std::endl; + } +} + +//------------------------------------------------------------------------------ + +int main(int argc, char* argv[]) +{ + try + { + // Check command line arguments. + if (argc != 3) + { + std::cerr << + "Usage: websocket-server-sync
\n" << + "Example:\n" << + " websocket-server-sync 0.0.0.0 8080\n"; + return EXIT_FAILURE; + } + auto const address = boost::asio::ip::address::from_string(argv[1]); + auto const port = static_cast(std::atoi(argv[2])); + + // The io_service is required for all I/O + boost::asio::io_service ios{1}; + + // The acceptor receives incoming connections + tcp::acceptor acceptor{ios, {address, port}}; + for(;;) + { + // This will receive the new connection + tcp::socket socket{ios}; + + // Block until we get a connection + acceptor.accept(socket); + + // Launch the session, transferring ownership of the socket + std::thread{std::bind( + &do_session, + std::move(socket))}.detach(); + } + } + catch (const std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return EXIT_FAILURE; + } +} diff --git a/include/boost/beast/websocket/impl/close.ipp b/include/boost/beast/websocket/impl/close.ipp index fc62353e..e18d489c 100644 --- a/include/boost/beast/websocket/impl/close.ipp +++ b/include/boost/beast/websocket/impl/close.ipp @@ -65,13 +65,8 @@ public: { } - void operator()() - { - (*this)({}); - } - void - operator()(error_code ec, + operator()(error_code ec = {}, std::size_t bytes_transferred = 0); friend diff --git a/test/bench/wsload/wsload.cpp b/test/bench/wsload/wsload.cpp index 10de155e..7650a05e 100644 --- a/test/bench/wsload/wsload.cpp +++ b/test/bench/wsload/wsload.cpp @@ -7,7 +7,14 @@ // Official repository: https://github.com/boostorg/beast // -#include +//------------------------------------------------------------------------------ +// +// wsload +// +// Measure the performance of a WebSocket server +// +//------------------------------------------------------------------------------ + #include #include @@ -75,6 +82,12 @@ public: } }; +void +fail(boost::system::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} + class connection : public std::enable_shared_from_this { @@ -132,20 +145,12 @@ public: } private: - void - fail(boost::beast::string_view what, error_code ec) - { - if( ec == asio::error::operation_aborted || - ec == ws::error::closed) - return; - print(log_, "[", ep_, "] ", what, ": ", ec.message()); - } - void on_connect(error_code ec) { if(ec) - return fail("on_connect", ec); + return fail(ec, "on_connect"); + ws_.async_handshake( ep_.address().to_string() + ":" + std::to_string(ep_.port()), "/", @@ -159,7 +164,8 @@ private: on_handshake(error_code ec) { if(ec) - return fail("on_connect", ec); + return fail(ec, "handshake"); + do_write(); } @@ -180,9 +186,11 @@ private: on_write(error_code ec) { if(ec) - return fail("on_read", ec); + return fail(ec, "write"); + if(messages_--) return do_read(); + ws_.async_close({}, alloc_.wrap(std::bind( &connection::on_close, @@ -204,7 +212,8 @@ private: on_read(error_code ec) { if(ec) - return fail("on_read", ec); + return fail(ec, "read"); + ++count_; bytes_ += buffer_.size(); buffer_.consume(buffer_.size()); @@ -215,7 +224,7 @@ private: on_close(error_code ec) { if(ec) - return fail("on_close", ec); + return fail(ec, "close"); } }; @@ -264,8 +273,7 @@ main(int argc, char** argv) if(argc != 8) { std::cerr << - "Usage: " << argv[0] << - "
"; + "Usage: bench-wsload
"; return EXIT_FAILURE; } @@ -316,7 +324,7 @@ main(int argc, char** argv) } catch(std::exception const& e) { - std::cerr << "Exception: " << e.what() << std::endl; + std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } diff --git a/test/example/CMakeLists.txt b/test/example/CMakeLists.txt index 7089152b..00d48e8e 100644 --- a/test/example/CMakeLists.txt +++ b/test/example/CMakeLists.txt @@ -8,4 +8,3 @@ # add_subdirectory (common) -add_subdirectory (server) diff --git a/test/example/Jamfile b/test/example/Jamfile index f065de66..cab496f0 100644 --- a/test/example/Jamfile +++ b/test/example/Jamfile @@ -9,7 +9,6 @@ alias run-tests : common//run-tests - server//run-tests ; alias fat-tests ; diff --git a/test/example/common/CMakeLists.txt b/test/example/common/CMakeLists.txt index 3c308872..6c73d797 100644 --- a/test/example/common/CMakeLists.txt +++ b/test/example/common/CMakeLists.txt @@ -17,8 +17,8 @@ add_executable (tests-example-common ${COMMON_INCLUDES} Jamfile detect_ssl.cpp - mime_types.cpp - rfc7231.cpp + root_certificates.cpp + server_certificate.cpp session_alloc.cpp ssl_stream.cpp write_msg.cpp diff --git a/test/example/common/Jamfile b/test/example/common/Jamfile index 5445122c..d5e58c98 100644 --- a/test/example/common/Jamfile +++ b/test/example/common/Jamfile @@ -9,8 +9,8 @@ local SOURCES = detect_ssl.cpp - mime_types.cpp - rfc7231.cpp + root_certificates.cpp + server_certificate.cpp session_alloc.cpp ssl_stream.cpp write_msg.cpp diff --git a/test/example/common/rfc7231.cpp b/test/example/common/rfc7231.cpp deleted file mode 100644 index 57792fac..00000000 --- a/test/example/common/rfc7231.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/common/rfc7231.hpp" - diff --git a/test/example/common/mime_types.cpp b/test/example/common/root_certificates.cpp similarity index 87% rename from test/example/common/mime_types.cpp rename to test/example/common/root_certificates.cpp index 7d9945d3..70ebacce 100644 --- a/test/example/common/mime_types.cpp +++ b/test/example/common/root_certificates.cpp @@ -8,5 +8,5 @@ // // Test that header file is self-contained. -#include "example/common/mime_types.hpp" +#include "example/common/root_certificates.hpp" diff --git a/test/example/server/server.cpp b/test/example/common/server_certificate.cpp similarity index 87% rename from test/example/server/server.cpp rename to test/example/common/server_certificate.cpp index 6e584f15..40bd10fe 100644 --- a/test/example/server/server.cpp +++ b/test/example/common/server_certificate.cpp @@ -8,5 +8,5 @@ // // Test that header file is self-contained. -#include "example/server-framework/server.hpp" +#include "example/common/server_certificate.hpp" diff --git a/test/example/server/CMakeLists.txt b/test/example/server/CMakeLists.txt deleted file mode 100644 index 4b29690c..00000000 --- a/test/example/server/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -GroupSources(example/server-framework framework) -GroupSources(example/common common) -GroupSources(test/extras/include/boost/beast extras) -GroupSources(include/boost/beast beast) -GroupSources(test/example/server "/") - -add_executable (tests-example-server - ${BOOST_BEAST_INCLUDES} - ${COMMON_INCLUDES} - ${SERVER_INCLUDES} - ${TEST_MAIN} - Jamfile - file_service.cpp - framework.cpp - http_async_port.cpp - http_base.cpp - http_sync_port.cpp - https_ports.cpp - multi_port.cpp - server.cpp - service_list.cpp - ssl_certificate - tests.cpp - ws_async_port.cpp - ws_sync_port.cpp - ws_upgrade_service.cpp - wss_ports.cpp -) diff --git a/test/example/server/Jamfile b/test/example/server/Jamfile deleted file mode 100644 index 80747d9b..00000000 --- a/test/example/server/Jamfile +++ /dev/null @@ -1,39 +0,0 @@ -# -# Copyright (c) 2016-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) -# -# Official repository: https://github.com/boostorg/beast -# - -local SOURCES = - file_service.cpp - framework.cpp - http_async_port.cpp - http_base.cpp - http_sync_port.cpp - https_ports.cpp - multi_port.cpp - server.cpp - service_list.cpp - ssl_certificate.cpp - #tests.cpp - ws_async_port.cpp - ws_sync_port.cpp - ws_upgrade_service.cpp - wss_ports.cpp - ; - -local RUN_TESTS ; - -for local f in $(SOURCES) -{ - RUN_TESTS += [ compile $(f) ] ; -} - -alias run-tests : $(RUN_TESTS) ; - -alias fat-tests : run-tests ; - -explicit fat-tests ; diff --git a/test/example/server/file_service.cpp b/test/example/server/file_service.cpp deleted file mode 100644 index 2e6d65c6..00000000 --- a/test/example/server/file_service.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/file_service.hpp" - diff --git a/test/example/server/framework.cpp b/test/example/server/framework.cpp deleted file mode 100644 index 383906a2..00000000 --- a/test/example/server/framework.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/framework.hpp" - diff --git a/test/example/server/http_async_port.cpp b/test/example/server/http_async_port.cpp deleted file mode 100644 index 3bc5cbe5..00000000 --- a/test/example/server/http_async_port.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/http_async_port.hpp" - diff --git a/test/example/server/http_base.cpp b/test/example/server/http_base.cpp deleted file mode 100644 index f44444de..00000000 --- a/test/example/server/http_base.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/http_base.hpp" - diff --git a/test/example/server/http_sync_port.cpp b/test/example/server/http_sync_port.cpp deleted file mode 100644 index 62220301..00000000 --- a/test/example/server/http_sync_port.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/http_sync_port.hpp" - diff --git a/test/example/server/https_ports.cpp b/test/example/server/https_ports.cpp deleted file mode 100644 index b4a82622..00000000 --- a/test/example/server/https_ports.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#if BOOST_BEAST_USE_OPENSSL - -// Test that header file is self-contained. -#include "example/server-framework/https_ports.hpp" - -#endif diff --git a/test/example/server/multi_port.cpp b/test/example/server/multi_port.cpp deleted file mode 100644 index 64481361..00000000 --- a/test/example/server/multi_port.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#if BOOST_BEAST_USE_OPENSSL - -// Test that header file is self-contained. -#include "example/server-framework/multi_port.hpp" - -#endif - diff --git a/test/example/server/service_list.cpp b/test/example/server/service_list.cpp deleted file mode 100644 index 99a5000a..00000000 --- a/test/example/server/service_list.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/service_list.hpp" - diff --git a/test/example/server/ssl_certificate.cpp b/test/example/server/ssl_certificate.cpp deleted file mode 100644 index dfa11632..00000000 --- a/test/example/server/ssl_certificate.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#if BOOST_BEAST_USE_OPENSSL - -// Test that header file is self-contained. -#include "example/server-framework/ssl_certificate.hpp" - -#endif diff --git a/test/example/server/tests.cpp b/test/example/server/tests.cpp deleted file mode 100644 index 833ff2d3..00000000 --- a/test/example/server/tests.cpp +++ /dev/null @@ -1,320 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#include "example/server-framework/http_sync_port.hpp" -#include "example/server-framework/http_async_port.hpp" -#include "example/server-framework/ws_sync_port.hpp" -#include "example/server-framework/ws_async_port.hpp" -#include "example/server-framework/ws_upgrade_service.hpp" - -#if BOOST_BEAST_USE_OPENSSL -# include "example/server-framework/https_ports.hpp" -# include "example/server-framework/multi_port.hpp" -# include "example/server-framework/ssl_certificate.hpp" -# include "example/server-framework/wss_ports.hpp" -#endif - -#include -#include -#include - -namespace boost { -namespace beast { -namespace websocket { - -class server_test - : public beast::unit_test::suite - , public test::enable_yield_to -{ -public: - static unsigned short constexpr port_num = 6000; - - 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); - } - }; - - set_ws_options - get_ws_options() - { - beast::websocket::permessage_deflate pmd; - pmd.client_enable = true; - pmd.server_enable = true; - pmd.compLevel = 3; - return set_ws_options{pmd}; - } - - template - void - doOptions(Stream& stream, error_code& ec) - { - http::request req; - req.version = 11; - req.method(http::verb::options); - req.target("*"); - req.set(http::field::user_agent, "test"); - req.set(http::field::connection, "close"); - - http::write(stream, req, ec); - if(! BEAST_EXPECTS( - ec == http::error::end_of_stream, - ec.message())) - return; - - beast::flat_buffer b; - http::response res; - http::read(stream, b, res, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - } - - template - void - doHello(stream& ws, error_code& ec) - { - ws.handshake("localhost", "/", ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - ws.write(boost::asio::buffer(std::string("Hello, world!")), ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - beast::multi_buffer b; - ws.read(b, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - ws.close(beast::websocket::close_code::normal, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - } - - void - httpClient(framework::endpoint_type const& ep) - { - error_code ec; - boost::asio::ip::tcp::socket con{ios_}; - con.connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - doOptions(con, ec); - } - - void - wsClient(framework::endpoint_type const& ep) - { - error_code ec; - stream ws{ios_}; - ws.next_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - doHello(ws, ec); - } - - void - testPlain() - { - using namespace framework; - - // ws sync - { - error_code ec; - server instance; - auto const ep1 = endpoint_type{ - address_type::from_string("127.0.0.1"), port_num}; - auto const wsp = instance.make_port( - ec, ep1, instance, log, get_ws_options()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - auto const ep2 = endpoint_type{ - address_type::from_string("127.0.0.1"), - static_cast(port_num + 1)}; - auto const sp = instance.make_port< - http_sync_port>>( - ec, ep2, instance, log); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - sp->template init<0>(ec, *wsp); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - - wsClient(ep1); - wsClient(ep2); - - httpClient(ep2); - } - - // ws async - { - error_code ec; - server instance; - auto const ep1 = endpoint_type{ - address_type::from_string("127.0.0.1"), port_num}; - auto const wsp = instance.make_port( - ec, ep1, instance, log, get_ws_options()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - auto const ep2 = endpoint_type{ - address_type::from_string("127.0.0.1"), - static_cast(port_num + 1)}; - auto const sp = instance.make_port< - http_async_port>>( - ec, ep2, instance, log); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - sp->template init<0>(ec, *wsp); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - - wsClient(ep1); - wsClient(ep2); - - httpClient(ep2); - } - } - -#if BOOST_BEAST_USE_OPENSSL - // - // OpenSSL enabled ports - // - - void - httpsClient(framework::endpoint_type const& ep, - boost::asio::ssl::context& ctx) - { - error_code ec; - boost::asio::ssl::stream con{ios_, ctx}; - con.next_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - con.handshake( - boost::asio::ssl::stream_base::client, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - doOptions(con, ec); - if(ec) - return; - con.shutdown(ec); - // VFALCO No idea why we get eof in the normal case - if(ec == boost::asio::error::eof) - ec.assign(0, ec.category()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - } - - void - wssClient(framework::endpoint_type const& ep, - boost::asio::ssl::context& ctx) - { - error_code ec; - stream> wss{ios_, ctx}; - wss.next_layer().next_layer().connect(ep, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - wss.next_layer().handshake( - boost::asio::ssl::stream_base::client, ec); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - doHello(wss, ec); - } - - void - testSSL() - { - using namespace framework; - - ssl_certificate cert; - - // wss sync - { - error_code ec; - server instance; - auto const ep1 = endpoint_type{ - address_type::from_string("127.0.0.1"), port_num}; - auto const wsp = instance.make_port( - ec, ep1, instance, log, cert.get(), get_ws_options()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - auto const ep2 = endpoint_type{ - address_type::from_string("127.0.0.1"), - static_cast(port_num + 1)}; - auto const sp = instance.make_port< - https_sync_port>>( - ec, ep2, instance, log, cert.get()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - sp->template init<0>(ec, *wsp); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - - wssClient(ep1, cert.get()); - wssClient(ep2, cert.get()); - - httpsClient(ep2, cert.get()); - } - - // wss async - { - error_code ec; - server instance; - auto const ep1 = endpoint_type{ - address_type::from_string("127.0.0.1"), port_num}; - auto const wsp = instance.make_port( - ec, ep1, instance, log, cert.get(), get_ws_options()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - auto const ep2 = endpoint_type{ - address_type::from_string("127.0.0.1"), - static_cast(port_num + 1)}; - auto const sp = instance.make_port< - https_async_port>>( - ec, ep2, instance, log, cert.get()); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - sp->template init<0>(ec, *wsp); - if(! BEAST_EXPECTS(! ec, ec.message())) - return; - - wssClient(ep1, cert.get()); - wssClient(ep2, cert.get()); - - httpsClient(ep2, cert.get()); - } - } -#endif - - void - run() override - { - testPlain(); - - #if BOOST_BEAST_USE_OPENSSL - testSSL(); - #endif - } -}; - -BEAST_DEFINE_TESTSUITE(beast,websocket,server); - -} // websocket -} // beast -} // boost diff --git a/test/example/server/ws_async_port.cpp b/test/example/server/ws_async_port.cpp deleted file mode 100644 index 1f8ce846..00000000 --- a/test/example/server/ws_async_port.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/ws_async_port.hpp" - diff --git a/test/example/server/ws_sync_port.cpp b/test/example/server/ws_sync_port.cpp deleted file mode 100644 index 7ac15395..00000000 --- a/test/example/server/ws_sync_port.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/ws_sync_port.hpp" - diff --git a/test/example/server/ws_upgrade_service.cpp b/test/example/server/ws_upgrade_service.cpp deleted file mode 100644 index 73fdb4d4..00000000 --- a/test/example/server/ws_upgrade_service.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -// Test that header file is self-contained. -#include "example/server-framework/ws_upgrade_service.hpp" - diff --git a/test/example/server/wss_ports.cpp b/test/example/server/wss_ports.cpp deleted file mode 100644 index 719341c8..00000000 --- a/test/example/server/wss_ports.cpp +++ /dev/null @@ -1,15 +0,0 @@ -// -// Copyright (c) 2016-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) -// -// Official repository: https://github.com/boostorg/beast -// - -#if BOOST_BEAST_USE_OPENSSL - -// Test that header file is self-contained. -#include "example/server-framework/wss_ports.hpp" - -#endif