Cliente HTTP

Faca requisicoes HTTP para serviços externos. Suporta todos os métodos HTTP, headers, parametros de query, dados de formulario, uploads de arquivo, respostas em streaming e requisicoes em lote concorrentes.

Carregamento

local http_client = require("http_client")

Métodos HTTP

Todos os métodos compartilham a mesma assinatura: method(url, options?) retornando Response, error.

Requisição 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)         -- corpo da resposta

Requisição 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"})
})

Requisição PUT

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

Requisição PATCH

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

Requisição DELETE

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

Requisição HEAD

Retorna apenas headers, sem corpo.

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

Método Customizado

local resp, err = http_client.request("PROPFIND", "https://dav.example.com/folder", {
    headers = {["Depth"] = "1"}
})
Parâmetro Tipo Descrição
method string Método HTTP
url string URL da requisição
options table Opções da requisição (opcional)

Opções de Requisição

Campo Tipo Descrição
headers table Headers da requisição {["Name"] = "value"}
body string Corpo da requisição
query table Parametros de query {key = "value"}
form table Dados de formulario (define Content-Type automaticamente)
files table Uploads de arquivo (array de definicoes de arquivo)
cookies table Cookies da requisição {name = "value"}
auth table Basic auth {user = "name", pass = "secret"}
timeout number/string Timeout: numero em segundos, ou string como "30s", "1m"
stream boolean Streaming do corpo da resposta ao inves de buffer
max_response_body number Tamanho maximo da resposta em bytes (0 = padrão)
unix_socket string Conectar via caminho de socket Unix
tls table Configuracao TLS por requisicao (ver Opcoes TLS)

Parametros de Query

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

Headers e Autenticação

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

-- Ou usar basic auth
local resp, err = http_client.get("https://api.example.com/data", {
    auth = {user = "admin", pass = "secret"}
})

Dados de Formulario

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

Upload de Arquivo

local resp, err = http_client.post("https://api.example.com/upload", {
    form = {title = "My Document"},
    files = {
        {
            name = "attachment",      -- nome do campo do formulario
            filename = "report.pdf",  -- nome original do arquivo
            content = pdf_data,       -- conteudo do arquivo
            content_type = "application/pdf"
        }
    }
})
Campo de Arquivo Tipo Obrigatorio Descrição
name string sim Nome do campo do formulario
filename string não Nome original do arquivo
content string sim* Conteudo do arquivo
reader userdata sim* Alternativa: io.Reader para conteudo
content_type string não Tipo MIME (padrão: application/octet-stream)

*content ou reader e obrigatorio.

Timeout

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

-- String: formato de duração 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"})

Opcoes TLS

Configure opcoes TLS por requisicao para mTLS (mutual TLS) e certificados CA customizados.

Campo Tipo Descrição
cert string Certificado do cliente em formato PEM
key string Chave privada do cliente em formato PEM
ca string Certificado CA customizado em formato PEM
server_name string Nome do servidor para verificacao SNI
insecure_skip_verify boolean Pular verificacao de certificado TLS

Tanto cert quanto key devem ser fornecidos juntos para mTLS. O campo ca substitui o pool de certificados do sistema por um CA customizado.

Autenticacao 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 Customizado

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",
    }
})

Pular Verificacao TLS

Pular verificacao TLS para ambientes de desenvolvimento. Requer a permissão de segurança http_client.insecure_tls.

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

Objeto Response

Campo Tipo Descrição
status_code number Código de status HTTP
body string Corpo da resposta (se não streaming)
body_size number Tamanho do corpo em bytes (-1 se streaming)
headers table Headers da resposta
cookies table Cookies da resposta
url string URL final (apos redirecionamentos)
stream Stream Objeto stream (se 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

Respostas em Streaming

Para respostas grandes, use streaming para evitar carregar o corpo inteiro na memoria.

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

-- Processar em chunks
while true do
    local chunk, err = resp.stream:read(65536)
    if err or not chunk then break end
    -- processar chunk
end
resp.stream:close()
Método Stream Retorna Descrição
read(size) string, error Ler até size bytes
close() - Fechar o stream

Requisicoes em Lote

Executar multiplas requisicoes concorrentemente.

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("Request " .. i .. " failed:", err)
        end
    end
else
    -- Todas bem-sucedidas
    for i, resp in ipairs(responses) do
        print("Response " .. i .. ":", resp.status_code)
    end
end
Parâmetro Tipo Descrição
requests table Array de {method, url, options?}

Retorna: responses, errors - arrays indexados pela posicao da requisição

Notas:

  • Requisicoes executam concorrentemente
  • Streaming (stream = true) não e suportado em lote
  • Arrays de resultado correspondem a ordem das requisicoes (indexados a partir de 1)

Codificação 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"

Permissões

Requisicoes HTTP estao sujeitas a avaliação de política de segurança.

Acoes de Segurança

Ação Recurso Descrição
http_client.request URL Permitir/negar requisicoes para URLs específicas
http_client.unix_socket Caminho do socket Permitir/negar conexoes Unix socket
http_client.private_ip Endereco IP Permitir/negar acesso a faixas de IP privado
http_client.insecure_tls URL Permitir/negar TLS inseguro (pular verificacao)

Verificando Acesso

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

Protecao SSRF

Faixas de IP privado (10.x, 192.168.x, 172.16-31.x, localhost) sao bloqueadas por padrão. Acesso requer a permissão http_client.private_ip.

local resp, err = http_client.get("http://192.168.1.1/admin")
-- Erro: not allowed: private IP 192.168.1.1

Veja Security Model para configuração de políticas.

Erros

Condição Tipo Retentável
Política de segurança negou errors.PERMISSION_DENIED não
IP privado bloqueado errors.PERMISSION_DENIED não
Socket Unix negado errors.PERMISSION_DENIED não
TLS inseguro negado errors.PERMISSION_DENIED não
URL ou opções invalidas errors.INVALID não
Sem contexto errors.INTERNAL não
Falha de rede errors.INTERNAL sim
Timeout errors.INTERNAL sim
local resp, err = http_client.get(url)
if err then
    if errors.is(err, errors.PERMISSION_DENIED) then
        print("Access denied:", err:message())
    elseif err:retryable() then
        print("Temporary error:", err:message())
    end
    return nil, err
end

Veja Error Handling para trabalhar com erros.