diff --git a/.github/workflows/ctest.yml b/.github/workflows/ctest.yml index 7f37eb1..51b6025 100644 --- a/.github/workflows/ctest.yml +++ b/.github/workflows/ctest.yml @@ -13,10 +13,10 @@ jobs: shell: bash steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Configure CMake - run: CXX=clang++-19 CC=clang-19 cmake -S ${{github.workspace}}/ -B ${{runner.workspace}}/build -DCLANG_TIDY_NAME=clang-tidy-19 + run: CXX=clang++-22 CC=clang-22 cmake -S ${{github.workspace}}/ -B ${{runner.workspace}}/build -DCLANG_TIDY_NAME=clang-tidy-22 - name: Build working-directory: ${{runner.workspace}}/build @@ -40,10 +40,10 @@ jobs: shell: pwsh steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Configure CMake - run: cmake -S ${{github.workspace}}\ -B ${{runner.workspace}}\build -G "Ninja" + run: cmake -S ${{github.workspace}}\ -B ${{runner.workspace}}\build -G "MinGW Makefiles" - name: Build working-directory: ${{runner.workspace}}\build diff --git a/.gitignore b/.gitignore index 1a5a6b3..c8d63fb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .vscode/ **/.DS_Store **/*build*/ +Testing/ diff --git a/CMakeLists.txt b/CMakeLists.txt index df5b3c6..4959616 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.25) project(simple-http-server) -set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD 23) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -19,28 +19,28 @@ if(CMAKE_BUILD_TYPE AND NOT CMAKE_BUILD_TYPE IN_LIST CMAKE_CONFIGURATION_TYPES) endif() if(CMAKE_C_COMPILER_ID STREQUAL GNU OR CMAKE_C_COMPILER_ID MATCHES Clang) - set(CMAKE_C_FLAGS "-std=20 -pedantic -Wall -Werror -ggdb") + set(CMAKE_C_FLAGS "-std=23 -pedantic -Wall -Werror -ggdb") set(CMAKE_C_FLAGS_ASAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address -fno-optimize-sibling-calls -fno-omit-frame-pointer") set(CMAKE_C_FLAGS_LSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=leak") set(CMAKE_C_FLAGS_MSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=memory -fno-optimize-sibling-calls -fno-omit-frame-pointer") set(CMAKE_C_FLAGS_UBSAN "${CMAKE_C_FLAGS_DEBUG} -fsanitize=undefined") set(CMAKE_C_FLAGS_COVERAGE "${CMAKE_C_FLAGS_DEBUG} --coverage") elseif(MSVC) - set(CMAKE_C_FLAGS "/std:c20 /W4 /WX") + set(CMAKE_C_FLAGS "/std:c23 /W4 /WX") set(CMAKE_C_FLAGS_ASAN "${CMAKE_C_FLAGS_DEBUG} /fsanitize=address") set(CMAKE_EXE_LINKER_FLAGS_ASAN "/debug /INCREMENTAL:NO") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL GNU OR CMAKE_CXX_COMPILER_ID MATCHES Clang) - set(CMAKE_CXX_FLAGS "-std=c++20 -pedantic -Wall -Werror -ggdb") + set(CMAKE_CXX_FLAGS "-std=c++23 -pedantic -Wall -Werror -ggdb") set(CMAKE_CXX_FLAGS_ASAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fno-optimize-sibling-calls -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS_LSAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=leak") set(CMAKE_CXX_FLAGS_MSAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=memory -fno-optimize-sibling-calls -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS_UBSAN "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=undefined") set(CMAKE_CXX_FLAGS_COVERAGE "${CMAKE_CXX_FLAGS_DEBUG} --coverage") elseif(MSVC) - set(CMAKE_CXX_FLAGS "/std:c++20 /W4 /WX") + set(CMAKE_CXX_FLAGS "/std:c++23 /W4 /WX") set(CMAKE_CXX_FLAGS_ASAN "${CMAKE_CXX_FLAGS_DEBUG} /fsanitize=address") set(CMAKE_EXE_LINKER_FLAGS_ASAN "/debug /INCREMENTAL:NO") endif() @@ -101,7 +101,7 @@ if(BUILD_TESTING) include(FetchContent) FetchContent_Declare( googletest - URL https://github.com/google/googletest/archive/d144031940543e15423a25ae5a8a74141044862f.zip + URL https://github.com/google/googletest/archive/d72f9c8aea6817cdf1ca0ac10887f328de7f3da2.zip DOWNLOAD_EXTRACT_TIMESTAMP NEW ) diff --git a/libs/includes/DefineSystem.h b/libs/includes/DefineSystem.h index a615595..b2de1f6 100644 --- a/libs/includes/DefineSystem.h +++ b/libs/includes/DefineSystem.h @@ -1,4 +1,4 @@ -#if defined __has_include +#ifdef __has_include #if __has_include() #define WINDOWS #elif __has_include() diff --git a/libs/includes/Directory.h b/libs/includes/Directory.h index 4cb274d..42ae0e1 100644 --- a/libs/includes/Directory.h +++ b/libs/includes/Directory.h @@ -11,7 +11,7 @@ namespace simple_http_server { class Directory { public: - enum AllowType : std::uint8_t { BLACKLIST, WHITELIST }; + enum class AllowType : std::uint8_t { BLACKLIST, WHITELIST }; private: std::filesystem::path path_; @@ -20,12 +20,14 @@ class Directory { AllowType type_; std::vector allow_set_; + + // NOLINTNEXTLINE(bugprone-throwing-static-initialization) inline static const std::vector forced_blacklist_ = { std::regex("^.*\\.\\..*$")}; // to block path traversal public: explicit Directory(std::filesystem::path path, HeadersMap headers = {}, - AllowType type = BLACKLIST, + AllowType type = AllowType::BLACKLIST, std::vector allow_set = {}) : path_(std::move(path)), headers_(std::move(headers)), diff --git a/libs/includes/Logger.h b/libs/includes/Logger.h index 0f076b8..c984fdd 100644 --- a/libs/includes/Logger.h +++ b/libs/includes/Logger.h @@ -17,6 +17,7 @@ namespace simple_http_server { // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X(level, name, i) level = (i), +// NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum Level : std::uint8_t { LOGGER_LEVELS }; #undef X @@ -32,6 +33,7 @@ class Logger { public: // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) #define X(level, name, _) name, + // NOLINTNEXTLINE(bugprone-throwing-static-initialization) inline static const std::vector level_name = {LOGGER_LEVELS}; #undef X private: diff --git a/libs/includes/Request.h b/libs/includes/Request.h index d619775..e7c8a12 100644 --- a/libs/includes/Request.h +++ b/libs/includes/Request.h @@ -10,7 +10,7 @@ namespace simple_http_server { class Request { public: - enum Type : std::uint8_t { + enum class Type : std::uint8_t { GET, POST, // add here new types such as DELETE and etc @@ -24,7 +24,7 @@ class Request { static auto ParseArguments(const std::string& url_with_args) -> ArgumentsMap; static auto DecodeURL(std::string& url_with_args); - Type type_ = UNKNOWN; + Type type_ = Type::UNKNOWN; std::string url_; ArgumentsMap arguments_; std::string httpVersion_; diff --git a/libs/includes/Response.h b/libs/includes/Response.h index 39313d0..f928cd6 100644 --- a/libs/includes/Response.h +++ b/libs/includes/Response.h @@ -19,6 +19,7 @@ class Response { [[nodiscard]] auto Empty() const -> bool { return (statusCode_ == 0); } + // NOLINTNEXTLINE(cppcoreguidelines-use-enum-class) enum HttpStatusCodes : std::uint16_t { OK = 200, FORBIDDEN = 403, @@ -33,11 +34,20 @@ class Response { HeadersMap headers_; std::string body_; - inline static const std::unordered_map defaultMessages_{ - {OK, "OK"}, - {NOT_FOUND, "Not Found"}, - {FORBIDDEN, "Forbidden"}, - {INTERNAL_ERROR, "Internal Server Error"}}; + static constexpr auto defaultMessage(int code) noexcept -> std::string_view { + switch (code) { + case HttpStatusCodes::OK: + return "OK"; + case HttpStatusCodes::NOT_FOUND: + return "Not Found"; + case HttpStatusCodes::FORBIDDEN: + return "Forbidden"; + case HttpStatusCodes::INTERNAL_ERROR: + return "Internal Server Error"; + default: + return "Unknown"; + } + } }; } // namespace simple_http_server diff --git a/libs/includes/Socket/WindowsSocket.h b/libs/includes/Socket/WindowsSocket.h index 841d39c..1ba7f20 100644 --- a/libs/includes/Socket/WindowsSocket.h +++ b/libs/includes/Socket/WindowsSocket.h @@ -20,6 +20,7 @@ #include #include +#include #include #include diff --git a/libs/sources/Logger.cpp b/libs/sources/Logger.cpp index 2a1eff9..62fcda6 100644 --- a/libs/sources/Logger.cpp +++ b/libs/sources/Logger.cpp @@ -10,7 +10,9 @@ namespace simple_http_server { +// NOLINTNEXTLINE(bugprone-throwing-static-initialization) std::filesystem::path Logger::logFilename_ = "./server.log"; +// NOLINTNEXTLINE(bugprone-throwing-static-initialization) std::ofstream Logger::logStream_; Level Logger::logLevel_ = WARNING; bool Logger::isInit_ = false; @@ -44,7 +46,7 @@ void Logger::Log(Level level, std::ostringstream&& message) { const time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); Logger::logStream_ << std::put_time(std::localtime(&now), "%Y-%m-%d@%H:%M:%S") - << " " << level_name[level] << " " + << " " << level_name.at(level) << " " << std::move(message).str() << "\n"; Flush(); // TODO(dzen) think about flush(); probably should use Flush() each N lines diff --git a/libs/sources/Request.cpp b/libs/sources/Request.cpp index e205b5c..d451493 100644 --- a/libs/sources/Request.cpp +++ b/libs/sources/Request.cpp @@ -16,7 +16,8 @@ auto Request::ParseArguments(const std::string& url_with_args) -> ArgumentsMap { auto next_arg = args_start; - auto parse_argument = [&ret, &url_with_args](auto current_arg) mutable { + auto parse_argument = [&ret, + &url_with_args](auto current_arg) mutable -> auto { auto sep = url_with_args.find('=', current_arg); auto next_arg = url_with_args.find('&', current_arg + 1); @@ -55,11 +56,11 @@ auto Request::DecodeURL(std::string& url_with_args) { res.reserve(url_with_args.length()); for (std::string::size_type it = 0; it < url_with_args.length(); ++it) { - if (url_with_args[it] == '%' && it < url_with_args.length() - 3) { - res += HexToDec({url_with_args[it + 1], url_with_args[it + 2]}); + if (url_with_args.at(it) == '%' && it < url_with_args.length() - 3) { + res += HexToDec({url_with_args.at(it + 1), url_with_args.at(it + 2)}); it += 2; } else { - res += url_with_args[it]; + res += url_with_args.at(it); } } diff --git a/libs/sources/Response.cpp b/libs/sources/Response.cpp index 0a040de..4e1a68b 100644 --- a/libs/sources/Response.cpp +++ b/libs/sources/Response.cpp @@ -1,7 +1,6 @@ #include "Response.h" #include -#include #include #include @@ -14,12 +13,8 @@ Response::Response(int statusCode, std::string body, HeadersMap headers, : statusCode_(statusCode), headers_(std::move(headers)), body_(std::move(body)) { - if (statusMessage.empty() && !defaultMessages_.contains(statusCode)) { - throw std::runtime_error("no message for error code provided"); - } - - statusMessage_ = (statusMessage.empty() ? defaultMessages_.at(statusCode_) - : statusMessage); + statusMessage_ = + (statusMessage.empty() ? defaultMessage(statusCode_) : statusMessage); if (!headers_.contains("Content-Length")) { headers_.emplace("Content-Length", std::to_string(body_.length())); } diff --git a/libs/sources/Server.cpp b/libs/sources/Server.cpp index 8557471..849bff2 100644 --- a/libs/sources/Server.cpp +++ b/libs/sources/Server.cpp @@ -44,7 +44,8 @@ void Server::Start() { try { const auto& client_sock = socket_->Accept(); - threadPool.enqueue([this, client_sock]() { HandleClient(client_sock); }); + threadPool.enqueue( + [this, client_sock]() -> auto { HandleClient(client_sock); }); } catch (const std::exception& exception) { LOG(ERROR, exception.what()); @@ -73,18 +74,19 @@ auto Server::TryRenderFile(const Request& request) -> Response { path /= request.GetUrl().substr(prev_pos + 1); - auto check_path = [](const auto& type, const auto& set, const auto& path) { - bool allowed = (type == Directory::BLACKLIST); + auto check_path = [](const auto& type, const auto& set, + const auto& path) -> auto { + bool allowed = (type == Directory::AllowType::BLACKLIST); for (const auto& reg : set) { if (std::regex_match(path.string(), reg)) { - allowed = (type == Directory::WHITELIST); + allowed = (type == Directory::AllowType::WHITELIST); } } return allowed; }; - bool allowed = - check_path(Directory::BLACKLIST, Directory::GetForcedBlacklist(), path); + bool allowed = check_path(Directory::AllowType::BLACKLIST, + Directory::GetForcedBlacklist(), path); allowed = (allowed ? check_path(dir.GetType(), dir.GetAllowSet(), path) : false); diff --git a/libs/sources/Socket/WindowsSocket.cpp b/libs/sources/Socket/WindowsSocket.cpp index f71801a..9bab1b5 100644 --- a/libs/sources/Socket/WindowsSocket.cpp +++ b/libs/sources/Socket/WindowsSocket.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include // I have no idea, where SOCKET_ERROR, etc defined, so @@ -89,7 +90,7 @@ auto WindowsSocket::SendMessageAndCloseClient( auto WindowsSocket::Accept() -> WindowsSocket::SocketDescriptor { auto client_addr = accept(socketDescriptor_, nullptr, nullptr); - if (client_addr == INVALID_SOCKET) { + if (std::cmp_equal(client_addr, INVALID_SOCKET)) { throw std::runtime_error("could not accept socket"); } diff --git a/libs/sources/SocketFactory.cpp b/libs/sources/SocketFactory.cpp index f6d0bdf..4f457fb 100644 --- a/libs/sources/SocketFactory.cpp +++ b/libs/sources/SocketFactory.cpp @@ -8,7 +8,7 @@ #ifdef POSIX #include "Socket/PosixSocket.h" -#elif defined WINDOWS +#elifdef WINDOWS #include "Socket/WindowsSocket.h" #else #error Unknown platform @@ -20,7 +20,7 @@ auto SocketFactory::CreateSocket(const std::string& address, int port) -> std::unique_ptr { #ifdef POSIX return std::make_unique(address, port); -#elif defined WINDOWS +#elifdef WINDOWS return std::make_unique(address, port); #else #error Unknown platform diff --git a/libs/sources/ThreadPool.cpp b/libs/sources/ThreadPool.cpp index 0905f76..dd28211 100644 --- a/libs/sources/ThreadPool.cpp +++ b/libs/sources/ThreadPool.cpp @@ -11,7 +11,7 @@ ThreadPool::ThreadPool(std::uint64_t numThreads) { for (decltype(numThreads) i = 0; i < numThreads; ++i) { - workers_.emplace_back([this] { workerThread(); }); + workers_.emplace_back([this]() -> void { workerThread(); }); } } @@ -39,7 +39,8 @@ void ThreadPool::workerThread() { std::function task; { std::unique_lock lock(queueMutex_); - condition_.wait(lock, [this] { return stop_ || !tasks_.empty(); }); + condition_.wait(lock, + [this]() -> auto { return stop_ || !tasks_.empty(); }); if (stop_ && tasks_.empty()) { return; diff --git a/main.cpp b/main.cpp index 13d40cb..3ce2e8c 100644 --- a/main.cpp +++ b/main.cpp @@ -5,31 +5,42 @@ #include "Request.h" #include "Response.h" #include "Server.h" +#include "exception" +#include "iostream" auto main() -> int { - static constexpr int DEFAULT_PORT = 8080; - simple_http_server::Server server("0.0.0.0", DEFAULT_PORT, - simple_http_server::DEBUG); + try { + static constexpr int DEFAULT_PORT = 8080; + simple_http_server::Server server("0.0.0.0", DEFAULT_PORT, + simple_http_server::DEBUG); - server.MapUrl( - "/plain", - [](const simple_http_server::Request&) { - return simple_http_server::Response( - simple_http_server::Response::HttpStatusCodes::OK, - "Hello world in plain text"); - }, - true); + server.MapUrl( + "/plain", + [](const simple_http_server::Request&) -> auto { + return simple_http_server::Response( + simple_http_server::Response::HttpStatusCodes::OK, + "Hello world in plain text"); + }, + true); - server.MapUrl("/", [](const simple_http_server::Request&) { - return simple_http_server::Server::Render("index.html"); - }); + server.MapUrl("/", [](const simple_http_server::Request&) -> auto { + return simple_http_server::Server::Render("index.html"); + }); - server.MapDirectory("/dir", - simple_http_server::Directory( - "./", {}, simple_http_server::Directory::WHITELIST, - {std::regex("^.*.log$")})); + server.MapDirectory( + "/dir", + simple_http_server::Directory( + "./", {}, simple_http_server::Directory::AllowType::WHITELIST, + {std::regex("^.*.log$")})); - server.Start(); + server.Start(); - return 0; + return 0; + } catch (const std::exception& exc) { + std::cerr << "Fatal error: " << exc.what() << '\n'; + return 1; + } catch (...) { + std::cerr << "Unknown fatal error\n"; + return 1; + } } diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 3cf993a..0e4a19a 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -32,7 +32,7 @@ TEST(Logger, MainTest) { for (const auto& line : levels) { log >> time >> level >> messageKey >> messageValue; - EXPECT_EQ(level, Logger::level_name[line.first]); + EXPECT_EQ(level, Logger::level_name.at(line.first)); EXPECT_EQ(messageKey, "line.second:"); EXPECT_EQ(messageValue, line.second); } diff --git a/tests/request_tests.cpp b/tests/request_tests.cpp index cedafab..a11757a 100644 --- a/tests/request_tests.cpp +++ b/tests/request_tests.cpp @@ -13,7 +13,7 @@ TEST(Request, Parse) { "\r\n" "field1=value1&field2=value2"); - EXPECT_EQ(parsed.GetType(), Request::POST); + EXPECT_EQ(parsed.GetType(), Request::Type::POST); EXPECT_EQ(parsed.GetUrl(), "/home.html"); EXPECT_EQ(parsed.GetArguments(), ArgumentsMap()); EXPECT_EQ(parsed.GetHttpVersion(), "HTTP/1.1"); @@ -32,7 +32,7 @@ TEST(Request, ParseWithArguments) { "Gecko/20100101 Firefox/50.0\r\n" "\r\n"); - EXPECT_EQ(parsed.GetType(), Request::GET); + EXPECT_EQ(parsed.GetType(), Request::Type::GET); EXPECT_EQ(parsed.GetUrl(), "/home.html"); EXPECT_EQ(parsed.GetArguments(), ArgumentsMap({{"a", "b"}, {"c", "d"}})); EXPECT_EQ(parsed.GetHttpVersion(), "HTTP/1.1"); @@ -52,7 +52,7 @@ TEST(Request, ParseDecoded) { "Gecko/20100101 Firefox/50.0\r\n" "\r\n"); - EXPECT_EQ(parsed.GetType(), Request::GET); + EXPECT_EQ(parsed.GetType(), Request::Type::GET); EXPECT_EQ(parsed.GetUrl(), "/home.html"); EXPECT_EQ(parsed.GetArguments(), ArgumentsMap({{"a", "b b"}, {"c", "d"}})); EXPECT_EQ(parsed.GetHttpVersion(), "HTTP/1.1"); @@ -71,7 +71,7 @@ TEST(Request, ParseInvalidHeaders) { "User-Agent: \r\n" "\r\n"); - EXPECT_EQ(parsed.GetType(), Request::UNKNOWN); + EXPECT_EQ(parsed.GetType(), Request::Type::UNKNOWN); EXPECT_EQ(parsed.GetUrl(), "google.com"); EXPECT_EQ(parsed.GetArguments(), ArgumentsMap({})); EXPECT_EQ(parsed.GetHttpVersion(), "HTTP/1.1"); @@ -88,7 +88,7 @@ TEST(Request, ParseInvalidHeadersWithValid) { "(KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36\r\n" "\r\n"); - EXPECT_EQ(parsed.GetType(), Request::GET); + EXPECT_EQ(parsed.GetType(), Request::Type::GET); EXPECT_EQ(parsed.GetUrl(), "/manager/html"); EXPECT_EQ(parsed.GetArguments(), ArgumentsMap({})); EXPECT_EQ(parsed.GetHttpVersion(), "HTTP/1.1"); diff --git a/tests/server_tests.cpp b/tests/server_tests.cpp index 0fc246f..891e270 100644 --- a/tests/server_tests.cpp +++ b/tests/server_tests.cpp @@ -1,13 +1,14 @@ #include +#include #include #include #include #include #include +#include #include #include -#include #include "Logger.h" #include "Request.h" @@ -18,11 +19,10 @@ namespace simple_http_server { namespace { -const std::vector results = { +constexpr std::array results = { "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: " "17\r\n\r\ntest1234片仮名", "HTTP/1.1 404 Not Found\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"}; -} // namespace class ServerTest : public testing::TestWithParam> { protected: @@ -44,17 +44,17 @@ class ServerTest : public testing::TestWithParam> { server = new Server("127.0.0.1", port); // NOLINT - server_thread = std::thread([]() { + server_thread = std::thread([]() -> auto { server->MapUrl( "/true", - [](const simple_http_server::Request&) { + [](const simple_http_server::Request&) -> auto { return simple_http_server::Response( simple_http_server::Response::OK, "test1234片仮名"); }, true); server->MapUrl( "/false", - [](const simple_http_server::Request&) { + [](const simple_http_server::Request&) -> auto { return simple_http_server::Response( simple_http_server::Response::OK, "test1234片仮名"); }, @@ -98,7 +98,7 @@ TEST_P(ServerTest, TestMappings) { EXPECT_FALSE(resp->empty()); EXPECT_EQ(std::string(resp->begin(), resp->end()), - results[GetParam().second]); + results.at(GetParam().second)); } INSTANTIATE_TEST_SUITE_P(, ServerTest, @@ -107,4 +107,5 @@ INSTANTIATE_TEST_SUITE_P(, ServerTest, std::make_pair("/true/1234", 0), std::make_pair("/false/1234", 1))); +} // namespace } // namespace simple_http_server diff --git a/tests/socket_tests.cpp b/tests/socket_tests.cpp index 9a53951..dbee7be 100644 --- a/tests/socket_tests.cpp +++ b/tests/socket_tests.cpp @@ -25,7 +25,7 @@ TEST(Socket, BasicPong) { ASSERT_TRUE(server->BindAndListen()); - std::thread server_thread([&server, &test_message]() { + std::thread server_thread([&server, &test_message]() -> auto { auto client_sock = server->Accept(); const auto& received = server->ReceiveMessage(client_sock);