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.