#pragma once #include "ControlActionResult.h" #include #include #include #include #include #include #include #include namespace RenderCadenceCompositor { struct HttpControlServerConfig { unsigned short preferredPort = 8080; unsigned short portSearchCount = 20; std::chrono::milliseconds idleSleep = std::chrono::milliseconds(10); }; struct HttpControlServerCallbacks { std::function getStateJson; std::function addLayer; std::function removeLayer; }; class UniqueSocket { public: explicit UniqueSocket(SOCKET socket = INVALID_SOCKET); ~UniqueSocket(); UniqueSocket(const UniqueSocket&) = delete; UniqueSocket& operator=(const UniqueSocket&) = delete; UniqueSocket(UniqueSocket&& other) noexcept; UniqueSocket& operator=(UniqueSocket&& other) noexcept; SOCKET get() const { return mSocket; } bool valid() const { return mSocket != INVALID_SOCKET; } SOCKET release(); void reset(SOCKET socket = INVALID_SOCKET); private: SOCKET mSocket = INVALID_SOCKET; }; class HttpControlServer { public: struct HttpRequest { std::string method; std::string path; std::map headers; std::string body; }; struct HttpResponse { std::string status; std::string contentType; std::string body; }; HttpControlServer() = default; ~HttpControlServer(); HttpControlServer(const HttpControlServer&) = delete; HttpControlServer& operator=(const HttpControlServer&) = delete; bool Start( const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot, HttpControlServerConfig config, HttpControlServerCallbacks callbacks, std::string& error); void Stop(); bool IsRunning() const { return mRunning.load(std::memory_order_acquire); } unsigned short Port() const { return mPort; } void SetCallbacksForTest(HttpControlServerCallbacks callbacks); void SetRootsForTest(const std::filesystem::path& uiRoot, const std::filesystem::path& docsRoot); HttpResponse RouteRequestForTest(const HttpRequest& request) const; static bool ParseHttpRequest(const std::string& rawRequest, HttpRequest& request); private: void ThreadMain(); bool TryAcceptClient(); bool HandleClient(UniqueSocket clientSocket); bool SendResponse(SOCKET clientSocket, const HttpResponse& response) const; HttpResponse RouteRequest(const HttpRequest& request) const; HttpResponse ServeGet(const HttpRequest& request) const; HttpResponse ServePost(const HttpRequest& request) const; HttpResponse ServeOpenApiSpec() const; HttpResponse ServeSwaggerDocs() const; HttpResponse ServeUiAsset(const std::string& relativePath) const; std::string LoadTextFile(const std::filesystem::path& path) const; static HttpResponse JsonResponse(const std::string& status, const std::string& body); static HttpResponse TextResponse(const std::string& status, const std::string& body); static HttpResponse HtmlResponse(const std::string& status, const std::string& body); static std::string ActionResponse(bool ok, const std::string& error = std::string()); static std::string GuessContentType(const std::filesystem::path& path); static bool IsSafeRelativePath(const std::filesystem::path& path); static std::string ToLower(std::string text); std::filesystem::path mUiRoot; std::filesystem::path mDocsRoot; HttpControlServerConfig mConfig; HttpControlServerCallbacks mCallbacks; UniqueSocket mListenSocket; std::thread mThread; std::atomic mRunning{ false }; unsigned short mPort = 0; bool mWinsockStarted = false; }; }