---
name: http_request
kind: function
lang: cpp
domain: core
version: "1.0.0"
purity: impure
signature: "fn_http::Response fn_http::request(const fn_http::Request& req)"
description: "Generic HTTP client via cURL CLI (popen). Supports GET/POST/PUT/DELETE/PATCH, custom headers, raw body, Bearer/Basic auth shortcuts, timeout, optional TLS-verify skip. Portable across Linux / WSL / MinGW — requires only `curl` in PATH at runtime, no link-time libcurl. Returns status, body, response headers, error, duration_ms. Replaces inline curl-popen patterns in apps/services_monitor, dag_engine_ui, data_factory."
tags: [http, client, curl, network, registry-gap, core, helper]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
  - "core/http_request.h"
tested: true
tests:
  - "request: GET 200 status + body"
  - "request: GET 404 status (HTTP error not transport)"
  - "request: POST with body + content-type header"
  - "request: bearer_token shortcut adds Authorization header"
  - "request: basic_user/basic_pass shortcut authenticates"
  - "request: invalid URL surfaces transport error"
  - "request: timeout_ms honored"
test_file_path: "cpp/tests/test_http_request.cpp"
file_path: "cpp/functions/core/http_request.cpp"
params:
  - name: req
    desc: "Request struct: method (GET/POST/PUT/DELETE/PATCH, empty defaults to GET), url (full URL incl. scheme), headers (vector of {key,value}), body (raw request body bytes), timeout_ms (hard timeout, default 5000), bearer_token (shortcut for Authorization: Bearer), basic_user + basic_pass (shortcut for HTTP Basic, curl --user), insecure (bool — skip TLS verify, TESTING ONLY)."
output: "Response struct: status (HTTP code; 0 = transport error), body (response bytes), headers (vector of {key,value} as returned by server), error (empty on success, populated on transport failure / non-zero curl exit), duration_ms (wall clock of the call)."
---

## Ejemplo

```cpp
#include "core/http_request.h"

fn_http::Request req;
req.method  = "GET";
req.url     = "https://httpbin.org/get";
req.headers = {{"Accept", "application/json"}};
req.timeout_ms = 3000;

fn_http::Response res = fn_http::request(req);
if (!res.error.empty()) {
    fprintf(stderr, "transport error: %s\n", res.error.c_str());
} else if (res.status / 100 != 2) {
    fprintf(stderr, "http %d: %s\n", res.status, res.body.c_str());
} else {
    printf("OK %d (%lld ms): %.200s\n", res.status,
           (long long)res.duration_ms, res.body.c_str());
}
```

POST con JSON + Bearer auth:

```cpp
fn_http::Request req;
req.method  = "POST";
req.url     = "https://api.example.com/items";
req.headers = {{"Content-Type", "application/json"}};
req.body    = R"({"name":"hello"})";
req.bearer_token = std::getenv("API_TOKEN");
auto res = fn_http::request(req);
```

## Cuando usarla

- Cuando una app C++ necesita hablar HTTP con un endpoint (REST API local, servicio en VPS, webhook).
- Antes de copiar otra vez el patron `popen("curl -s ...")` desde `apps/services_monitor/http_client.cpp` o `apps/dag_engine_ui/main.cpp` — esto es exactamente la promocion que hace falta (issue 0110).
- Como base para wrappers JSON especificos (`http_get_json` ya existe; los siguientes deberian apilarse encima de `request`, no de curl directo).

## Gotchas

- **Requiere `curl` en `PATH`** en runtime. No hay fallback; si falta, `Response.error` lo dice y `status==0`.
- **Sin streaming**: la respuesta entera entra en memoria. No usar para downloads grandes (>50 MB).
- **`insecure=true` desactiva TLS verify** — solo para tests contra mocks self-signed. NUNCA en produccion.
- **Status `0` significa transport error** (DNS, conexion rechazada, timeout, curl ausente). Status `>=400` es respuesta HTTP normal — el caller decide si es fallo.
- **Headers son case-insensitive en HTTP**; aqui los devolvemos tal como los manda el servidor. Si necesitas lookup case-insensitive, normaliza en el caller.
- **Body multipart / form-encoded**: no implementado como shortcut. Construye el body a mano y setea `Content-Type` apropiado en `headers`.
- **Cookies**: no se mantienen sesion. Cada llamada es independiente. Si necesitas cookie jar, pasala via header `Cookie` explicito.
- **Shell injection**: los argumentos pasan via `popen` con quoting POSIX (single-quote escape). URLs y headers con comillas raras se manejan bien, pero NO pases input no confiable como `url` si el binario corre con privilegios.
