laravel-http-service maintained by allyson
HTTP Service - Laravel Package
Pacote Laravel para gerenciamento avançado de requisições HTTP com logging automático e controle de rate limiting.
Características
- Logging automático de todas as requisições HTTP (URL, payload, response)
- Controle inteligente de rate limiting (429) usando banco de dados
- Armazenamento completo do histórico de requisições
- Totalmente configurável via arquivo de config ou .env
- Compatível com Laravel 12
- Gerenciamento de domínios bloqueados com timestamps
- Tracking de tempo de resposta
- Comandos Artisan para gerenciamento
- Controle granular - habilite/desabilite logging e rate limit por requisição
Instalação
Via Composer
composer require 3rn/http-service
Configuração Inicial
Execute o comando de instalação que irá publicar config e migrations:
php artisan http-service:install
Execute as migrations:
php artisan migrate
Uso Rápido
use ThreeRN\HttpService\Facades\HttpService;
// Requisição GET
$response = HttpService::get('https://api.example.com/users');
$users = $response->json();
// Requisição POST
$response = HttpService::post('https://api.example.com/users', [
'name' => 'John Doe',
'email' => 'john@example.com'
]);
// Com headers customizados
$response = HttpService::post(
'https://api.example.com/data',
['key' => 'value'],
['Authorization' => 'Bearer token123']
);
Métodos Disponíveis
Requisições HTTP
// GET com query parameters
HttpService::get($url, $query = [], $headers = []);
// POST com dados
HttpService::post($url, $data = [], $headers = []);
// PUT para atualização completa
HttpService::put($url, $data = [], $headers = []);
// PATCH para atualização parcial
HttpService::patch($url, $data = [], $headers = []);
// DELETE
HttpService::delete($url, $data = [], $headers = []);
Controles Opcionais
// Desabilitar logging temporariamente
HttpService::withoutLogging()->get($url);
// Habilitar logging explicitamente
HttpService::withLogging()->post($url, $data);
// Desabilitar verificação de rate limit
HttpService::withoutRateLimit()->get($url);
// Timeout customizado (em segundos)
HttpService::timeout(60)->get($url);
// Encadear múltiplas opções
HttpService::withoutLogging()
->withoutRateLimit()
->timeout(30)
->get($url);
Cache de Requisições
O pacote oferece recursos avançados de cache para otimizar requisições repetidas.
Cache com Expiração Dinâmica
Cache baseado em campos da resposta que contêm informações de expiração:
// Cache usando campo de data/hora
// Resposta: {"token": "abc123", "expirationTime": "2025-12-17 13:02:14"}
$response = HttpService::cacheUsingExpires('expirationTime')
->expiresAsDatetime() // Padrão, pode ser omitido
->get('https://api.example.com/auth/token');
// Cache usando campo aninhado
// Resposta: {"data": {"auth": {"expires": "2025-12-17 15:30:00"}}}
$response = HttpService::cacheUsingExpires('data.auth.expires')
->expiresAsDatetime()
->post('https://api.example.com/login', $credentials);
// Cache usando segundos
// Resposta: {"token": "xyz789", "expires_in": 3600}
$response = HttpService::cacheUsingExpires('expires_in')
->expiresAsSeconds()
->get('https://api.example.com/token');
// Cache usando minutos
// Resposta: {"session_id": "sess_123", "ttl": 30}
$response = HttpService::cacheUsingExpires('ttl')
->expiresAsMinutes()
->get('https://api.example.com/session');
Máscara de Expiração
Defina como interpretar o valor do campo de expiração:
expiresAsDatetime()- Data/hora no formato Y-m-d H:i:s ou ISO 8601 (padrão)expiresAsSeconds()- Valor em segundosexpiresAsMinutes()- Valor em minutos
TTL de Fallback
Use um TTL padrão caso o campo não seja encontrado:
// Se 'expirationTime' não existir, cacheia por 2 horas (7200 segundos)
$response = HttpService::cacheUsingExpires('expirationTime', 7200)
->expiresAsDatetime()
->get('https://api.example.com/data');
Cache Fixo
Cache com tempo de vida fixo:
// Cache por 1 hora (3600 segundos)
$response = HttpService::withCache(3600)
->get('https://api.example.com/data');
Cache Condicional
Cache ativado apenas após múltiplas chamadas:
// Cache após 3 chamadas em 60 segundos, com TTL de 1 hora
$response = HttpService::cacheWhen(3, 60, 3600)
->get('https://api.example.com/data');
Desabilitar Cache
// Desabilitar cache para uma requisição específica
$response = HttpService::withoutCache()
->get('https://api.example.com/data');
Limpar Cache
// Limpar todo o cache de requisições
HttpService::clearCache();
Cache por Status (cacheOnly / cacheExcept)
Você pode controlar o cache com base nos códigos HTTP da resposta usando os métodos cacheOnly e cacheExcept.
cacheOnly(array $statuses, ?int $ttl = null): self- Armazena em cache apenas as respostas cujo
status_codeesteja presente em$statuses. - Se
$ttlfor passado, ele sobrescreve o TTL para essa requisição específica (em segundos). - Retorna uma cópia (
clone) doHttpService, então a configuração afeta apenas a cadeia encadeada desta chamada.
- Armazena em cache apenas as respostas cujo
// Cachear SOMENTE respostas 200 por 10 minutos
$response = HttpService::cacheOnly([200], 600)
->get('https://api.example.com/data');
// Cachear respostas 200 e 201 (com TTL customizado)
$response = HttpService::cacheOnly([200, 201], 300)
->post('https://api.example.com/create', $payload);
cacheExcept(array $statuses, ?int $ttl = null): self- Armazena em cache todas as respostas exceto aquelas cujo
status_codeesteja em$statuses. - Se
$ttlfor passado, ele sobrescreve o TTL para essa requisição específica (em segundos). - Retorna uma cópia (
clone) doHttpService, então a configuração afeta apenas a cadeia encadeada desta chamada.
- Armazena em cache todas as respostas exceto aquelas cujo
// Cachear tudo exceto erro 500
$response = HttpService::cacheExcept([500])
->get('https://api.example.com/data');
// Cachear tudo exceto 4xx e 5xx (exemplo)
$response = HttpService::cacheExcept([400,401,403,404,500,502,503], 3600)
->get('https://api.example.com/data');
Observações de implementação:
- Os métodos
cacheOnlyecacheExceptdefinemcacheStrategy = 'always', portanto ativam o cache para aquela chamada. - Os filtros são aplicados somente no momento de armazenamento: o pacote verifica o
statusda resposta e respeitacacheOnlyStatusesecacheExceptStatusesantes de persistir no cache. cacheOnlylimpacacheExceptStatusese vice-versa; assim, elas não entram em conflito.- Se você preferir limpar ambos os filtros manualmente, use
clearCacheStatusFilters().
Revisão da implementação
Após revisar src/Services/HttpService.php, os métodos cacheOnly e cacheExcept já estão implementados corretamente e não precisam de alterações funcionais imediatas. Uma sugestão opcional para consistência é que clearCacheStatusFilters() poderia retornar um clone (como outros métodos que configuram comportamento) para manter o padrão imutável/encadeável, mas isso não é obrigatório.
Formatos de Data/Hora Suportados
Para expiresAsDatetime():
Y-m-d H:i:s- Exemplo: "2025-12-17 13:02:14"- ISO 8601 - Exemplo: "2025-12-17T13:02:14Z"
- Qualquer formato aceito pelo construtor DateTime do PHP
Notação de Ponto para Campos Aninhados
'field'→ busca$response['field']'data.auth.expires'→ busca$response['data']['auth']['expires']'user.preferences.cache.ttl'→ busca$response['user']['preferences']['cache']['ttl']
Exemplos Completos
Veja examples/cache-expires-examples.php para mais exemplos de uso.
Rate Limiting
O pacote gerencia automaticamente erros 429 (Too Many Requests):
Funcionamento Automático
- Antes da requisição: Verifica se o domínio está bloqueado
- Durante a requisição: Executa normalmente se não houver bloqueio
- Após 429: Bloqueia o domínio automaticamente
- Retry-After: Respeita o header
Retry-Afterdo servidor
Tratamento de Exceções
use ThreeRN\HttpService\Exceptions\RateLimitException;
try {
$response = HttpService::get('https://api.example.com/data');
} catch (RateLimitException $e) {
echo "Domínio bloqueado: " . $e->getDomain();
echo "Aguarde: " . $e->getRemainingMinutes() . " minutos";
}
Estratégia Wait-on-Rate-Limit
Ao invés de lançar RateLimitException, o serviço pode aguardar de forma síncrona até o bloqueio expirar e então executar a requisição normalmente.
Ativar globalmente via config ou .env:
HTTP_SERVICE_RATE_LIMIT_WAIT_ON_BLOCK=true
Ativar por chamada com waitOnRateLimit():
// Aguarda o bloqueio expirar e então executa
$response = HttpService::waitOnRateLimit()->get('https://api.example.com/data');
Desativar pontualmente (quando estiver ligado globalmente):
// Lança RateLimitException normalmente, ignorando a config global
$response = HttpService::throwOnRateLimit()->get('https://api.example.com/data');
Atenção: O processo ficará bloqueado (via
sleep) pelo tempo exato restante do bloqueio. Não use em requests web síncronos com bloqueios longos. Ideal para jobs/queues ou cenários onde odefault_block_timeé pequeno.
Gerenciamento Manual
use ThreeRN\HttpService\Models\RateLimitControl;
// Verificar se está bloqueado
$isBlocked = RateLimitControl::isBlocked('api.example.com');
// Tempo restante de bloqueio (em minutos)
$minutes = RateLimitControl::getRemainingBlockTime('api.example.com');
// Tempo restante de bloqueio (em segundos)
$seconds = RateLimitControl::getRemainingBlockSeconds('api.example.com');
// Bloquear manualmente por 30 minutos
RateLimitControl::blockDomain('api.example.com', 30);
// Bloquear com motivo registrado
RateLimitControl::blockDomain('api.example.com', 30);
// (defina 'reason' via create/update direto no model se necessário)
// Desbloquear manualmente
RateLimitControl::unblockDomain('api.example.com');
// Listar bloqueios ativos
$blocks = RateLimitControl::active()->get();
// Limpar bloqueios expirados
$count = RateLimitControl::cleanExpiredBlocks();
Circuit Breaker
O circuit breaker é um padrão de resiliência que abre o circuito após N falhas consecutivas de um domínio, bloqueando requisições imediatamente (sem nem tentar a conexão) durante um período de recuperação. Diferente do rate limiting (que reage a 429), o circuit breaker é genérico e protege contra qualquer tipo de falha (5xx, timeouts, erros de conexão).
Estados
| Estado | Comportamento |
|---|---|
| CLOSED | Operação normal. Falhas são contadas. |
| OPEN | Circuito aberto. Lança CircuitBreakerException imediatamente. |
| HALF-OPEN | Após o recovery_time, permite uma requisição de sondagem. Se sucesso → CLOSED. Se falha → OPEN. |
Habilitando Globalmente
Via config ou .env:
HTTP_SERVICE_CIRCUIT_BREAKER_ENABLED=true
HTTP_SERVICE_CIRCUIT_BREAKER_THRESHOLD=5
HTTP_SERVICE_CIRCUIT_BREAKER_RECOVERY_TIME=60
Habilitando por Chamada
// Parâmetros: threshold de falhas, tempo de recuperação em segundos, statuses de falha
$response = HttpService::withCircuitBreaker(5, 60)
->get('https://api.example.com/data');
// Customizando statuses que contam como falha (padrão: 500-599)
$response = HttpService::withCircuitBreaker(3, 30, [500, 502, 503, 504])
->post('https://api.example.com/data', $payload);
// Desabilitando pontualmente (quando habilitado globalmente)
$response = HttpService::withoutCircuitBreaker()
->get('https://api.example.com/health');
Tratamento de Exceções
use ThreeRN\HttpService\Exceptions\CircuitBreakerException;
try {
$response = HttpService::withCircuitBreaker()->get('https://api.example.com/data');
} catch (CircuitBreakerException $e) {
echo "Circuito aberto para: " . $e->getDomain();
echo "Tente novamente em: " . $e->getRemainingSeconds() . " segundos";
}
Wait on Circuit Breaker
Por padrão, quando o circuito está OPEN o serviço lança CircuitBreakerException imediatamente. Com waitOnCircuitBreaker() o comportamento muda: o serviço aguarda (sleep) até o tempo de recuperação expirar e então tenta a requisição novamente em estado HALF-OPEN — idêntico ao waitOnRateLimit() para o rate limiting.
Atenção: não use em processos web síncronos com
recovery_timelongo. Ideal para jobs/queues ou quando ocircuit_breaker_recovery_timefor baixo.
// Ativa por chamada — aguarda o circuito recuperar e re-tenta
$response = HttpService::withCircuitBreaker(3, 10)
->waitOnCircuitBreaker()
->get('https://api.example.com/data');
// Desativa pontualmente (quando a config global estiver habilitada)
$response = HttpService::throwOnCircuitBreaker()
->get('https://api.example.com/data');
Ativando globalmente via config ou .env:
HTTP_SERVICE_CIRCUIT_BREAKER_WAIT_ON_OPEN=true
O método throwOnCircuitBreaker() permite sobrescrever o comportamento global em chamadas específicas.
Diferença entre Circuit Breaker e Rate Limiting
| Rate Limiting | Circuit Breaker | |
|---|---|---|
| Aberto por | Resposta 429 (Too Many Requests) | N falhas consecutivas (5xx, timeout, etc.) |
| Protege contra | Exceder cota da API | Serviço degradado/fora do ar |
| Tempo de bloqueio | Respeitando Retry-After do servidor | Configurável (recovery_time) |
| Persistência | Banco de dados | Cache do Laravel |
| Sondagem automática | Não | Sim (HALF-OPEN) |
Namespace Compartilhado entre Projetos
Por padrão, o estado do circuit breaker é isolado por aplicação — cada projeto mantém sua própria contagem de falhas. Isso é o comportamento correto na maioria dos casos.
Se dois ou mais projetos usam o mesmo driver de cache (ex: mesmo Redis) e precisam compartilhar o estado — para que o App B saiba que o App A já detectou que um domínio está fora do ar — configure o mesmo namespace nos dois:
# App A e App B — mesmo Redis, mesmo namespace
HTTP_SERVICE_CB_NAMESPACE=produtivo
Com isso, as chaves de cache ficam no formato http_cb_produtivo_<md5(domain)> e são compartilhadas entre os projetos.
Se HTTP_SERVICE_CB_NAMESPACE não for definido (padrão), cada app tem seu estado independente com chaves http_cb_<md5(domain)>.
Atenção: ao compartilhar namespace, um único projeto sofrendo falhas de rede ou erros locais pode abrir o circuito para todos os outros. Use com cuidado em ambientes heterogêneos.
Consultar Logs
Models e Query Scopes
use ThreeRN\HttpService\Models\HttpRequestLog;
// Buscar por URL
$logs = HttpRequestLog::byUrl('api.example.com')->get();
// Buscar por método HTTP
$posts = HttpRequestLog::byMethod('POST')->get();
// Buscar por status code
$errors = HttpRequestLog::byStatusCode(500)->get();
// Requisições com erro
$withErrors = HttpRequestLog::withErrors()->get();
// Requisições bem-sucedidas (2xx)
$successful = HttpRequestLog::successful()->get();
// Últimas 24 horas
$recent = HttpRequestLog::where('created_at', '>=', now()->subDay())
->orderBy('created_at', 'desc')
->get();
// Requisições lentas (mais de 5 segundos)
$slow = HttpRequestLog::where('response_time', '>', 5)->get();
Estrutura do Log
Cada log contém:
url- URL completa da requisiçãomethod- Método HTTP (GET, POST, etc)payload- Dados enviados (JSON)response- Resposta recebida (JSON)status_code- Código de status HTTPresponse_time- Tempo de resposta em segundoserror_message- Mensagem de erro (se houver)created_at/updated_at- Timestamps
Estrutura do Controle de Rate Limit
Cada registro de bloqueio contém:
domain- Domínio bloqueadoblocked_at- Momento do bloqueiowait_time_minutes- Duração do bloqueio em minutosunblock_at- Momento em que o bloqueio expirareason- Motivo do bloqueio (opcional, preenchível viacreate/update)created_at/updated_at- Timestamps
Comandos Artisan
Instalação
# Publicar config e migrations
php artisan http-service:install
Gerenciamento de Bloqueios
# Listar domínios bloqueados
php artisan http-service:list-blocks
# Desbloquear domínio específico
php artisan http-service:unblock api.example.com
# Limpar bloqueios expirados
php artisan http-service:clean-blocks
Limpeza de Logs
# Limpar logs antigos (usa log_retention_days do config)
php artisan http-service:clean-logs
# Limpar logs com período customizado
php artisan http-service:clean-logs --days=7
Configuração
Arquivo config/http-service.php:
return [
// Habilitar logging de requisições
'logging_enabled' => env('HTTP_SERVICE_LOGGING_ENABLED', true),
// Habilitar controle de rate limiting
'rate_limit_enabled' => env('HTTP_SERVICE_RATE_LIMIT_ENABLED', true),
// Tempo de bloqueio padrão após 429 (minutos)
'default_block_time' => env('HTTP_SERVICE_DEFAULT_BLOCK_TIME', 15),
// Aguardar o bloqueio expirar em vez de lançar exceção (wait-on-rate-limit)
'rate_limit_wait_on_block' => env('HTTP_SERVICE_RATE_LIMIT_WAIT_ON_BLOCK', false),
// Timeout padrão de requisições (segundos)
'timeout' => env('HTTP_SERVICE_TIMEOUT', 30),
// Limpeza automática de bloqueios expirados
'auto_clean_expired_blocks' => env('HTTP_SERVICE_AUTO_CLEAN_EXPIRED', true),
// Retenção de logs (dias) - null para manter indefinidamente
'log_retention_days' => env('HTTP_SERVICE_LOG_RETENTION_DAYS', 30),
// Conexão de banco para gravação de logs (null = conexão padrão)
'logging_connection' => env('HTTP_SERVICE_LOGGING_CONNECTION', null),
// Conexão de banco para rate limiting (null = usa logging_connection ou padrão)
'ratelimit_connection' => env('HTTP_SERVICE_RATELIMIT_CONNECTION', null),
// Nome customizado da tabela de logs (null = 'http_request_logs')
'logging_table' => env('HTTP_SERVICE_LOGGING_TABLE', null),
// Nome customizado da tabela de rate limit (null = 'rate_limit_controls')
'ratelimit_table' => env('HTTP_SERVICE_RATELIMIT_TABLE', null),
// Circuit Breaker
'circuit_breaker_enabled' => env('HTTP_SERVICE_CIRCUIT_BREAKER_ENABLED', false),
'circuit_breaker_threshold' => env('HTTP_SERVICE_CIRCUIT_BREAKER_THRESHOLD', 5),
'circuit_breaker_recovery_time' => env('HTTP_SERVICE_CIRCUIT_BREAKER_RECOVERY_TIME', 60),
'circuit_breaker_failure_statuses'=> range(500, 599), // não configurável via env
'circuit_breaker_namespace' => env('HTTP_SERVICE_CB_NAMESPACE', null),
// Aguardar recuperação do circuito em vez de lançar exceção (wait-on-circuit-breaker)
'circuit_breaker_wait_on_open' => env('HTTP_SERVICE_CIRCUIT_BREAKER_WAIT_ON_OPEN', false),
];
Variáveis de Ambiente (.env)
HTTP_SERVICE_LOGGING_ENABLED=true
HTTP_SERVICE_RATE_LIMIT_ENABLED=true
HTTP_SERVICE_DEFAULT_BLOCK_TIME=15
HTTP_SERVICE_RATE_LIMIT_WAIT_ON_BLOCK=false
HTTP_SERVICE_TIMEOUT=30
HTTP_SERVICE_AUTO_CLEAN_EXPIRED=true
HTTP_SERVICE_LOG_RETENTION_DAYS=30
HTTP_SERVICE_LOGGING_CONNECTION=
HTTP_SERVICE_RATELIMIT_CONNECTION=
HTTP_SERVICE_LOGGING_TABLE=
HTTP_SERVICE_RATELIMIT_TABLE=
HTTP_SERVICE_CIRCUIT_BREAKER_ENABLED=false
HTTP_SERVICE_CIRCUIT_BREAKER_THRESHOLD=5
HTTP_SERVICE_CIRCUIT_BREAKER_RECOVERY_TIME=60
HTTP_SERVICE_CIRCUIT_BREAKER_WAIT_ON_OPEN=false
HTTP_SERVICE_CB_NAMESPACE=
Tabelas Customizáveis
Por padrão o pacote usa http_request_logs e rate_limit_controls. Para usar nomes diferentes sem alterar as migrations:
HTTP_SERVICE_LOGGING_TABLE=minha_tabela_de_logs
HTTP_SERVICE_RATELIMIT_TABLE=meu_controle_de_ratelimit
Os models HttpRequestLog e RateLimitControl aplicam o nome configurado via setTable() no construtor.
Conexões de Banco Separadas
Para isolar logs e rate limiting da conexão principal (útil para garantir persistência mesmo com rollback de transação):
HTTP_SERVICE_LOGGING_CONNECTION=logging
HTTP_SERVICE_RATELIMIT_CONNECTION=logging
Se HTTP_SERVICE_RATELIMIT_CONNECTION não estiver definido, o pacote usa HTTP_SERVICE_LOGGING_CONNECTION como fallback. Se nenhum estiver definido, usa a conexão padrão do Laravel.
Exemplos de Uso
Em Controllers
namespace App\Http\Controllers;
use ThreeRN\HttpService\Facades\HttpService;
use ThreeRN\HttpService\Exceptions\RateLimitException;
class ApiController extends Controller
{
public function fetchData()
{
try {
$response = HttpService::get('https://api.example.com/data');
return response()->json([
'success' => true,
'data' => $response->json(),
]);
} catch (RateLimitException $e) {
return response()->json([
'success' => false,
'message' => 'Rate limited',
'retry_in_minutes' => $e->getRemainingMinutes(),
], 429);
}
}
}
Em Jobs/Queue
namespace App\Jobs;
use Illuminate\Contracts\Queue\ShouldQueue;
use ThreeRN\HttpService\Facades\HttpService;
use ThreeRN\HttpService\Exceptions\RateLimitException;
class ProcessApiDataJob implements ShouldQueue
{
public function handle()
{
try {
$response = HttpService::post('https://api.example.com/process', $this->data);
// Processar resposta...
} catch (RateLimitException $e) {
// Reagendar job para quando o bloqueio expirar
$this->release($e->getRemainingMinutes() * 60);
}
}
}
Com Retry Logic
function makeApiRequestWithRetry($url, $data, $maxRetries = 3)
{
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$response = HttpService::timeout(30)->post($url, $data);
if ($response->successful()) {
return $response->json();
}
} catch (RateLimitException $e) {
$waitMinutes = $e->getRemainingMinutes();
sleep($waitMinutes * 60);
} catch (\Exception $e) {
sleep(5); // Aguarda antes de tentar novamente
}
$attempt++;
}
throw new \Exception("Falha após {$maxRetries} tentativas");
}
Manutenção e Performance
Limpeza Automática
Configure tarefas agendadas no app/Console/Kernel.php:
protected function schedule(Schedule $schedule)
{
// Limpar bloqueios expirados diariamente
$schedule->command('http-service:clean-blocks')->daily();
// Limpar logs antigos semanalmente
$schedule->command('http-service:clean-logs')->weekly();
}
Índices de Banco de Dados
As migrations já incluem índices otimizados para:
- Consultas por URL
- Consultas por método HTTP
- Consultas por status code
- Consultas por data
- Verificação de bloqueios de domínio
Requisitos
- PHP 8.2 ou superior
- Laravel 12.x
- Banco de dados (MySQL, PostgreSQL, SQLite, etc)
Estrutura do Pacote
http-service/
├── config/
│ └── http-service.php
├── database/
│ └── migrations/
├── src/
│ ├── Console/
│ │ └── Commands/
│ ├── Exceptions/
│ ├── Facades/
│ ├── Models/
│ ├── Services/
│ └── HttpServiceProvider.php
├── examples/
└── README.md
Documentação Adicional
Contribuindo
Contribuições são bem-vindas! Por favor, abra uma issue ou pull request.
Licença
MIT License - veja LICENSE para detalhes.
Suporte
Para dúvidas ou problemas:
- Abra uma issue no GitHub
- Email: allysonmt@gmail.com
Créditos
Desenvolvido por Allyson P. da Mata