Cliente HTTP

Realizar solicitudes HTTP a servicios externos. Soporta todos los metodos HTTP, cabeceras, parametros de consulta, datos de formulario, carga de archivos, respuestas en streaming y solicitudes por lotes concurrentes.

Carga

local http_client = require("http_client")

Metodos HTTP

Todos los metodos comparten la misma firma: method(url, options?) devolviendo Response, error.

Solicitud GET

local resp, err = http_client.get("https://api.example.com/users")
if err then
    return nil, err
end

print(resp.status_code)  -- 200
print(resp.body)         -- cuerpo de respuesta

Solicitud POST

local resp, err = http_client.post("https://api.example.com/users", {
    headers = {["Content-Type"] = "application/json"},
    body = json.encode({name = "Alice", email = "alice@example.com"})
})

Solicitud PUT

local resp, err = http_client.put("https://api.example.com/users/123", {
    headers = {["Content-Type"] = "application/json"},
    body = json.encode({name = "Alice Smith"})
})

Solicitud PATCH

local resp, err = http_client.patch("https://api.example.com/users/123", {
    body = json.encode({status = "active"})
})

Solicitud DELETE

local resp, err = http_client.delete("https://api.example.com/users/123", {
    headers = {["Authorization"] = "Bearer " .. token}
})

Solicitud HEAD

Devuelve solo cabeceras, sin cuerpo.

local resp, err = http_client.head("https://cdn.example.com/file.zip")
local size = resp.headers["Content-Length"]

Método Personalizado

local resp, err = http_client.request("PROPFIND", "https://dav.example.com/folder", {
    headers = {["Depth"] = "1"}
})
Parámetro Tipo Descripción
method string Método HTTP
url string URL de solicitud
options table Opciones de solicitud (opcional)

Opciones de Solicitud

Campo Tipo Descripción
headers table Cabeceras de solicitud {["Name"] = "value"}
body string Cuerpo de solicitud
query table Parametros de consulta {key = "value"}
form table Datos de formulario (establece Content-Type automaticamente)
files table Carga de archivos (array de definiciones de archivo)
cookies table Cookies de solicitud {name = "value"}
auth table Autenticación basica {user = "name", pass = "secret"}
timeout number/string Timeout: número en segundos, o string como "30s", "1m"
stream boolean Transmitir cuerpo de respuesta en lugar de almacenar en buffer
max_response_body number Tamano maximo de respuesta en bytes (0 = predeterminado)
unix_socket string Conectar via ruta de socket Unix
tls table Configuracion TLS por solicitud (ver Opciones TLS)

Parametros de Consulta

local resp, err = http_client.get("https://api.example.com/search", {
    query = {
        q = "lua programming",
        page = "1",
        limit = "20"
    }
})

Cabeceras y Autenticación

local resp, err = http_client.get("https://api.example.com/data", {
    headers = {
        ["Authorization"] = "Bearer " .. token,
        ["Accept"] = "application/json"
    }
})

-- O usar autenticación basica
local resp, err = http_client.get("https://api.example.com/data", {
    auth = {user = "admin", pass = "secret"}
})

Datos de Formulario

local resp, err = http_client.post("https://api.example.com/login", {
    form = {
        username = "alice",
        password = "secret123"
    }
})

Carga de Archivos

local resp, err = http_client.post("https://api.example.com/upload", {
    form = {title = "My Document"},
    files = {
        {
            name = "attachment",      -- nombre de campo de formulario
            filename = "report.pdf",  -- nombre de archivo original
            content = pdf_data,       -- contenido del archivo
            content_type = "application/pdf"
        }
    }
})
Campo de Archivo Tipo Requerido Descripción
name string si Nombre de campo de formulario
filename string no Nombre de archivo original
content string si* Contenido del archivo
reader userdata si* Alternativa: io.Reader para contenido
content_type string no Tipo MIME (predeterminado: application/octet-stream)

*Se requiere content o reader.

Timeout

-- Número: segundos
local resp, err = http_client.get(url, {timeout = 30})

-- String: formato de duración Go
local resp, err = http_client.get(url, {timeout = "30s"})
local resp, err = http_client.get(url, {timeout = "1m30s"})
local resp, err = http_client.get(url, {timeout = "1h"})

Opciones TLS

Configurar ajustes TLS por solicitud para mTLS (TLS mutuo) y certificados CA personalizados.

Campo Tipo Descripcion
cert string Certificado de cliente en formato PEM
key string Clave privada del cliente en formato PEM
ca string Certificado CA personalizado en formato PEM
server_name string Nombre del servidor para verificacion SNI
insecure_skip_verify boolean Omitir verificacion de certificado TLS

Tanto cert como key deben proporcionarse juntos para mTLS. El campo ca reemplaza el pool de certificados del sistema con un CA personalizado.

Autenticacion mTLS

local cert_pem = fs.read("/certs/client.crt")
local key_pem = fs.read("/certs/client.key")

local resp, err = http_client.get("https://secure.example.com/api", {
    tls = {
        cert = cert_pem,
        key = key_pem,
    }
})

CA Personalizado

local ca_pem = fs.read("/certs/internal-ca.crt")

local resp, err = http_client.get("https://internal.example.com/api", {
    tls = {
        ca = ca_pem,
        server_name = "internal.example.com",
    }
})

Omitir Verificacion Insegura

Omitir verificacion TLS para entornos de desarrollo. Requiere el permiso de seguridad http_client.insecure_tls.

local resp, err = http_client.get("https://localhost:8443/api", {
    tls = {
        insecure_skip_verify = true,
    }
})

Objeto Response

Campo Tipo Descripción
status_code number Código de estado HTTP
body string Cuerpo de respuesta (si no es streaming)
body_size number Tamano del cuerpo en bytes (-1 si es streaming)
headers table Cabeceras de respuesta
cookies table Cookies de respuesta
url string URL final (despues de redirecciones)
stream Stream Objeto Stream (si stream = true)
local resp, err = http_client.get("https://api.example.com/data")
if err then
    return nil, err
end

if resp.status_code == 200 then
    local data = json.decode(resp.body)
    print("Content-Type:", resp.headers["Content-Type"])
end

Respuestas en Streaming

Para respuestas grandes, use streaming para evitar cargar todo el cuerpo en memoria.

local resp, err = http_client.get("https://cdn.example.com/large-file.zip", {
    stream = true
})
if err then
    return nil, err
end

-- Procesar en fragmentos
while true do
    local chunk, err = resp.stream:read(65536)
    if err or not chunk then break end
    -- procesar fragmento
end
resp.stream:close()
Método de Stream Devuelve Descripción
read(size) string, error Leer hasta size bytes
close() - Cerrar el stream

Solicitudes por Lotes

Ejecutar multiples solicitudes concurrentemente.

local responses, errors = http_client.request_batch({
    {"GET", "https://api.example.com/users"},
    {"GET", "https://api.example.com/products"},
    {"POST", "https://api.example.com/log", {body = "event"}}
})

if errors then
    for i, err in ipairs(errors) do
        if err then
            print("Solicitud " .. i .. " fallo:", err)
        end
    end
else
    -- Todas exitosas
    for i, resp in ipairs(responses) do
        print("Respuesta " .. i .. ":", resp.status_code)
    end
end
Parámetro Tipo Descripción
requests table Array de {method, url, options?}

Devuelve: responses, errors - arrays indexados por posicion de solicitud

Notas:

  • Las solicitudes se ejecutan concurrentemente
  • Streaming (stream = true) no es soportado en lotes
  • Los arrays de resultado coinciden con el orden de solicitud (indexado desde 1)

Codificacion de URL

Codificar

local encoded = http_client.encode_uri("hello world")
-- "hello+world"

local url = "https://api.example.com/search?q=" .. http_client.encode_uri(query)

Decodificar

local decoded, err = http_client.decode_uri("hello+world")
-- "hello world"

Permisos

Las solicitudes HTTP estan sujetas a evaluacion de politica de seguridad.

Acciones de Seguridad

Accion Recurso Descripción
http_client.request URL Permitir/denegar solicitudes a URLs especificas
http_client.unix_socket Ruta de socket Permitir/denegar conexiones de socket Unix
http_client.private_ip Direccion IP Permitir/denegar acceso a rangos de IP privados
http_client.insecure_tls URL Permitir/denegar TLS inseguro (omitir verificacion)

Verificar Acceso

local security = require("security")

if security.can("http_client.request", "https://api.example.com/users") then
    local resp = http_client.get("https://api.example.com/users")
end

Proteccion SSRF

Los rangos de IP privados (10.x, 192.168.x, 172.16-31.x, localhost) estan bloqueados por defecto. El acceso requiere el permiso http_client.private_ip.

local resp, err = http_client.get("http://192.168.1.1/admin")
-- Error: no permitido: IP privada 192.168.1.1

Consulte Modelo de Seguridad para configuración de politicas.

Errores

Condición Tipo Reintentable
Politica de seguridad denegada errors.PERMISSION_DENIED no
IP privada bloqueada errors.PERMISSION_DENIED no
Socket Unix denegado errors.PERMISSION_DENIED no
TLS inseguro denegado errors.PERMISSION_DENIED no
URL u opciones invalidas errors.INVALID no
Sin contexto errors.INTERNAL no
Fallo de red errors.INTERNAL si
Timeout errors.INTERNAL si
local resp, err = http_client.get(url)
if err then
    if errors.is(err, errors.PERMISSION_DENIED) then
        print("Acceso denegado:", err:message())
    elseif err:retryable() then
        print("Error temporal:", err:message())
    end
    return nil, err
end

Consulte Manejo de Errores para trabajar con errores.