Um cliente HTTP Node.js poderoso e flexível com recursos avançados de scraping, circuit breaking, caching e muito mais.
- Instalação
- Recursos
- Uso Básico
- Configuração Avançada
- HTTP Methods
- Sistema de Logs
- Interceptadores
- Circuit Breaker
- Cache e Rate Limiting
- Sessões e Cookies
- Scraping
- GraphQL
- Server-Sent Events
- Métricas
- Tratamento de Erros
- CLI
- Exemplos Práticos
- FAQ
- Contribuição
- Licença
✨ Zero dependências não-nativas
- Baseado apenas em módulos nativos do Node.js
- Código limpo e eficiente
- Fácil manutenção e atualização
🔄 Retry automático com backoff exponencial
- Tentativas configuráveis
- Delay exponencial entre tentativas
- Tratamento inteligente de falhas
🛡️ Circuit breaker por domínio
- Proteção contra falhas em cascata
- Estados: CLOSED, OPEN, HALF-OPEN
- Configuração por domínio
🚀 HTTP/2 e IPv6
- Suporte completo a HTTP/2
- Multiplexação de requests
- Compatibilidade com IPv6
📊 Métricas detalhadas
- Tempo de resposta
- Taxa de sucesso/erro
- Uso de cache
- Métricas por rota
🔍 Scraping integrado
- Parser HTML inteligente
- Seletores CSS
- Transformação de dados
🎯 GraphQL e SSE
- Queries e mutations
- Subscrições em tempo real
- Validação de schema
🍪 Gerenciamento de sessões
- Cookies persistentes
- TTL configurável
- Limpeza automática
⚡ Cache inteligente
- Cache em memória
- TTL configurável
- Invalidação automática
🔒 Headers anti-bot
- User-Agent rotation
- Headers customizáveis
- Fingerprint aleatório
📝 Logs granulares
- Níveis de log configuráveis
- Formato customizável
- Debug mode
🔄 Interceptadores
- Pre/post request
- Transformação de resposta
- Validação de dados
⏱️ Timeouts granulares
- Connect timeout
- Response timeout
- Idle timeout
💾 Compressão automática
- GZIP
- Deflate
- Brotli
npm install swiftly
const swiftly = require('swiftly');
// GET request básico
const response = await swiftly.get('https://api.exemplo.com/posts');
console.log(response.data);
// Com query params
const userPosts = await swiftly.get('https://api.exemplo.com/posts', {
params: {
userId: 1,
status: 'published'
}
});
const post = await swiftly.post('https://api.exemplo.com/posts', {
title: 'Novo Post',
body: 'Conteúdo do post',
tags: ['javascript', 'node']
});
const fileBuffer = Buffer.from('Conteúdo do arquivo');
fileBuffer.name = 'documento.txt';
fileBuffer.type = 'text/plain';
const response = await swiftly.post('https://api.exemplo.com/upload', {
file: fileBuffer,
description: 'Meu documento'
}, {
formData: true
});
// Scraping simples
const titles = await swiftly.scrape('https://exemplo.com', '.post-title');
// Scraping com múltiplos seletores
const data = await swiftly.scrape('https://exemplo.com', {
title: 'h1.main-title',
description: 'meta[name="description"]',
posts: {
selector: '.post',
transform: (el) => ({
title: el.querySelector('h2').textContent,
link: el.querySelector('a').href,
date: new Date(el.querySelector('.date').textContent)
})
}
});
const data = await swiftly.query(`
query GetUser($id: ID!) {
user(id: $id) {
name
email
posts {
id
title
}
}
}
`, {
id: 1
});
const subscription = swiftly.subscribe('https://api.exemplo.com/events', {
onMessage: (data) => console.log('Novo evento:', data),
onError: (error) => console.error('Erro:', error),
onOpen: () => console.log('Conexão estabelecida'),
reconnect: true,
maxRetries: 5
});
const client = swiftly({
// Timeouts (ms)
timeouts: {
connect: 5000, // Timeout de conexão
response: 30000, // Timeout de resposta
idle: 60000 // Timeout de inatividade
},
// Cache
cache: {
enabled: true,
ttl: 300000, // 5 minutos
maxSize: 1000 // Máximo de itens
},
// Rate Limiting
rateLimiting: {
enabled: true,
requestsPerSecond: 2,
maxDelay: 64000, // Máximo delay entre requests
minDelay: 1000 // Mínimo delay entre requests
},
// Circuit Breaker
circuitBreaker: {
enabled: true,
failureThreshold: 5,
resetTimeout: 60000
},
// Sessões
session: {
ttl: 3600000, // 1 hora
maxSessions: 100,
autoCleanup: true
},
// Compressão
compression: {
request: true, // Comprimir requests
response: true, // Descomprimir responses
minSize: 1024 // Tamanho mínimo para comprimir
},
// Debug
debug: true, // Habilita logs detalhados
// HTTP/2
useHttp2: true, // Habilita suporte a HTTP/2
// Validação SSL
validateSSL: true, // Validar certificados SSL
// Redirects
followRedirects: true,
maxRedirects: 5
});
// GET básico
const response = await client.get('https://api.exemplo.com/data');
// Com query params
const filtered = await client.get('https://api.exemplo.com/posts', {
params: {
category: 'tech',
page: 1
}
});
// Com headers customizados
const protected = await client.get('https://api.exemplo.com/protected', {
headers: {
'Authorization': 'Bearer token'
}
});
// POST com JSON
const created = await client.post('https://api.exemplo.com/posts', {
title: 'Título',
content: 'Conteúdo'
});
// POST com form data
const formData = await client.post('https://api.exemplo.com/form', {
name: 'João',
age: 25
}, {
formData: true
});
// POST com arquivo
const fileUpload = await client.post('https://api.exemplo.com/upload', {
file: fileBuffer,
type: 'document'
}, {
formData: true
});
const updated = await client.put('https://api.exemplo.com/posts/1', {
title: 'Título Atualizado'
});
const deleted = await client.delete('https://api.exemplo.com/posts/1');
const client = swiftly({ debug: true });
// Os logs incluem:
// - Requests e responses
// - Cache hits/misses
// - Circuit breaker state
// - Rate limiting delays
// - Session cleanup
// - Erros e retries
// Exemplo de saída:
// [Swiftly 2024-03-19T10:15:30.123Z] GET request to https://api.exemplo.com
// [Swiftly 2024-03-19T10:15:30.456Z] Response: 200 OK
const client = swiftly();
// Request interceptor
client.interceptors.request.use((config) => {
// Adicionar headers
config.headers['Authorization'] = 'Bearer token';
return config;
});
// Response interceptor com validação
client.interceptors.response.use((response) => {
const schema = {
id: { type: 'number', required: true },
title: { type: 'string', minLength: 1 }
};
validateSchema(response.data, schema);
return response;
});
const client = swiftly({
circuitBreaker: {
enabled: true,
failureThreshold: 5,
resetTimeout: 60000
}
});
// Monitorar eventos do circuit breaker
client.on('circuit:open', (data) => {
console.log(`Circuit breaker aberto para: ${data.domain}`);
console.log(`Falhas: ${data.failureCount}`);
console.log(`Reset em: ${data.resetTimeout}ms`);
});
client.on('circuit:close', (data) => {
console.log(`Circuit breaker fechado para: ${data.domain}`);
});
client.on('circuit:half-open', (data) => {
console.log(`Circuit breaker em half-open para: ${data.domain}`);
});
const client = swiftly({
cache: {
enabled: true,
ttl: 300000
},
rateLimiting: {
enabled: true,
requestsPerSecond: 2
}
});
// Eventos de cache
client.on('cache:hit', (data) => console.log('Cache hit:', data.url));
client.on('cache:miss', (data) => console.log('Cache miss:', data.url));
// Eventos de rate limiting
client.on('rate:limit', (data) => {
console.log(`Rate limit atingido: ${data.delay}ms delay`);
});
// Rate limiting adaptativo
client.on('rate:adapt', (data) => {
console.log(`Novo rate limit: ${data.requestsPerSecond}/s`);
});
const client = swiftly({
session: {
ttl: 3600000,
maxSessions: 100
}
});
// Login e persistência de sessão
const auth = await client.post('https://api.exemplo.com/login', {
username: 'user',
password: 'pass'
});
// Requests autenticados usam a sessão automaticamente
const profile = await client.get('https://api.exemplo.com/profile');
// Eventos de sessão
client.on('sessions:cleanup', (data) => {
console.log(`${data.cleaned} sessões limpas`);
});
// Scraping com transformação
const articles = await swiftly.scrape('https://blog.exemplo.com', {
selector: 'article',
transform: (el) => ({
title: el.querySelector('h2').textContent,
link: el.querySelector('a').href,
date: new Date(el.querySelector('.date').textContent),
tags: Array.from(el.querySelectorAll('.tag')).map(tag => tag.textContent),
author: {
name: el.querySelector('.author-name').textContent,
avatar: el.querySelector('.author-avatar').src
}
})
});
// Scraping paginado
async function* scrapeAllPages(url) {
let currentPage = 1;
while (true) {
const articles = await swiftly.scrape(`${url}?page=${currentPage}`, '.article');
if (articles.length === 0) break;
yield articles;
currentPage++;
}
}
console.log(client.metrics);
/*
{
requestCount: 150,
successCount: 145,
errorCount: 5,
totalTime: 2500,
averageResponseTime: 16.67,
cacheHits: 50,
cacheMisses: 100,
retries: 3,
totalDataTransferred: 1048576,
http2Requests: 75
}
*/
// Métricas por rota
console.log(client.routeMetrics);
/*
{
'GET /api/users': {
count: 50,
totalTime: 750,
avgTime: 15
}
}
*/
try {
await client.get('https://api.exemplo.com/data');
} catch (error) {
if (error.name === 'ValidationError') {
console.log('Erro de validação:', error.message);
console.log('Contexto:', error.context);
}
else if (error.name === 'CircuitBreakerError') {
console.log('Circuit breaker aberto para:', error.domain);
console.log('Reset em:', error.resetTimeout);
}
else if (error.name === 'TimeoutError') {
console.log('Tipo de timeout:', error.type); // connect, response, idle
console.log('Limite:', error.timeout);
}
else if (error.name === 'RateLimitError') {
console.log('Rate limit excedido');
console.log('Próxima tentativa em:', error.nextAttempt);
}
else if (error.response) {
// Erro HTTP com resposta
console.log('Status:', error.response.status);
console.log('Data:', error.response.data);
}
}
# GET request
swiftly get https://api.exemplo.com/data
# GET com query params
swiftly get https://api.exemplo.com/posts --params '{"userId":1}'
# POST com dados
swiftly post https://api.exemplo.com/posts -d '{"title":"Novo Post"}'
# Scraping
swiftly scrape https://exemplo.com -s '.post-title'
# Scraping com transformação
swiftly scrape https://exemplo.com -s '.post' --transform './transform.js'
# GraphQL query
swiftly query 'query { users { id name } }'
# Monitorar eventos
swiftly events https://api.exemplo.com/stream
# Upload de arquivo
swiftly upload https://api.exemplo.com/upload ./arquivo.txt
# Debug mode
swiftly get https://api.exemplo.com/data --debug
const client = swiftly();
// Login
const auth = await client.post('https://api.exemplo.com/login', {
username: 'user',
password: 'pass'
});
// Requests autenticados usam a sessão automaticamente
const profile = await client.get('https://api.exemplo.com/profile');
const file = Buffer.from('Conteúdo do arquivo');
file.name = 'documento.txt';
file.type = 'text/plain';
const response = await client.post('https://api.exemplo.com/upload', {
file: file,
description: 'Meu documento'
}, {
formData: true
});
const client = swiftly({
rateLimiting: {
enabled: true,
requestsPerSecond: 2,
adaptive: true,
maxDelay: 64000,
minDelay: 1000
}
});
// O rate limiting se adapta baseado nas respostas do servidor
client.on('rate:adapt', (data) => {
console.log(`Novo rate limit: ${data.requestsPerSecond}/s`);
});
async function scrapeAllArticles() {
const articles = [];
let page = 1;
while (true) {
const pageArticles = await swiftly.scrape(`https://blog.exemplo.com/page/${page}`, {
selector: 'article',
transform: (el) => ({
title: el.querySelector('h2').textContent,
link: el.querySelector('a').href,
date: new Date(el.querySelector('.date').textContent)
})
});
if (pageArticles.length === 0) break;
articles.push(...pageArticles);
page++;
}
return articles;
}
// Query
const data = await client.query(`
query GetUserPosts($userId: ID!) {
user(id: $userId) {
posts {
id
title
}
}
}
`, {
userId: 1
});
// Mutation
const newPost = await client.query(`
mutation CreatePost($input: PostInput!) {
createPost(input: $input) {
id
title
}
}
`, {
input: {
title: 'Novo Post',
body: 'Conteúdo'
}
});
// Subscription
const unsubscribe = await client.subscribe(`
subscription OnNewPost {
newPost {
id
title
}
}
`, {
onData: (post) => console.log('Novo post:', post),
onError: (error) => console.error('Erro:', error)
});
const client = swiftly({ debug: true });
// Monitorar todos os eventos importantes
[
'request:start',
'request:end',
'cache:hit',
'cache:miss',
'circuit:open',
'circuit:close',
'sessions:cleanup',
'rate:limit',
'progress'
].forEach(event => {
client.on(event, (data) => {
console.log(`[${event}]`, data);
// Salvar métricas
if (event === 'request:end') {
saveMetrics({
url: data.url,
method: data.method,
status: data.status,
time: data.time
});
}
});
});
const client = swiftly({
timeouts: {
connect: 5000, // Timeout de conexão
response: 30000, // Timeout de resposta
idle: 60000 // Timeout de inatividade
}
});
try {
await client.get('https://api.exemplo.com/slow-endpoint');
} catch (error) {
if (error.name === 'TimeoutError') {
console.log(`Timeout do tipo ${error.type}`);
}
}
client.interceptors.response.use((response) => {
const schema = {
id: { type: 'number', required: true },
title: { type: 'string', minLength: 1 },
date: { type: 'string', format: 'date-time' }
};
validateSchema(response.data, schema);
return response;
});
const client = swiftly({
retries: 3,
retryDelay: 1000,
retryCondition: (error) => {
return error.status === 429 || error.status >= 500;
}
});
Contribuições são sempre bem-vindas! Por favor, leia nosso guia de contribuição antes de submeter um pull request.
- Fork o projeto
- Crie sua feature branch (
git checkout -b feature/AmazingFeature
) - Commit suas mudanças (
git commit -m 'Add some AmazingFeature'
) - Push para a branch (
git push origin feature/AmazingFeature
) - Abra um Pull Request
Cognima
- Github: @cognima
- Node.js core team
- Todos os contribuidores
- Nossa comunidade incrível
⭐ Se este projeto te ajudou, por favor considere dar uma estrela no GitHub!