diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 9da1f06..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,25 +0,0 @@ -cmake_minimum_required(VERSION 3.30.5) -project(conan2_template) - -set(CMAKE_CXX_STANDARD 17) - -if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake-build-debug/conan_toolchain.cmake) -elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") - include(${CMAKE_CURRENT_SOURCE_DIR}/cmake-build-release/conan_toolchain.cmake) -elseif () - message(ERROR!!!) -endif () - -find_package(Boost REQUIRED) - -add_executable(App ./src/main.cpp - ./src/helpers/helpers.h - ./src/helpers/helpers.cpp - ./src/endpoints_handlers/HandleRequest.h) - -target_link_libraries(App PRIVATE Boost::boost) - -if (MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj") -endif () \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 2132949..0000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -conan install . -pr ./profiles/debug_profile --output-folder=cmake-build-debug --build=missing - -conan install . -pr ./profiles/release_profile --output-folder=cmake-build-release --build=missing diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 6a55361..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,6 +0,0 @@ -[requires] -boost/1.78.0 - -[generators] -CMakeDeps -CMakeToolchain diff --git a/profiles/debug_profile b/profiles/debug_profile deleted file mode 100644 index e0d4d57..0000000 --- a/profiles/debug_profile +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=x86_64 -build_type=Debug -compiler=msvc -compiler.cppstd=17 -compiler.runtime=dynamic -compiler.version=193 -os=Windows diff --git a/profiles/release_profile b/profiles/release_profile deleted file mode 100644 index fa6e904..0000000 --- a/profiles/release_profile +++ /dev/null @@ -1,8 +0,0 @@ -[settings] -arch=x86_64 -build_type=Release -compiler=msvc -compiler.cppstd=17 -compiler.runtime=dynamic -compiler.version=193 -os=Windows diff --git a/src/endpoints_handlers/HandleRequest.h b/src/endpoints_handlers/HandleRequest.h deleted file mode 100644 index 8c5d3de..0000000 --- a/src/endpoints_handlers/HandleRequest.h +++ /dev/null @@ -1,82 +0,0 @@ -#include - -namespace uad { -template -void HandleRequest(boost::beast::string_view doc_root, - boost::beast::http::request> &&req, - Send &&send) { - auto const bad_request = [&req](beast::string_view why) { - boost::beast::http::response res{boost::beast::http::status::bad_request, - req.version()}; - res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = std::string(why); - res.prepare_payload(); - return res; - }; - - auto const not_found = [&req](beast::string_view target) { - boost::beast::http::response res{boost::beast::http::status::not_found, - req.version()}; - res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = "The resource '" + std::string(target) + "' was not found."; - res.prepare_payload(); - return res; - }; - - auto const server_error = [&req](beast::string_view what) { - boost::beast::http::response res{boost::beast::http::status::internal_server_error, - req.version()}; - res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, "text/html"); - res.keep_alive(req.keep_alive()); - res.body() = "An error occurred: '" + std::string(what) + "'"; - res.prepare_payload(); - return res; - }; - - if (req.method() != boost::beast::http::verb::get && req.method() != boost::beast::http::verb::head) - return send(bad_request("Unknown boost::beast::HTTP-method")); - - if (req.target().empty() || req.target()[0] != '/' || - req.target().find("..") != beast::string_view::npos) - return send(bad_request("Illegal request-target")); - - std::string path = PathCat(doc_root, req.target()); - if (req.target().back() == '/') - path.append("index.html"); - - beast::error_code ec; - boost::beast::http::file_body::value_type body; - body.open(path.c_str(), beast::file_mode::scan, ec); - - if (ec == beast::errc::no_such_file_or_directory) - return send(not_found(req.target())); - - if (ec) - return send(server_error(ec.message())); - - auto const size = body.size(); - - if (req.method() == boost::beast::http::verb::head) { - boost::beast::http::response res{boost::beast::http::status::ok, req.version()}; - res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, MimeType(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); - } - - boost::beast::http::response res{ - std::piecewise_construct, std::make_tuple(std::move(body)), - std::make_tuple(boost::beast::http::status::ok, req.version())}; - res.set(boost::beast::http::field::server, BOOST_BEAST_VERSION_STRING); - res.set(boost::beast::http::field::content_type, MimeType(path)); - res.content_length(size); - res.keep_alive(req.keep_alive()); - return send(std::move(res)); -} -} diff --git a/src/helpers/helpers.cpp b/src/helpers/helpers.cpp deleted file mode 100644 index 5e6730d..0000000 --- a/src/helpers/helpers.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "helpers.h" - -namespace uad { -boost::beast::string_view MimeType(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"; -} - -std::string PathCat(boost::beast::string_view base, boost::beast::string_view path){ - if (base.empty()) - return std::string(path); - std::string result(base); -#ifdef 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; -} -} diff --git a/src/helpers/helpers.h b/src/helpers/helpers.h deleted file mode 100644 index 37cb506..0000000 --- a/src/helpers/helpers.h +++ /dev/null @@ -1,7 +0,0 @@ -#include - -namespace uad { -boost::beast::string_view MimeType(boost::beast::string_view path); - -std::string PathCat(boost::beast::string_view base, boost::beast::string_view path); -} diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index 467ff18..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,404 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "./helpers/helpers.h" -#include "./endpoints_handlers/HandleRequest.h" - -namespace beast = boost::beast; -namespace http = beast::http; -namespace websocket = beast::websocket; -namespace net = boost::asio; -using tcp = boost::asio::ip::tcp; -using namespace uad; - -//------------------------------------------------------------------------------ - -// Report a failure -void fail(beast::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_; - beast::flat_buffer buffer_; - -public: - // Take ownership of the socket - explicit websocket_session(tcp::socket &&socket) : ws_(std::move(socket)) {} - - // Start the asynchronous accept operation - template - void do_accept(http::request> req) { - // Set suggested timeout settings for the websocket - ws_.set_option( - websocket::stream_base::timeout::suggested(beast::role_type::server)); - - // Set a decorator to change the Server of the handshake - ws_.set_option( - websocket::stream_base::decorator([](websocket::response_type &res) { - res.set(http::field::server, - std::string(BOOST_BEAST_VERSION_STRING) + " advanced-server"); - })); - - // Accept the websocket handshake - ws_.async_accept(req, - beast::bind_front_handler(&websocket_session::on_accept, - shared_from_this())); - } - -private: - void on_accept(beast::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_, - beast::bind_front_handler(&websocket_session::on_read, - shared_from_this())); - } - - void on_read(beast::error_code ec, std::size_t bytes_transferred) { - boost::ignore_unused(bytes_transferred); - - // 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(), - beast::bind_front_handler(&websocket_session::on_write, - shared_from_this())); - } - - void on_write(beast::error_code ec, std::size_t bytes_transferred) { - boost::ignore_unused(bytes_transferred); - - 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; - }; - - 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() >= limit; } - - // Called when a message finishes sending - // Returns `true` if the caller should initiate a read - bool on_write() { - BOOST_ASSERT(!items_.empty()); - auto const was_full = is_full(); - items_.erase(items_.begin()); - if (!items_.empty()) - (*items_.front())(); - return was_full; - } - - // Called by the HTTP handler to send a response. - template - void operator()(http::message &&msg) { - // 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()() { - http::async_write(self_.stream_, msg_, - beast::bind_front_handler(&http_session::on_write, - self_.shared_from_this(), - msg_.need_eof())); - } - }; - - // Allocate and store the work - items_.push_back(boost::make_unique(self_, std::move(msg))); - - // If there was no previous work, start this one - if (items_.size() == 1) - (*items_.front())(); - } - }; - - beast::tcp_stream stream_; - beast::flat_buffer buffer_; - std::shared_ptr doc_root_; - queue queue_; - - // The parser is stored in an optional container so we can - // construct it from scratch it at the beginning of each new message. - boost::optional> parser_; - -public: - // Take ownership of the socket - http_session(tcp::socket &&socket, - std::shared_ptr const &doc_root) - : stream_(std::move(socket)), doc_root_(doc_root), queue_(*this) {} - - // Start the session - void run() { - // We need to be executing within a strand to perform async operations - // on the I/O objects in this session. Although not strictly necessary - // for single-threaded contexts, this example code is written to be - // thread-safe by default. - net::dispatch(stream_.get_executor(), - beast::bind_front_handler(&http_session::do_read, - this->shared_from_this())); - } - -private: - void do_read() { - // Construct a new parser for each message - parser_.emplace(); - - // Apply a reasonable limit to the allowed size - // of the body in bytes to prevent abuse. - parser_->body_limit(10000); - - // Set the timeout. - stream_.expires_after(std::chrono::seconds(30)); - - // Read a request using the parser-oriented interface - http::async_read( - stream_, buffer_, *parser_, - beast::bind_front_handler(&http_session::on_read, shared_from_this())); - } - - void on_read(beast::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"); - - // See if it is a WebSocket Upgrade - if (websocket::is_upgrade(parser_->get())) { - // Create a websocket session, transferring ownership - // of both the socket and the HTTP request. - std::make_shared(stream_.release_socket()) - ->do_accept(parser_->release()); - return; - } - - // Send the response - HandleRequest(*doc_root_, parser_->release(), queue_); - - // If we aren't at the queue limit, try to pipeline another request - if (!queue_.is_full()) - do_read(); - } - - void on_write(bool close, beast::error_code ec, - std::size_t bytes_transferred) { - 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(); - } - - // Inform the queue that a write completed - if (queue_.on_write()) { - // Read another request - do_read(); - } - } - - void do_close() { - // Send a TCP shutdown - beast::error_code ec; - stream_.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 { - net::io_context &ioc_; - tcp::acceptor acceptor_; - std::shared_ptr doc_root_; - -public: - listener(net::io_context &ioc, tcp::endpoint endpoint, - std::shared_ptr const &doc_root) - : ioc_(ioc), acceptor_(net::make_strand(ioc)), doc_root_(doc_root) { - beast::error_code ec; - - // Open the acceptor - acceptor_.open(endpoint.protocol(), ec); - if (ec) { - fail(ec, "open"); - return; - } - - // Allow address reuse - acceptor_.set_option(net::socket_base::reuse_address(true), ec); - if (ec) { - fail(ec, "set_option"); - return; - } - - // Bind to the server address - acceptor_.bind(endpoint, ec); - if (ec) { - fail(ec, "bind"); - return; - } - - // Start listening for connections - acceptor_.listen(net::socket_base::max_listen_connections, ec); - if (ec) { - fail(ec, "listen"); - return; - } - } - - // Start accepting incoming connections - void run() { - // We need to be executing within a strand to perform async operations - // on the I/O objects in this session. Although not strictly necessary - // for single-threaded contexts, this example code is written to be - // thread-safe by default. - net::dispatch(acceptor_.get_executor(), - beast::bind_front_handler(&listener::do_accept, - this->shared_from_this())); - } - -private: - void do_accept() { - // The new connection gets its own strand - acceptor_.async_accept( - net::make_strand(ioc_), - beast::bind_front_handler(&listener::on_accept, shared_from_this())); - } - - void on_accept(beast::error_code ec, tcp::socket socket) { - 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 = net::ip::make_address(argv[1]); - auto const port = static_cast(std::atoi(argv[2])); - auto const doc_root = std::make_shared(argv[3]); - auto const threads = std::max(1, std::atoi(argv[4])); - - // The io_context is required for all I/O - net::io_context ioc{threads}; - - // Create and launch a listening port - std::make_shared(ioc, tcp::endpoint{address, port}, doc_root) - ->run(); - - // Capture SIGINT and SIGTERM to perform a clean shutdown - net::signal_set signals(ioc, SIGINT, SIGTERM); - signals.async_wait([&](beast::error_code const &, int) { - // Stop the `io_context`. This will cause `run()` - // to return immediately, eventually destroying the - // `io_context` and all of the sockets in it. - ioc.stop(); - }); - - // 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([&ioc] { ioc.run(); }); - ioc.run(); - - // (If we get here, it means we got a SIGINT or SIGTERM) - - // Block until all the threads exit - for (auto &t : v) - t.join(); - - return EXIT_SUCCESS; -}