#include "http_client.h" #include #include #include #include #include #ifdef _WIN32 #include #include #include #pragma comment(lib, "ws2_32.lib") // std::call_once para evitar race condition si hay peticiones simultaneas // desde multiples threads (main + runners). static std::once_flag g_wsa_once; static bool g_wsa_ok = false; static bool wsa_init() { std::call_once(g_wsa_once, []() { WSADATA wsa; g_wsa_ok = (WSAStartup(MAKEWORD(2, 2), &wsa) == 0); }); return g_wsa_ok; } typedef SOCKET sock_t; #define SOCK_INVALID INVALID_SOCKET #define SOCK_CLOSE closesocket #define SOCK_ERR WSAGetLastError() #else #include #include #include #include #include #include typedef int sock_t; #define SOCK_INVALID (-1) #define SOCK_CLOSE close #define SOCK_ERR errno #endif HttpClient::HttpClient(const std::string& host, int port) : host_(host), port_(port) {} HttpResponse HttpClient::get(const std::string& path) { return request("GET", path, "", ""); } HttpResponse HttpClient::post(const std::string& path, const std::string& body, const std::string& content_type) { return request("POST", path, body, content_type); } HttpResponse HttpClient::request(const std::string& method, const std::string& path, const std::string& body, const std::string& content_type) { HttpResponse resp; #ifdef _WIN32 if (!wsa_init()) { fprintf(stderr, "[http] WSAStartup failed\n"); return resp; } #endif sock_t sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock == SOCK_INVALID) { fprintf(stderr, "[http] socket() failed: %d\n", SOCK_ERR); return resp; } // Timeout — Windows y POSIX usan formatos distintos para SO_{RCV,SND}TIMEO. // Windows: DWORD milisegundos. POSIX: struct timeval. #ifdef _WIN32 DWORD timeout_ms = static_cast(timeout_sec_ * 1000); setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms)); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout_ms, sizeof(timeout_ms)); #else struct timeval tv; tv.tv_sec = timeout_sec_; tv.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)); setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)); #endif // Connect struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(static_cast(port_)); addr.sin_addr.s_addr = inet_addr(host_.c_str()); if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) { int err = SOCK_ERR; SOCK_CLOSE(sock); // Reportamos el errno/WSAError en el body para que el toast sea util. char buf[128]; std::snprintf(buf, sizeof(buf), "connect() failed to %s:%d (err=%d)", host_.c_str(), port_, err); resp.body = buf; return resp; } // Build request std::ostringstream req; req << method << " " << path << " HTTP/1.1\r\n"; req << "Host: " << host_ << ":" << port_ << "\r\n"; req << "Connection: close\r\n"; if (!body.empty()) { req << "Content-Type: " << content_type << "\r\n"; req << "Content-Length: " << body.size() << "\r\n"; } req << "\r\n"; if (!body.empty()) req << body; std::string raw_req = req.str(); send(sock, raw_req.c_str(), static_cast(raw_req.size()), 0); // Read response std::vector buf(8192); std::string raw; for (;;) { int n = recv(sock, buf.data(), static_cast(buf.size()), 0); if (n <= 0) break; raw.append(buf.data(), n); } SOCK_CLOSE(sock); // Parse status line auto hdr_end = raw.find("\r\n\r\n"); if (hdr_end == std::string::npos) return resp; // "HTTP/1.1 200 OK\r\n..." auto first_line_end = raw.find("\r\n"); if (first_line_end == std::string::npos) return resp; std::string status_line = raw.substr(0, first_line_end); auto sp1 = status_line.find(' '); if (sp1 != std::string::npos) { resp.status = std::atoi(status_line.c_str() + sp1 + 1); } resp.body = raw.substr(hdr_end + 4); // Handle chunked transfer encoding std::string headers_str = raw.substr(0, hdr_end); if (headers_str.find("chunked") != std::string::npos) { // Decode chunked body std::string decoded; const char* p = resp.body.c_str(); const char* end = p + resp.body.size(); while (p < end) { // Read chunk size (hex) char* chunk_end = nullptr; long chunk_size = strtol(p, &chunk_end, 16); if (chunk_size <= 0) break; // Skip \r\n after size p = chunk_end; if (p < end && *p == '\r') p++; if (p < end && *p == '\n') p++; // Read chunk data if (p + chunk_size <= end) { decoded.append(p, chunk_size); } p += chunk_size; // Skip trailing \r\n if (p < end && *p == '\r') p++; if (p < end && *p == '\n') p++; } resp.body = decoded; } return resp; }