#pragma once /** * @brief Simple HTTP Client wrapper around cpp-httplib * * Header-only wrapper for making HTTP requests to LLM APIs. * Requires cpp-httplib and OpenSSL for HTTPS support. */ // Only enable OpenSSL support if it was found during CMake configuration // CPPHTTPLIB_OPENSSL_SUPPORT is defined via target_compile_definitions when OpenSSL is available #include #include #include #include #include #include #include namespace aissia { using json = nlohmann::json; struct HttpResponse { int status = 0; std::string body; bool success = false; std::string error; }; class HttpClient { public: explicit HttpClient(const std::string& baseUrl, int timeoutSeconds = 30) : m_baseUrl(baseUrl), m_timeoutSeconds(timeoutSeconds) { m_logger = spdlog::get("HttpClient"); if (!m_logger) { m_logger = spdlog::stdout_color_mt("HttpClient"); } // Parse URL to extract host and determine HTTPS if (baseUrl.find("https://") == 0) { m_host = baseUrl.substr(8); m_useSSL = true; } else if (baseUrl.find("http://") == 0) { m_host = baseUrl.substr(7); m_useSSL = false; } else { m_host = baseUrl; m_useSSL = false; } // Remove trailing path auto slashPos = m_host.find('/'); if (slashPos != std::string::npos) { m_host = m_host.substr(0, slashPos); } } void setHeader(const std::string& key, const std::string& value) { m_headers[key] = value; } void setBearerToken(const std::string& token) { m_headers["Authorization"] = "Bearer " + token; } HttpResponse post(const std::string& path, const json& body) { HttpResponse response; try { // Create client with full URL scheme for proper HTTPS support std::string url = (m_useSSL ? "https://" : "http://") + m_host; httplib::Client client(url); // Only enable certificate verification if OpenSSL support is compiled in #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (m_useSSL) { client.enable_server_certificate_verification(true); } #endif client.set_connection_timeout(m_timeoutSeconds); client.set_read_timeout(m_timeoutSeconds); client.set_write_timeout(m_timeoutSeconds); httplib::Headers headers; headers.emplace("Content-Type", "application/json"); for (const auto& [key, value] : m_headers) { headers.emplace(key, value); } std::string bodyStr = body.dump(); m_logger->debug("POST {} ({} bytes)", path, bodyStr.size()); auto result = client.Post(path, headers, bodyStr, "application/json"); if (result) { response.status = result->status; response.body = result->body; response.success = (result->status >= 200 && result->status < 300); if (!response.success) { response.error = "HTTP " + std::to_string(result->status); m_logger->warn("HTTP {} for {}: {}", result->status, path, result->body.substr(0, 200)); } } else { response.error = httplib::to_string(result.error()); m_logger->error("HTTP request failed: {}", response.error); } } catch (const std::exception& e) { response.error = e.what(); m_logger->error("HTTP exception: {}", e.what()); } return response; } HttpResponse postMultipart(const std::string& path, const httplib::MultipartFormDataItems& items) { HttpResponse response; try { std::string url = (m_useSSL ? "https://" : "http://") + m_host; httplib::Client client(url); // Only enable certificate verification if OpenSSL support is compiled in #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (m_useSSL) { client.enable_server_certificate_verification(true); } #endif client.set_connection_timeout(m_timeoutSeconds); client.set_read_timeout(m_timeoutSeconds); httplib::Headers headers; for (const auto& [key, value] : m_headers) { headers.emplace(key, value); } auto result = client.Post(path, headers, items); if (result) { response.status = result->status; response.body = result->body; response.success = (result->status >= 200 && result->status < 300); } else { response.error = httplib::to_string(result.error()); } } catch (const std::exception& e) { response.error = e.what(); } return response; } HttpResponse get(const std::string& path) { HttpResponse response; try { std::string url = (m_useSSL ? "https://" : "http://") + m_host; httplib::Client client(url); // Only enable certificate verification if OpenSSL support is compiled in #ifdef CPPHTTPLIB_OPENSSL_SUPPORT if (m_useSSL) { client.enable_server_certificate_verification(true); } #endif client.set_connection_timeout(m_timeoutSeconds); client.set_read_timeout(m_timeoutSeconds); httplib::Headers headers; for (const auto& [key, value] : m_headers) { headers.emplace(key, value); } auto result = client.Get(path, headers); if (result) { response.status = result->status; response.body = result->body; response.success = (result->status >= 200 && result->status < 300); } else { response.error = httplib::to_string(result.error()); } } catch (const std::exception& e) { response.error = e.what(); } return response; } private: std::string m_baseUrl; std::string m_host; bool m_useSSL = false; int m_timeoutSeconds; std::map m_headers; std::shared_ptr m_logger; }; } // namespace aissia