refactor: reemplazar cpp-httplib por HTTP client minimalista
cpp-httplib requiere std::mutex que no compila con MinGW win32 thread model. Se reemplaza por http_client.cpp: sockets crudos, sin threading, sin SSL, funciona en ambos thread models. Elimina vendor/httplib.h (10K lineas). nlohmann/json se mantiene. main.cpp: doble clic sin argumentos intenta la API en localhost:8484 automaticamente. Si falla muestra pantalla de error con boton Retry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+154
@@ -0,0 +1,154 @@
|
||||
#include "http_client.h"
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
|
||||
static bool wsa_init() {
|
||||
static bool done = false;
|
||||
if (!done) {
|
||||
WSADATA wsa;
|
||||
done = (WSAStartup(MAKEWORD(2, 2), &wsa) == 0);
|
||||
}
|
||||
return done;
|
||||
}
|
||||
typedef SOCKET sock_t;
|
||||
#define SOCK_INVALID INVALID_SOCKET
|
||||
#define SOCK_CLOSE closesocket
|
||||
#define SOCK_ERR WSAGetLastError()
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <errno.h>
|
||||
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;
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
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));
|
||||
|
||||
// Connect
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_port = htons(static_cast<uint16_t>(port_));
|
||||
addr.sin_addr.s_addr = inet_addr(host_.c_str());
|
||||
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
SOCK_CLOSE(sock);
|
||||
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<int>(raw_req.size()), 0);
|
||||
|
||||
// Read response
|
||||
std::vector<char> buf(8192);
|
||||
std::string raw;
|
||||
for (;;) {
|
||||
int n = recv(sock, buf.data(), static_cast<int>(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;
|
||||
}
|
||||
Reference in New Issue
Block a user