/** * @file web_client/http_web_client.cc * @brief cpp-httplib implementation of WebClient. */ #include "web_client/http_web_client.h" #include #include #include #include #include #include "services/logging/logger.h" namespace { constexpr time_t kConnectionTimeoutSeconds = 5; constexpr time_t kReadTimeoutSeconds = 10; constexpr int kSuccessMin = 200; constexpr int kSuccessMax = 300; const std::regex kUrlRegex( R"(^(https?://[^/?#]+)(/[^?#]*(?:\?[^#]*)?(?:#.*)?)?)"); std::pair SplitUrl(const std::string& url) { std::smatch match; if (!std::regex_match(url, match, kUrlRegex)) { throw std::invalid_argument("[HttpWebClient] Malformed URL: " + url); } return {match[1].str(), match[2].matched ? match[2].str() : "/"}; } } // namespace std::string HttpWebClient::Get(const std::string& url) { const auto [origin, path] = SplitUrl(url); httplib::Client client(origin); client.set_follow_location(true); client.set_connection_timeout(kConnectionTimeoutSeconds); client.set_read_timeout(kReadTimeoutSeconds); client.set_default_headers({ {"Accept", "application/json"}, {"User-Agent", "biergarten-pipeline/1.0"} }); const httplib::Result result = client.Get(path); if (!result) { throw std::runtime_error( "[HttpWebClient] Request failed for URL: " + url + " — " + httplib::to_string(result.error())); } if (result->status < kSuccessMin || result->status >= kSuccessMax) { if (logger_) { logger_->Log(LogLevel::Error, PipelinePhase::UserGeneration, std::string("[HttpWebClient] Request failed for URL: ") + url); } throw std::runtime_error( "[HttpWebClient] HTTP " + std::to_string(result->status) + " for URL: " + url); } return result->body; } std::string HttpWebClient::EncodeURL(const std::string& value) { return httplib::encode_uri_component(value); }