From e166deda11f6f8643d5a3c19cd47bb828f84aedc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD?= Date: Fri, 8 Nov 2024 19:55:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D1=81=D0=BF=D0=B8=D0=BB=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20-=20=D0=B2=D1=8B=D0=BD?= =?UTF-8?q?=D0=B5=D1=81=D0=B5=D0=BD=D0=B8=D0=B5=20helpers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 2 +- src/Session.cpp | 3 + src/Session.h | 104 +++++++++++++++++++++++++++++ src/helper.cpp | 67 +++++++++++++++++++ src/helpers.h | 119 +++++++++++++++++++++++++++++++++ src/main.cpp | 174 ++++-------------------------------------------- src/sdk.h | 4 ++ 7 files changed, 310 insertions(+), 163 deletions(-) create mode 100644 src/Session.cpp create mode 100644 src/Session.h create mode 100644 src/helper.cpp create mode 100644 src/helpers.h create mode 100644 src/sdk.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 31c931e..7e66ed1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,5 +14,5 @@ endif() set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) -add_executable(application src/main.cpp) +add_executable(application src/main.cpp src/helpers.h src/helper.cpp src/sdk.h) target_link_libraries(application PRIVATE Threads::Threads) \ No newline at end of file diff --git a/src/Session.cpp b/src/Session.cpp new file mode 100644 index 0000000..b433e1d --- /dev/null +++ b/src/Session.cpp @@ -0,0 +1,3 @@ +// +// Created by Антон on 08.11.2024. +// diff --git a/src/Session.h b/src/Session.h new file mode 100644 index 0000000..cdd80db --- /dev/null +++ b/src/Session.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace uad +{ +namespace beast = boost::beast; +namespace http = beast::http; +using tcp = boost::asio::ip::tcp; + +class session : public std::enable_shared_from_this +{ + beast::tcp_stream stream_; + beast::flat_buffer buffer_; + std::shared_ptr doc_root_; + http::request req_; + + public: + session( + tcp::socket&& socket, + std::shared_ptr const& doc_root) + : stream_(std::move(socket)), doc_root_(doc_root) + { + } + + void run() + { + net::dispatch(stream_.get_executor(), + beast::bind_front_handler( + &session::do_read, + shared_from_this())); + } + + void do_read() + { + req_ = {}; + + stream_.expires_after(std::chrono::seconds(30)); + + http::async_read(stream_, buffer_, req_, + beast::bind_front_handler( + &session::on_read, + shared_from_this())); + } + + void on_read( + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec == http::error::end_of_stream) + return do_close(); + + if (ec) + return fail(ec, "read"); + + send_response( + handle_request(*doc_root_, std::move(req_))); + } + + void send_response(http::message_generator&& msg) + { + bool keep_alive = msg.keep_alive(); + + beast::async_write( + stream_, + std::move(msg), + beast::bind_front_handler( + &session::on_write, shared_from_this(), keep_alive)); + } + + void on_write( + bool keep_alive, + beast::error_code ec, + std::size_t bytes_transferred) + { + boost::ignore_unused(bytes_transferred); + + if (ec) + return fail(ec, "write"); + + if (!keep_alive) + { + return do_close(); + } + + do_read(); + } + + void do_close() + { + beast::error_code ec; + stream_.socket().shutdown(tcp::socket::shutdown_send, ec); + + } +}; +} diff --git a/src/helper.cpp b/src/helper.cpp new file mode 100644 index 0000000..1fa98ab --- /dev/null +++ b/src/helper.cpp @@ -0,0 +1,67 @@ +#include "helpers.h" + +namespace uad +{ +beast::string_view mime_type(beast::string_view path) +{ + using beast::iequals; + auto const ext = [&path] + { + auto const pos = path.rfind("."); + if (pos == beast::string_view::npos) + return 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 path_cat( + beast::string_view base, + 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; +} + +void fail(beast::error_code ec, char const* what) +{ + std::cerr << what << ": " << ec.message() << "\n"; +} +} diff --git a/src/helpers.h b/src/helpers.h new file mode 100644 index 0000000..c09bc6c --- /dev/null +++ b/src/helpers.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace uad +{ +namespace beast = boost::beast; +namespace http = beast::http; +namespace net = boost::asio; +using tcp = boost::asio::ip::tcp; + +beast::string_view mime_type(beast::string_view path); + +std::string path_cat( + beast::string_view base, + beast::string_view path); + +template +http::message_generator handle_request( + beast::string_view doc_root, + http::request>&& req) +{ + auto const bad_request = + [&req](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() = std::string(why); + res.prepare_payload(); + return res; + }; + + auto const not_found = + [&req](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 '" + std::string(target) + "' was not found."; + res.prepare_payload(); + return res; + }; + + auto const server_error = + [&req](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: '" + std::string(what) + "'"; + res.prepare_payload(); + return res; + }; + + if (req.method() != http::verb::get && + req.method() != http::verb::head) + return bad_request("Unknown HTTP-method"); + + if (req.target().empty() || + req.target()[0] != '/' || + req.target().find("..") != beast::string_view::npos) + return bad_request("Illegal request-target"); + + std::string path = path_cat(doc_root, req.target()); + if (req.target().back() == '/') + path.append("index.html"); + + beast::error_code ec; + 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 not_found(req.target()); + + if (ec) + return server_error(ec.message()); + + auto const size = body.size(); + + 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(size); + res.keep_alive(req.keep_alive()); + return res; + } + + 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(size); + res.keep_alive(req.keep_alive()); + return res; +} + +void fail(beast::error_code ec, char const* what); +} diff --git a/src/main.cpp b/src/main.cpp index 1793c58..59fec4d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,3 +1,5 @@ +#include "sdk.h" + #include #include #include @@ -13,165 +15,13 @@ #include #include +#include "helpers.h" + namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = boost::asio::ip::tcp; -beast::string_view -mime_type(beast::string_view path) -{ - using beast::iequals; - auto const ext = [&path] - { - auto const pos = path.rfind("."); - if (pos == beast::string_view::npos) - return 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 -path_cat( - beast::string_view base, - 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; -} - -template -http::message_generator -handle_request( - beast::string_view doc_root, - http::request>&& req) -{ - auto const bad_request = - [&req](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() = std::string(why); - res.prepare_payload(); - return res; - }; - - auto const not_found = - [&req](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 '" + std::string(target) + "' was not found."; - res.prepare_payload(); - return res; - }; - - auto const server_error = - [&req](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: '" + std::string(what) + "'"; - res.prepare_payload(); - return res; - }; - - if (req.method() != http::verb::get && - req.method() != http::verb::head) - return bad_request("Unknown HTTP-method"); - - if (req.target().empty() || - req.target()[0] != '/' || - req.target().find("..") != beast::string_view::npos) - return bad_request("Illegal request-target"); - - std::string path = path_cat(doc_root, req.target()); - if (req.target().back() == '/') - path.append("index.html"); - - beast::error_code ec; - 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 not_found(req.target()); - - if (ec) - return server_error(ec.message()); - - auto const size = body.size(); - - 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(size); - res.keep_alive(req.keep_alive()); - return res; - } - - 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(size); - res.keep_alive(req.keep_alive()); - return res; -} - -void -fail(beast::error_code ec, char const* what) -{ - std::cerr << what << ": " << ec.message() << "\n"; -} - class session : public std::enable_shared_from_this { beast::tcp_stream stream_; @@ -220,10 +70,10 @@ class session : public std::enable_shared_from_this return do_close(); if (ec) - return fail(ec, "read"); + return uad::fail(ec, "read"); send_response( - handle_request(*doc_root_, std::move(req_))); + uad::handle_request(*doc_root_, std::move(req_))); } void @@ -247,7 +97,7 @@ class session : public std::enable_shared_from_this boost::ignore_unused(bytes_transferred); if (ec) - return fail(ec, "write"); + return uad::fail(ec, "write"); if (!keep_alive) { @@ -284,21 +134,21 @@ class listener : public std::enable_shared_from_this acceptor_.open(endpoint.protocol(), ec); if (ec) { - fail(ec, "open"); + uad::fail(ec, "open"); return; } acceptor_.set_option(net::socket_base::reuse_address(true), ec); if (ec) { - fail(ec, "set_option"); + uad::fail(ec, "set_option"); return; } acceptor_.bind(endpoint, ec); if (ec) { - fail(ec, "bind"); + uad::fail(ec, "bind"); return; } @@ -306,7 +156,7 @@ class listener : public std::enable_shared_from_this net::socket_base::max_listen_connections, ec); if (ec) { - fail(ec, "listen"); + uad::fail(ec, "listen"); return; } } @@ -333,7 +183,7 @@ class listener : public std::enable_shared_from_this { if (ec) { - fail(ec, "accept"); + uad::fail(ec, "accept"); return; } else diff --git a/src/sdk.h b/src/sdk.h new file mode 100644 index 0000000..d2551f1 --- /dev/null +++ b/src/sdk.h @@ -0,0 +1,4 @@ +#pragma once +#ifdef WIN32 +#include +#endif \ No newline at end of file