Cliente HTTP
Cliente HTTP
Sección titulada «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.
local http_client = require("http_client")Metodos HTTP
Sección titulada «Metodos HTTP»Todos los metodos comparten la misma firma: method(url, options?) devolviendo Response, error.
Solicitud GET
Sección titulada «Solicitud GET»local resp, err = http_client.get("https://api.example.com/users")if err then return nil, errend
print(resp.status_code) -- 200print(resp.body) -- cuerpo de respuestaSolicitud POST
Sección titulada «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
Sección titulada «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
Sección titulada «Solicitud PATCH»local resp, err = http_client.patch("https://api.example.com/users/123", { body = json.encode({status = "active"})})Solicitud DELETE
Sección titulada «Solicitud DELETE»local resp, err = http_client.delete("https://api.example.com/users/123", { headers = {["Authorization"] = "Bearer " .. token}})Solicitud HEAD
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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 basicalocal resp, err = http_client.get("https://api.example.com/data", { auth = {user = "admin", pass = "secret"}})Datos de Formulario
Sección titulada «Datos de Formulario»local resp, err = http_client.post("https://api.example.com/login", { form = { username = "alice", password = "secret123" }})Carga de Archivos
Sección titulada «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
Sección titulada «Timeout»-- Número: segundoslocal resp, err = http_client.get(url, {timeout = 30})
-- String: formato de duración Golocal 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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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
Sección titulada «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, errend
if resp.status_code == 200 then local data = json.decode(resp.body) print("Content-Type:", resp.headers["Content-Type"])endRespuestas en Streaming
Sección titulada «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, errend
-- Procesar en fragmentoswhile true do local chunk, err = resp.stream:read(65536) if err or not chunk then break end -- procesar fragmentoendresp.stream:close()| Método de Stream | Devuelve | Descripción |
|---|---|---|
read(n?) | string, error | Leer hasta n bytes (predeterminado: buffer de implementación) |
close() | boolean, error | Cerrar el stream |
resp.stream es un objeto stream completo — seek, stat y scanner también están disponibles.
Solicitudes por Lotes
Sección titulada «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 endelse -- Todas exitosas for i, resp in ipairs(responses) do print("Respuesta " .. i .. ":", resp.status_code) endend| 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
Sección titulada «Codificacion de URL»Codificar
Sección titulada «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
Sección titulada «Decodificar»local decoded, err = http_client.decode_uri("hello+world")-- "hello world"Permisos
Sección titulada «Permisos»Las solicitudes HTTP estan sujetas a evaluacion de politica de seguridad.
Acciones de Seguridad
Sección titulada «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
Sección titulada «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")endProteccion SSRF
Sección titulada «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.1Consulte Modelo de Seguridad para configuración de politicas.
Errores
Sección titulada «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, errendConsulte Manejo de Errores para trabajar con errores.