chore: auto-commit (7 archivos)
- CMakeLists.txt - app.md - appicon.ico - http_client.cpp - http_client.h - main.cpp - vendor/ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,16 @@
|
|||||||
|
add_imgui_app(app_gestion
|
||||||
|
main.cpp
|
||||||
|
http_client.cpp
|
||||||
|
)
|
||||||
|
target_include_directories(app_gestion PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
|
||||||
|
# data_table::render via fn_module_data_table (apps + modules tables).
|
||||||
|
if(TARGET fn_module_data_table)
|
||||||
|
target_link_libraries(app_gestion PRIVATE fn_module_data_table)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(WIN32)
|
||||||
|
# Sockets para HttpClient.
|
||||||
|
target_link_libraries(app_gestion PRIVATE ws2_32)
|
||||||
|
set_target_properties(app_gestion PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
---
|
||||||
|
name: app_gestion
|
||||||
|
lang: cpp
|
||||||
|
domain: tools
|
||||||
|
version: 0.1.0
|
||||||
|
description: "Gestion central de apps + framework + modulos del registry (read + rebuild + redeploy + open)"
|
||||||
|
tags: [imgui, dashboard, registry, tools]
|
||||||
|
uses_functions: []
|
||||||
|
uses_modules: [data_table_cpp]
|
||||||
|
uses_types: []
|
||||||
|
framework: "imgui"
|
||||||
|
entry_point: "main.cpp"
|
||||||
|
dir_path: "apps/app_gestion"
|
||||||
|
repo_url: "https://gitea.organic-machine.com/dataforge/app_gestion"
|
||||||
|
icon:
|
||||||
|
phosphor: "sliders-horizontal"
|
||||||
|
accent: "#0891b2"
|
||||||
|
---
|
||||||
|
|
||||||
|
# app_gestion
|
||||||
|
|
||||||
|
Gestion central de apps + framework + modulos del registry (read + rebuild + redeploy + open)
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd cpp && cmake --build build --target app_gestion -j
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./cpp/build/app_gestion
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Capability growth log
|
||||||
|
|
||||||
|
Una linea por bump SemVer. Bump-type segun `.claude/commands/version.md`:
|
||||||
|
- `major`: breaking observable (CLI args, schema BBDD propia, formato wire).
|
||||||
|
- `minor`: feature aditiva (nuevo panel, endpoint, opcion).
|
||||||
|
- `patch`: bugfix sin cambio observable.
|
||||||
|
|
||||||
|
- v0.1.0 (2026-05-18) — baseline.
|
||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
+173
@@ -0,0 +1,173 @@
|
|||||||
|
#include "http_client.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
#include <mutex>
|
||||||
|
#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 <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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<DWORD>(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<uint16_t>(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<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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
// Minimal HTTP client — no threading, no SSL, just plain TCP to localhost.
|
||||||
|
// Works with both win32 and posix MinGW thread models.
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct HttpResponse {
|
||||||
|
int status = 0;
|
||||||
|
std::string body;
|
||||||
|
bool ok() const { return status >= 200 && status < 300; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Simple blocking HTTP GET/POST over TCP sockets.
|
||||||
|
// host: "127.0.0.1", port: 8484
|
||||||
|
class HttpClient {
|
||||||
|
public:
|
||||||
|
HttpClient(const std::string& host, int port);
|
||||||
|
|
||||||
|
HttpResponse get(const std::string& path);
|
||||||
|
HttpResponse post(const std::string& path, const std::string& body,
|
||||||
|
const std::string& content_type = "application/json");
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string host_;
|
||||||
|
int port_;
|
||||||
|
int timeout_sec_ = 30; // cold drift audit lee ~400MB de binarios; 5s era insuficiente
|
||||||
|
|
||||||
|
HttpResponse request(const std::string& method, const std::string& path,
|
||||||
|
const std::string& body, const std::string& content_type);
|
||||||
|
};
|
||||||
Vendored
+24765
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user