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:
2026-04-13 01:45:30 +02:00
parent ed07106cf1
commit 5b7001ebfb
6 changed files with 261 additions and 10370 deletions
+73 -99
View File
@@ -1,45 +1,54 @@
#include "data_http.h"
#define CPPHTTPLIB_OPENSSL_SUPPORT 0
#include "vendor/httplib.h"
#include "http_client.h"
#include "vendor/nlohmann/json.hpp"
#include <cstdio>
#include <cstdlib>
using json = nlohmann::json;
// POST a SQL query to the API and return parsed JSON, or null on failure.
static json api_query(httplib::Client& cli, const char* sql) {
json body;
body["sql"] = sql;
auto res = cli.Post("/api/databases/registry/query",
body.dump(), "application/json");
if (!res || res->status != 200) {
if (res) fprintf(stderr, "[http] query error %d: %s\n", res->status, res->body.c_str());
else fprintf(stderr, "[http] connection failed for query: %s\n", sql);
return nullptr;
// Parse host and port from URL like "http://127.0.0.1:8484"
static bool parse_url(const std::string& url, std::string& host, int& port) {
auto pos = url.find("://");
std::string rest = (pos != std::string::npos) ? url.substr(pos + 3) : url;
auto colon = rest.find(':');
if (colon == std::string::npos) {
host = rest;
port = 80;
} else {
host = rest.substr(0, colon);
port = std::atoi(rest.substr(colon + 1).c_str());
}
return json::parse(res->body, nullptr, false);
return !host.empty() && port > 0;
}
// POST a SQL query to the API and return parsed JSON, or null on failure.
static json api_query(HttpClient& cli, const char* sql) {
json body;
body["sql"] = sql;
auto res = cli.post("/api/databases/registry/query", body.dump(), "application/json");
if (!res.ok()) {
if (res.status > 0) fprintf(stderr, "[http] query error %d: %s\n", res.status, res.body.c_str());
else fprintf(stderr, "[http] connection failed for query: %s\n", sql);
return nullptr;
}
return json::parse(res.body, nullptr, false);
}
// Extract first int from a single-row, single-column result.
static int extract_int(const json& j) {
if (j.is_null() || !j.contains("rows") || j["rows"].empty())
return 0;
if (j.is_null() || !j.contains("rows") || j["rows"].empty()) return 0;
auto& val = j["rows"][0][0];
if (val.is_number()) return val.get<int>();
if (val.is_string()) return std::atoi(val.get<std::string>().c_str());
return 0;
}
// Extract string from a row at given column index.
static std::string extract_str(const json& row, size_t idx) {
if (idx >= row.size() || row[idx].is_null()) return "";
if (row[idx].is_string()) return row[idx].get<std::string>();
return row[idx].dump();
}
// Extract int from a row at given column index.
static int extract_row_int(const json& row, size_t idx) {
if (idx >= row.size() || row[idx].is_null()) return 0;
if (row[idx].is_number()) return row[idx].get<int>();
@@ -48,13 +57,18 @@ static int extract_row_int(const json& row, size_t idx) {
}
bool load_registry_data_http(const std::string& api_url, RegistryData& out) {
httplib::Client cli(api_url);
cli.set_connection_timeout(2);
cli.set_read_timeout(5);
std::string host;
int port;
if (!parse_url(api_url, host, port)) {
fprintf(stderr, "[http] invalid URL: %s\n", api_url.c_str());
return false;
}
// Health check first
auto health = cli.Get("/health");
if (!health || health->status != 200) {
HttpClient cli(host, port);
// Health check
auto health = cli.get("/health");
if (!health.ok()) {
fprintf(stderr, "[http] sqlite_api not reachable at %s\n", api_url.c_str());
return false;
}
@@ -62,72 +76,47 @@ bool load_registry_data_http(const std::string& api_url, RegistryData& out) {
fprintf(stdout, "[http] Connected to sqlite_api at %s\n", api_url.c_str());
// --- Counts ---
auto j = api_query(cli, "SELECT COUNT(*) FROM functions");
out.stats.total_functions = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM types");
out.stats.total_types = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM apps");
out.stats.total_apps = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM analysis");
out.stats.total_analysis = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM unit_tests");
out.stats.total_unit_tests = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM proposals");
out.stats.total_proposals = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM functions WHERE tested = 1");
out.stats.tested_functions = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'");
out.stats.pure_functions = extract_int(j);
j = api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'");
out.stats.impure_functions = extract_int(j);
out.stats.total_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions"));
out.stats.total_types = extract_int(api_query(cli, "SELECT COUNT(*) FROM types"));
out.stats.total_apps = extract_int(api_query(cli, "SELECT COUNT(*) FROM apps"));
out.stats.total_analysis = extract_int(api_query(cli, "SELECT COUNT(*) FROM analysis"));
out.stats.total_unit_tests = extract_int(api_query(cli, "SELECT COUNT(*) FROM unit_tests"));
out.stats.total_proposals = extract_int(api_query(cli, "SELECT COUNT(*) FROM proposals"));
out.stats.tested_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE tested = 1"));
out.stats.pure_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'pure'"));
out.stats.impure_functions = extract_int(api_query(cli, "SELECT COUNT(*) FROM functions WHERE purity = 'impure'"));
// --- By language ---
out.by_lang.clear();
j = api_query(cli, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
auto j = api_query(cli, "SELECT lang, COUNT(*) as cnt FROM functions GROUP BY lang ORDER BY cnt DESC");
if (!j.is_null() && j.contains("rows"))
for (auto& row : j["rows"])
out.by_lang.push_back({extract_str(row, 0), extract_row_int(row, 1)});
}
}
// --- By domain ---
out.by_domain.clear();
j = api_query(cli, "SELECT domain, COUNT(*) as cnt FROM functions GROUP BY domain ORDER BY cnt DESC");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
if (!j.is_null() && j.contains("rows"))
for (auto& row : j["rows"])
out.by_domain.push_back({extract_str(row, 0), extract_row_int(row, 1)});
}
}
// --- By kind ---
out.by_kind.clear();
j = api_query(cli, "SELECT kind, COUNT(*) as cnt FROM functions GROUP BY kind ORDER BY cnt DESC");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
if (!j.is_null() && j.contains("rows"))
for (auto& row : j["rows"])
out.by_kind.push_back({extract_str(row, 0), extract_row_int(row, 1)});
}
}
// --- By date (last 30 days) ---
out.by_date.clear();
j = api_query(cli,
"SELECT date(created_at) as d, COUNT(*) as cnt FROM functions "
"WHERE created_at >= date('now', '-30 days') GROUP BY d ORDER BY d");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
if (!j.is_null() && j.contains("rows"))
for (auto& row : j["rows"])
out.by_date.push_back({extract_str(row, 0), extract_row_int(row, 1)});
}
}
// --- Recent functions (last 20) ---
// --- Recent functions ---
out.recent_funcs.clear();
j = api_query(cli,
"SELECT id, name, lang, domain, kind, purity, description, created_at, tested "
@@ -135,14 +124,10 @@ bool load_registry_data_http(const std::string& api_url, RegistryData& out) {
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
FunctionRow r;
r.id = extract_str(row, 0);
r.name = extract_str(row, 1);
r.lang = extract_str(row, 2);
r.domain = extract_str(row, 3);
r.kind = extract_str(row, 4);
r.purity = extract_str(row, 5);
r.description = extract_str(row, 6);
r.created_at = extract_str(row, 7);
r.id = extract_str(row, 0); r.name = extract_str(row, 1);
r.lang = extract_str(row, 2); r.domain = extract_str(row, 3);
r.kind = extract_str(row, 4); r.purity = extract_str(row, 5);
r.description = extract_str(row, 6); r.created_at = extract_str(row, 7);
r.tested = extract_row_int(row, 8) != 0;
out.recent_funcs.push_back(std::move(r));
}
@@ -150,49 +135,38 @@ bool load_registry_data_http(const std::string& api_url, RegistryData& out) {
// --- Apps ---
out.apps.clear();
j = api_query(cli,
"SELECT id, name, lang, domain, description, framework FROM apps ORDER BY name");
j = api_query(cli, "SELECT id, name, lang, domain, description, framework FROM apps ORDER BY name");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
AppRow r;
r.id = extract_str(row, 0);
r.name = extract_str(row, 1);
r.lang = extract_str(row, 2);
r.domain = extract_str(row, 3);
r.description = extract_str(row, 4);
r.framework = extract_str(row, 5);
r.id = extract_str(row, 0); r.name = extract_str(row, 1);
r.lang = extract_str(row, 2); r.domain = extract_str(row, 3);
r.description = extract_str(row, 4); r.framework = extract_str(row, 5);
out.apps.push_back(std::move(r));
}
}
// --- Analysis ---
out.analyses.clear();
j = api_query(cli,
"SELECT id, name, domain, description FROM analysis ORDER BY name");
j = api_query(cli, "SELECT id, name, domain, description FROM analysis ORDER BY name");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
AnalysisRow r;
r.id = extract_str(row, 0);
r.name = extract_str(row, 1);
r.domain = extract_str(row, 2);
r.description = extract_str(row, 3);
r.id = extract_str(row, 0); r.name = extract_str(row, 1);
r.domain = extract_str(row, 2); r.description = extract_str(row, 3);
out.analyses.push_back(std::move(r));
}
}
// --- Types ---
out.types.clear();
j = api_query(cli,
"SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name");
j = api_query(cli, "SELECT id, name, lang, domain, algebraic, description FROM types ORDER BY name");
if (!j.is_null() && j.contains("rows")) {
for (auto& row : j["rows"]) {
TypeRow r;
r.id = extract_str(row, 0);
r.name = extract_str(row, 1);
r.lang = extract_str(row, 2);
r.domain = extract_str(row, 3);
r.algebraic = extract_str(row, 4);
r.description = extract_str(row, 5);
r.id = extract_str(row, 0); r.name = extract_str(row, 1);
r.lang = extract_str(row, 2); r.domain = extract_str(row, 3);
r.algebraic = extract_str(row, 4); r.description = extract_str(row, 5);
out.types.push_back(std::move(r));
}
}