Cliente HTTP
Cliente HTTP
Seção intitulada “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
Seção intitulada “Carregamento”local http_client = require("http_client")Métodos HTTP
Seção intitulada “Métodos HTTP”Todos os métodos compartilham a mesma assinatura: method(url, options?) retornando Response, error.
Requisição GET
Seção intitulada “Requisição 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) -- corpo da respostaRequisição POST
Seção intitulada “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
Seção intitulada “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
Seção intitulada “Requisição PATCH”local resp, err = http_client.patch("https://api.example.com/users/123", { body = json.encode({status = "active"})})Requisição DELETE
Seção intitulada “Requisição DELETE”local resp, err = http_client.delete("https://api.example.com/users/123", { headers = {["Authorization"] = "Bearer " .. token}})Requisição HEAD
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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 authlocal resp, err = http_client.get("https://api.example.com/data", { auth = {user = "admin", pass = "secret"}})Dados de Formulario
Seção intitulada “Dados de Formulario”local resp, err = http_client.post("https://api.example.com/login", { form = { username = "alice", password = "secret123" }})Upload de Arquivo
Seção intitulada “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
Seção intitulada “Timeout”-- Numero: segundoslocal resp, err = http_client.get(url, {timeout = 30})
-- String: formato de duração 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"})Opcoes TLS
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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
Seção intitulada “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, errend
if resp.status_code == 200 then local data = json.decode(resp.body) print("Content-Type:", resp.headers["Content-Type"])endRespostas em Streaming
Seção intitulada “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, errend
-- Processar em chunkswhile true do local chunk, err = resp.stream:read(65536) if err or not chunk then break end -- processar chunkendresp.stream:close()| Método Stream | Retorna | Descrição |
|---|---|---|
read(n?) | string, error | Ler até n bytes (padrão: buffer da implementação) |
close() | boolean, error | Fechar o stream |
resp.stream é um objeto stream completo — seek, stat e scanner também estão disponíveis.
Requisicoes em Lote
Seção intitulada “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 endelse -- Todas bem-sucedidas for i, resp in ipairs(responses) do print("Response " .. i .. ":", resp.status_code) endend| 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
Seção intitulada “Codificação de URL”Codificar
Seção intitulada “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
Seção intitulada “Decodificar”local decoded, err = http_client.decode_uri("hello+world")-- "hello world"Permissões
Seção intitulada “Permissões”Requisicoes HTTP estao sujeitas a avaliação de política de segurança.
Acoes de Segurança
Seção intitulada “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
Seção intitulada “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")endProtecao SSRF
Seção intitulada “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.1Veja Security Model para configuração de políticas.
| 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, errendVeja Error Handling para trabalhar com erros.