aissia/src/shared/http/HttpClient.hpp
StillHammer f5c03e1343 fix: Windows build compatibility - disable OpenSSL and fix Winsock linking
- Remove hardcoded CPPHTTPLIB_OPENSSL_SUPPORT in HttpClient.hpp
- Make SSL certificate verification conditional on OpenSSL availability
- Add ws2_32 (Winsock) linking for all targets using httplib on Windows
- Disable TestRunnerModule on Windows (requires Unix dlfcn.h)
- Fix .gitignore to exclude GroveEngine symlink (local only)
- Disable httplib OpenSSL auto-detection when OpenSSL not found

This enables AISSIA to build successfully on Windows without OpenSSL,
using HTTP-only mode for LLM API calls.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-30 16:26:35 +08:00

210 lines
6.6 KiB
C++

#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 <httplib.h>
#include <nlohmann/json.hpp>
#include <string>
#include <optional>
#include <map>
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
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<std::string, std::string> m_headers;
std::shared_ptr<spdlog::logger> m_logger;
};
} // namespace aissia