From fc9830aa2451df99910785a6d9e0927ef2b9bb38 Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sun, 4 Feb 2018 05:19:58 +0100 Subject: [PATCH] Started to work on a basic example using boost asio and beast --- dep/CMakeLists.txt | 4 + dep/boost/CMakeLists.txt | 36 ++++ examples/CMakeLists.txt | 1 + examples/example-boost/CMakeLists.txt | 23 +++ examples/example-boost/example-boost.cpp | 84 ++++++++ examples/example-boost/server.cpp | 249 +++++++++++++++++++++++ examples/example-boost/server.hpp | 63 ++++++ 7 files changed, 460 insertions(+) create mode 100644 dep/boost/CMakeLists.txt create mode 100644 examples/example-boost/CMakeLists.txt create mode 100644 examples/example-boost/example-boost.cpp create mode 100644 examples/example-boost/server.cpp create mode 100644 examples/example-boost/server.hpp diff --git a/dep/CMakeLists.txt b/dep/CMakeLists.txt index 5f5344b..b3f70dc 100644 --- a/dep/CMakeLists.txt +++ b/dep/CMakeLists.txt @@ -14,4 +14,8 @@ if (CTI_CONTINUABLE_WITH_TESTS OR CTI_CONTINUABLE_WITH_EXAMPLES) if(NOT TARGET asio) add_subdirectory(asio) endif() + + if(NOT TARGET boost) + add_subdirectory(boost) + endif() endif() diff --git a/dep/boost/CMakeLists.txt b/dep/boost/CMakeLists.txt new file mode 100644 index 0000000..8d836b2 --- /dev/null +++ b/dep/boost/CMakeLists.txt @@ -0,0 +1,36 @@ +if(WIN32) + if(CMAKE_SIZEOF_VOID_P MATCHES 8) + set(PLATFORM 64) + else() + set(PLATFORM 32) + endif() + + if(DEFINED ENV{BOOST_ROOT}) + set(BOOST_ROOT $ENV{BOOST_ROOT}) + set(BOOST_LIBRARYDIR ${BOOST_ROOT}/lib${PLATFORM}-msvc-14.1) + endif() + + set(Boost_USE_STATIC_LIBS ON) + set(Boost_USE_MULTITHREADED ON) + set(Boost_USE_STATIC_RUNTIME OFF) +endif() + +find_package(Boost 1.66 + COMPONENTS + system + iostreams) + +if (${Boost_FOUND}) + add_library(boost INTERFACE) + + target_link_libraries(boost + INTERFACE + Boost::system + Boost::iostreams) + + target_compile_definitions(boost + INTERFACE + -DBOOST_DATE_TIME_NO_LIB + -DBOOST_REGEX_NO_LIB + -DBOOST_CHRONO_NO_LIB) +endif() diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 64741a9..b3d3010 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,3 @@ +add_subdirectory(example-boost) add_subdirectory(example-asio) add_subdirectory(example-ai) diff --git a/examples/example-boost/CMakeLists.txt b/examples/example-boost/CMakeLists.txt new file mode 100644 index 0000000..efd949a --- /dev/null +++ b/examples/example-boost/CMakeLists.txt @@ -0,0 +1,23 @@ +if (NOT ${Boost_FOUND}) + message(STATUS "Boost not found, skipping boost examples") + return() +endif() + +add_executable(example-boost + ${CMAKE_CURRENT_LIST_DIR}/server.hpp + ${CMAKE_CURRENT_LIST_DIR}/server.cpp + ${CMAKE_CURRENT_LIST_DIR}/example-boost.cpp) + +target_include_directories(example-boost + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}) + +target_link_libraries(example-boost + PRIVATE + continuable + boost) + +target_compile_definitions(example-boost + PUBLIC + -DCONTINUABLE_WITH_NO_EXCEPTIONS) + diff --git a/examples/example-boost/example-boost.cpp b/examples/example-boost/example-boost.cpp new file mode 100644 index 0000000..7b42211 --- /dev/null +++ b/examples/example-boost/example-boost.cpp @@ -0,0 +1,84 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +int main(int, char**) { + namespace http = boost::beast::http; + + auto server = http_server::create("0.0.0.0", 8080); + + boost::asio::deadline_timer t + + server->listen([](request_t const& req) -> cti::continuable { + return cti::make_continuable([&](auto&& promise) { + http::response res{http::status::ok, req.version()}; + + // Build the response + 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() = "Hello World!"; + res.prepare_payload(); + + // Resolve the request asynchronously + promise.set_value(res); + }); + }); + + // Construct a signal set registered for process termination. + boost::asio::signal_set signals(context); + signals.add(SIGTERM); + signals.add(SIGINT); + + // Run the I/O service on the requested number of threads + { + auto const threads = std::thread::hardware_concurrency(); + std::vector v; + v.reserve(threads - 1); + for (auto i = threads - 1; i > 0; --i) + v.emplace_back([&context] { context.run(); }); + context.run(); + } + + return 0; +} diff --git a/examples/example-boost/server.cpp b/examples/example-boost/server.cpp new file mode 100644 index 0000000..ae4865e --- /dev/null +++ b/examples/example-boost/server.cpp @@ -0,0 +1,249 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +// This file is based on the original boost beast example located at +// http://www.boost.org/doc/libs/1_66_0/libs/beast/example/http/server/async/http_server_async.cpp +// with the following license: +// +// 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 + +#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 + +// 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 { + tcp::socket socket_; + handler_t const& callback_; + boost::asio::strand strand_; + boost::beast::flat_buffer buffer_; + http::request req_; + std::shared_ptr res_; + +public: + // Take ownership of the socket + explicit session(tcp::socket socket, handler_t const& callback) + : socket_(std::move(socket)), callback_(callback), + strand_(socket_.get_executor()) { + } + + // Start the asynchronous operation + void run() { + do_read(); + } + + void do_read() { + // Read a request + http::async_read( + socket_, buffer_, req_, + boost::asio::bind_executor( + strand_, std::bind(&session::on_read, shared_from_this(), + std::placeholders::_1, std::placeholders::_2))); + } + + void on_read(boost::system::error_code ec, std::size_t bytes_transferred) { + boost::ignore_unused(bytes_transferred); + + // 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 + callback_(std::move(req_)).then([this](auto&& msg) { + // The lifetime of the message has to extend + // for the duration of the async operation so + // we use a shared_ptr to manage it. + auto sp = std::make_shared>(std::move(msg)); + + // Store a type-erased version of the shared + // pointer in the class to keep it alive. + res_ = sp; + + // Write the response + http::async_write( + socket_, *sp, + boost::asio::bind_executor( + strand_, std::bind(&session::on_write, shared_from_this(), + std::placeholders::_1, std::placeholders::_2, + sp->need_eof()))); + }); + } + + void on_write(boost::system::error_code ec, std::size_t bytes_transferred, + bool close) { + boost::ignore_unused(bytes_transferred); + + if (ec) + return fail(ec, "write"); + + if (close) { + // This means we should close the connection, usually because + // the response indicated the "Connection: close" semantic. + return do_close(); + } + + // We're done with the response so delete it + res_ = nullptr; + + // 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 { + tcp::acceptor acceptor_; + tcp::socket socket_; + handler_t callback_; + +public: + listener(boost::asio::io_context& ioc, tcp::endpoint endpoint, + handler_t&& callback) + : acceptor_(ioc), socket_(ioc), callback_(std::move(callback)) { + 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_listen_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_), callback_)->run(); + } + + // Accept another connection + do_accept(); + } +}; + +//------------------------------------------------------------------------------ + +struct http_server_impl : http_server +{ + +}; + +std::shared_ptr http_server::create(std::string address, unsigned short port) { + + + + return std::make_shared<> +} + + +void create_server(std::string address, unsigned short port, + handler_t callback) { + + auto const target = boost::asio::ip::make_address(address.c_str()); + + // Create and launch a listening port + std::make_shared(context, tcp::endpoint{target, port}, + std::move(callback)) + ->run(); +} diff --git a/examples/example-boost/server.hpp b/examples/example-boost/server.hpp new file mode 100644 index 0000000..fadc319 --- /dev/null +++ b/examples/example-boost/server.hpp @@ -0,0 +1,63 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v2.0.0 + + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_EXAMPLES_SERVER_HPP_INCLUDED +#define CONTINUABLE_EXAMPLES_SERVER_HPP_INCLUDED + +#include +#include + +#include +#include +#include +#include + +using request_t = boost::beast::http::request; +using response_t = + boost::beast::http::response; + +using handler_t = + fu2::unique_function(request_t const&) const>; + +struct http_server { + virtual ~http_server() = default; + + /// Creates a minimal asynchronous http server using boost asio and beast + static std::shared_ptr create(std::string address, + unsigned short port); + + virtual void listen(handler_t); + + virtual boost::asio::io_context& context(); + + virtual void stop(); +}; + +#endif // CONTINUABLE_EXAMPLES_SERVER_HPP_INCLUDED