Se você está construindo um scraper, integrando APIs de terceiros com rate limits agressivos, ou precisa acessar recursos geograficamente restritos, dominar proxies HTTP em PHP é uma habilidade essencial. Este guia mostra como configurar proxies em cada camada da stack PHP — desde cURL bruto até uma solução pronta para produção em Laravel.
Por que usar proxies em aplicações PHP?
Proxies HTTP resolvem três problemas principais: rate limiting (muitos sites bloqueiam após X requisições do mesmo IP), geo-blocking (conteúdo restrito por região), e anonimato (proteger sua infraestrutura de ser identificada). Para scraping e automação, proxies residenciais são particularmente valiosos porque parecem tráfego de usuários reais.
O ecossistema PHP oferece várias abordagens para trabalhar com proxies. Vamos explorar cada uma, começando pelo nível mais baixo.
1. cURL nativo: configurando CURLOPT_PROXY
O cURL é a fundação de praticamente todo cliente HTTP em PHP. Entender suas opções de proxy é fundamental. As duas constantes principais são CURLOPT_PROXY (endereço do servidor) e CURLOPT_PROXYUSERPWD (credenciais no formato usuario:senha).
<?php
/**
* Exemplo básico de requisição com proxy usando cURL nativo
*/
function fetchWithProxy(string $url, string $proxyUrl, ?string $credentials = null): string
{
$ch = curl_init();
// URL de destino
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
// Configuração do proxy
// Formato: gate.proxyhat.com:8080
curl_setopt($ch, CURLOPT_PROXY, $proxyUrl);
// Credenciais no formato usuario:senha
if ($credentials) {
curl_setopt($ch, CURLOPT_PROXYUSERPWD, $credentials);
}
// Timeout para evitar travamentos
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
// TLS/SSL - verificar certificado
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// Executar requisição
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);
if ($error) {
throw new RuntimeException("Erro cURL: {$error}");
}
if ($httpCode >= 400) {
throw new RuntimeException("HTTP {$httpCode} recebido de {$url}");
}
return $response;
}
// Uso com ProxyHat
$proxyHost = 'gate.proxyhat.com:8080';
$credentials = 'usuario-proxy:senha-proxy';
try {
$html = fetchWithProxy('https://httpbin.org/ip', $proxyHost, $credentials);
echo $html;
} catch (RuntimeException $e) {
echo "Falha: {$e->getMessage()}\n";
}
Para proxies SOCKS5, adicione curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPRO_SOCKS5); e use a porta 1080.
Rotação de IP por requisição
Com proxies residenciais, cada requisição pode sair de um IP diferente. O ProxyHat permite controlar isso via flags no username:
<?php
/**
* Gera credenciais com rotação automática ou sessão fixa
*/
function buildProxyCredentials(
string $username,
string $password,
?string $country = null,
?string $session = null
): string {
$user = $username;
// Adiciona país para geo-targeting
if ($country) {
$user .= "-country-{$country}";
}
// Sessão fixa mantém o mesmo IP entre requisições
if ($session) {
$user .= "-session-{$session}";
}
return "{$user}:{$password}";
}
// Exemplo: IP diferente para cada requisição (rotação automática)
$rotating = buildProxyCredentials('meu_user', 'minha_senha', 'US', null);
// Exemplo: Mesmo IP para múltiplas requisições (sticky session)
$sticky = buildProxyCredentials('meu_user', 'minha_senha', 'US', 'sessao123');
2. Guzzle: proxy via request options
O Guzzle é o cliente HTTP mais popular em PHP. Ele abstrai o cURL mas expõe todas as opções relevantes. A configuração de proxy é passada no array de opções.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
class ProxyClient
{
private Client $client;
private array $proxyConfig;
public function __construct(string $proxyHost, string $username, string $password)
{
$this->proxyConfig = [
'proxy' => [
'http' => "http://{$username}:{$password}@{$proxyHost}",
'https' => "http://{$username}:{$password}@{$proxyHost}",
],
'timeout' => 30,
'connect_timeout' => 10,
'verify' => true, // Verificar certificados SSL
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
],
];
$this->client = new Client($this->proxyConfig);
}
/**
* Faz requisição com retry automático em caso de falha
*/
public function fetch(string $url, int $maxRetries = 3): string
{
$lastException = null;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
$response = $this->client->get($url);
return (string) $response->getBody();
} catch (RequestException $e) {
$lastException = $e;
// Backoff exponencial
$delay = pow(2, $attempt) * 1000;
usleep($delay * 1000);
// Log do erro
error_log("Tentativa {$attempt} falhou: {$e->getMessage()}");
}
}
throw $lastException;
}
/**
* Requisição com sessão fixa (mesmo IP)
*/
public function fetchWithSession(string $url, string $sessionId, ?string $country = null): string
{
$username = 'meu_user';
$password = 'minha_senha';
if ($country) {
$username .= "-country-{$country}";
}
$username .= "-session-{$sessionId}";
$proxyUrl = "http://{$username}:{$password}@gate.proxyhat.com:8080";
// Override da configuração de proxy para esta requisição específica
$response = $this->client->get($url, [
'proxy' => [
'http' => $proxyUrl,
'https' => $proxyUrl,
],
]);
return (string) $response->getBody();
}
}
// Uso
$client = new ProxyClient('gate.proxyhat.com:8080', 'meu_user', 'minha_senha');
try {
$content = $client->fetch('https://httpbin.org/ip');
echo $content;
// Com sessão fixa
$content = $client->fetchWithSession('https://httpbin.org/ip', 'sessao-abc123', 'US');
echo $content;
} catch (Exception $e) {
echo "Erro: {$e->getMessage()}\n";
}
Middleware de rotação automática
Para rotação automática de proxy por requisição, você pode criar um middleware Guzzle:
<?php
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
class RotatingProxyMiddleware
{
private array $proxyPool;
private int $currentIndex = 0;
public function __construct(array $proxies)
{
$this->proxyPool = $proxies;
}
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
// Seleciona próximo proxy do pool (round-robin)
$proxy = $this->proxyPool[$this->currentIndex];
$this->currentIndex = ($this->currentIndex + 1) % count($this->proxyPool);
// Adiciona proxy às opções
$options['proxy'] = $proxy;
return $handler($request, $options);
};
}
}
// Configuração
$proxyPool = [
'http://user1:pass1@gate.proxyhat.com:8080',
'http://user2:pass2@gate.proxyhat.com:8080',
'http://user3:pass3@gate.proxyhat.com:8080',
];
$stack = HandlerStack::create();
$stack->push(new RotatingProxyMiddleware($proxyPool));
$client = new GuzzleHttp\Client(['handler' => $stack]);
3. Symfony HTTP Client: async e proxy
O Symfony HTTP Client oferece suporte nativo a requisições assíncronas, ideal para scraping em larga escala. A configuração de proxy é simples e performática.
<?php
require 'vendor/autoload.php';
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpClient\Exception\TransportException;
class AsyncProxyClient
{
private $client;
private string $proxyUrl;
public function __construct(string $username, string $password, string $proxyHost = 'gate.proxyhat.com:8080')
{
$this->proxyUrl = "http://{$username}:{$password}@{$proxyHost}";
$this->client = HttpClient::create([
'proxy' => $this->proxyUrl,
'timeout' => 30,
'max_redirects' => 5,
'verify_peer' => true,
'verify_host' => true,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (compatible; ProxyClient/1.0)',
],
]);
}
/**
* Requisição síncrona simples
*/
public function fetch(string $url): string
{
$response = $this->client->request('GET', $url);
// Lança exceção em caso de erro HTTP
if ($response->getStatusCode() >= 400) {
throw new RuntimeException("HTTP {$response->getStatusCode()} de {$url}");
}
return $response->getContent();
}
/**
* Múltiplas requisições concorrentes
*/
public function fetchConcurrent(array $urls, int $concurrency = 10): array
{
$responses = [];
$results = []
;
// Iniciar todas as requisições
foreach ($urls as $key => $url) {
$responses[$key] = $this->client->request('GET', $url);
}
// Processar respostas conforme chegam
foreach ($responses as $key => $response) {
try {
$results[$key] = [
'status' => $response->getStatusCode(),
'content' => $response->getContent(),
'headers' => $response->getHeaders(),
];
} catch (TransportException $e) {
$results[$key] = [
'error' => $e->getMessage(),
'status' => 0,
];
}
}
return $results;
}
/**
* Streaming de resposta grande
*/
public function streamResponse(string $url, callable $onChunk): void
{
$response = $this->client->request('GET', $url);
foreach ($this->client->stream($response) as $chunk) {
if ($chunk->isFirst()) {
// Headers recebidos
echo "Status: {$response->getStatusCode()}\n";
}
if ($chunk->isLast()) {
// Conexão fechada
echo "Concluído\n";
return;
}
// Processar chunk
$onChunk($chunk->getContent());
}
}
}
// Uso
$client = new AsyncProxyClient('meu_user', 'minha_senha');
// Requisição única
$content = $client->fetch('https://httpbin.org/ip');
// Requisições concorrentes
$urls = [
'page1' => 'https://httpbin.org/ip',
'page2' => 'https://httpbin.org/headers',
'page3' => 'https://httpbin.org/user-agent',
];
$results = $client->fetchConcurrent($urls);
print_r($results);
4. Integração Laravel: service class com pool de proxies
Em aplicações Laravel, o ideal é encapsular a lógica de proxy em uma service class injetável via container. Isso permite uso em controllers, jobs e queues de forma limpa e testável.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Cache;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use RuntimeException;
class ResidentialProxyService
{
private Client $client;
private array $config;
private ?string $currentSession = null;
// Pool de países disponíveis
private const SUPPORTED_COUNTRIES = ['US', 'BR', 'DE', 'GB', 'FR', 'ES', 'IT', 'JP'];
public function __construct()
{
$this->config = [
'host' => config('services.proxyhat.host', 'gate.proxyhat.com'),
'port' => config('services.proxyhat.port', 8080),
'username' => config('services.proxyhat.username'),
'password' => config('services.proxyhat.password'),
];
$this->client = new Client([
'timeout' => 30,
'connect_timeout' => 10,
'verify' => true,
]);
}
/**
* Faz requisição com IP rotativo (cada requisição = IP diferente)
*/
public function fetchRotating(string $url, ?string $country = null): string
{
$proxyUrl = $this->buildProxyUrl(null, $country);
return $this->executeWithRetry($url, $proxyUrl);
}
/**
* Faz requisição com sessão fixa (mesmo IP mantido)
*/
public function fetchSticky(string $url, string $sessionId, ?string $country = null): string
{
$proxyUrl = $this->buildProxyUrl($sessionId, $country);
return $this->executeWithRetry($url, $proxyUrl);
}
/**
* Inicia nova sessão e retorna ID
*/
public function createSession(?string $country = null): string
{
$sessionId = 'sess_' . bin2hex(random_bytes(8));
$this->currentSession = $sessionId;
// Cache da sessão para reuso
Cache::put("proxy_session:{$sessionId}", [
'country' => $country,
'created_at' => now(),
], now()->addMinutes(30));
return $sessionId;
}
/**
* Obtém sessão atual ou cria nova
*/
public function getOrCreateSession(?string $country = null): string
{
if ($this->currentSession && Cache::has("proxy_session:{$this->currentSession}")) {
return $this->currentSession;
}
return $this->createSession($country);
}
/**
* Constrói URL do proxy com parâmetros opcionais
*/
private function buildProxyUrl(?string $sessionId = null, ?string $country = null): string
{
$username = $this->config['username'];
// Geo-targeting
if ($country && in_array($country, self::SUPPORTED_COUNTRIES)) {
$username .= "-country-{$country}";
}
// Sessão sticky
if ($sessionId) {
$username .= "-session-{$sessionId}";
}
return sprintf(
'http://%s:%s@%s:%d',
$username,
$this->config['password'],
$this->config['host'],
$this->config['port']
);
}
/**
* Executa requisição com retry e logging
*/
private function executeWithRetry(string $url, string $proxyUrl, int $maxRetries = 3): string
{
$lastException = null;
for ($attempt = 1; $attempt <= $maxRetries; $attempt++) {
try {
$startTime = microtime(true);
$response = $this->client->get($url, [
'proxy' => ['http' => $proxyUrl, 'https' => $proxyUrl],
]);
$duration = round((microtime(true) - $startTime) * 1000);
// Log de sucesso
Log::channel('proxy')->info('Requisição bem-sucedida', [
'url' => $url,
'attempt' => $attempt,
'duration_ms' => $duration,
'status' => $response->getStatusCode(),
]);
return (string) $response->getBody();
} catch (RequestException $e) {
$lastException = $e;
Log::channel('proxy')->warning('Tentativa falhou', [
'url' => $url,
'attempt' => $attempt,
'error' => $e->getMessage(),
'status' => $e->getResponse()?->getStatusCode(),
]);
// Backoff exponencial com jitter
$delay = pow(2, $attempt) * 1000 + rand(0, 500);
usleep($delay * 1000);
}
}
Log::channel('proxy')->error('Todas as tentativas falharam', [
'url' => $url,
'attempts' => $maxRetries,
'last_error' => $lastException?->getMessage(),
]);
throw new RuntimeException(
"Falha após {$maxRetries} tentativas: {$lastException?->getMessage()}"
);
}
/**
* Verifica saúde do proxy
*/
public function healthCheck(): array
{
try {
$response = $this->client->get('https://httpbin.org/ip', [
'proxy' => ['http' => $this->buildProxyUrl(), 'https' => $this->buildProxyUrl()],
'timeout' => 10,
]);
return [
'status' => 'healthy',
'ip' => json_decode($response->getBody(), true)['origin'] ?? null,
'latency_ms' => $response->getHeader('X-Response-Time')[0] ?? null,
];
} catch (Exception $e) {
return [
'status' => 'unhealthy',
'error' => $e->getMessage(),
];
}
}
}
Configuração e registro no container
Registre o serviço no AppServiceProvider e adicione as configurações:
<?php
// config/services.php
return [
'proxyhat' => [
'host' => env('PROXYHAT_HOST', 'gate.proxyhat.com'),
'port' => env('PROXYHAT_PORT', 8080),
'username' => env('PROXYHAT_USERNAME'),
'password' => env('PROXYHAT_PASSWORD'),
],
];
// app/Providers/AppServiceProvider.php
public function register()
{
$this->app->singleton(ResidentialProxyService::class, function ($app) {
return new ResidentialProxyService();
});
}
// Uso em um Job
namespace App\Jobs;
use App\Services\ResidentialProxyService;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
class ScrapeProductJob implements ShouldQueue
{
use Queueable;
public function __construct(private string $productUrl, private ?string $country = null) {}
public function handle(ResidentialProxyService $proxy): void
{
$sessionId = $proxy->createSession($this->country);
$html = $proxy->fetchSticky($this->productUrl, $sessionId);
// Processar HTML...
// $product = $this->parseProduct($html);
// Product::create($product);
}
}
5. Multi-cURL para scraping concorrente
Para máxima performance em scraping de múltiplas URLs simultaneamente, o curl_multi_* permite executar dezenas de requisições em paralelo com um único processo PHP.
<?php
class ConcurrentProxyScraper
{
private string $proxyHost;
private string $proxyAuth;
private int $maxConcurrent;
public function __construct(
string $username,
string $password,
int $maxConcurrent = 20,
string $host = 'gate.proxyhat.com:8080'
) {
$this->proxyHost = $host;
$this->proxyAuth = "{$username}:{$password}";
$this->maxConcurrent = $maxConcurrent;
}
/**
* Executa múltiplas requisições em paralelo
*/
public function fetchBatch(array $urls): array
{
$results = [];
$handles = [];
// Inicializar multi-handle
$mh = curl_multi_init();
// Configurar timeout para multi-handle
curl_multi_setopt($mh, CURLMOPT_MAX_TOTAL_CONNECTIONS, $this->maxConcurrent);
// Criar handles individuais
foreach ($urls as $key => $url) {
$ch = $this->createHandle($url, $key);
$handles[$key] = $ch;
curl_multi_add_handle($mh, $ch);
}
// Executar todas as requisições
$active = null;
do {
$status = curl_multi_exec($mh, $active);
if ($status === CURLM_OK) {
// Aguardar atividade
curl_multi_select($mh, 1.0);
}
} while ($status === CURLM_OK && $active);
// Coletar resultados
foreach ($handles as $key => $ch) {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
$content = curl_multi_getcontent($ch);
$results[$key] = [
'status' => $httpCode,
'content' => $content,
'error' => $error ?: null,
'total_time' => curl_getinfo($ch, CURLINFO_TOTAL_TIME),
'size_download' => curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD),
];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
return $results;
}
/**
* Cria handle cURL configurado
*/
private function createHandle(string $url, string|int $key): CurlHandle
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 5,
CURLOPT_TIMEOUT => 30,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_PROXY => $this->proxyHost,
CURLOPT_PROXYUSERPWD => $this->proxyAuth,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
// Identificador para debug
CURLOPT_PRIVATE => $key,
]);
return $ch;
}
/**
* Processa URLs em chunks para controle de memória
*/
public function fetchBatchChunked(array $urls, callable $onChunk, int $chunkSize = 50): void
{
$chunks = array_chunk($urls, $chunkSize, true);
foreach ($chunks as $chunk) {
$results = $this->fetchBatch($chunk);
$onChunk($results);
// Limpar memória entre chunks
gc_collect_cycles();
}
}
}
// Uso
$scraper = new ConcurrentProxyScraper('meu_user', 'minha_senha', 20);
$urls = [
'product_1' => 'https://exemplo.com/produto/1',
'product_2' => 'https://exemplo.com/produto/2',
'product_3' => 'https://exemplo.com/produto/3',
// ... até centenas de URLs
];
// Processar em lotes
$scraper->fetchBatchChunked($urls, function (array $results) {
foreach ($results as $key => $data) {
if ($data['error']) {
echo "Erro em {$key}: {$data['error']}\n";
continue;
}
// Salvar no banco ou processar
echo "{$key}: {$data['status']} - {$data['size_download']} bytes\n";
}
}, 50);
6. TLS/SSL e CA bundle handling
Trabalhar com proxies requer atenção especial à verificação de certificados. Alguns proxies podem interceptar HTTPS (man-in-the-middle), o que exige configuração específica do CA bundle.
<?php
class TlsAwareProxyClient
{
private string $proxyHost;
private string $proxyAuth;
private ?string $caBundlePath;
public function __construct(
string $username,
string $password,
string $host = 'gate.proxyhat.com:8080'
) {
$this->proxyHost = $host;
$this->proxyAuth = "{$username}:{$password}";
$this->caBundlePath = $this->findCaBundle();
}
/**
* Localiza CA bundle do sistema
*/
private function findCaBundle(): ?string
{
// Caminhos comuns do CA bundle
$candidates = [
// Linux (Debian/Ubuntu)
'/etc/ssl/certs/ca-certificates.crt',
// Linux (RHEL/CentOS)
'/etc/pki/tls/certs/ca-bundle.crt',
// Linux (Arch)
'/etc/ssl/certs/ca-bundle.crt',
// macOS (Homebrew)
'/usr/local/etc/openssl/cert.pem',
// Windows (curl)
'C:\\Windows\\System32\\curl-ca-bundle.crt',
// Composer CA bundle (fallback)
dirname(__DIR__) . '/vendor/cacert.pem',
];
foreach ($candidates as $path) {
if (file_exists($path) && is_readable($path)) {
return $path;
}
}
// Fallback: usar CA bundle do Guzzle
$guzzleBundle = dirname(__DIR__) . '/vendor/guzzlehttp/guzzle/src/Handler/cacert.pem';
if (file_exists($guzzleBundle)) {
return $guzzleBundle;
}
return null;
}
/**
* Requisição com verificação TLS estrita
*/
public function fetchStrict(string $url): string
{
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => $this->proxyHost,
CURLOPT_PROXYUSERPWD => $this->proxyAuth,
CURLOPT_TIMEOUT => 30,
// TLS estrito
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_SSLVERSION => CURL_SSLVERSION_TLSv1_2,
// CA bundle específico
CURLOPT_CAINFO => $this->caBundlePath,
// Verificar revogação de certificado
CURLOPT_SSL_OPTIONS => CURLSSLOPT_REVOKE_BEST_EFFORT,
];
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new RuntimeException("Erro TLS: {$error}");
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new RuntimeException("HTTP {$httpCode}");
}
return $response;
}
/**
* Requisição com TLS flexível (para proxies que interceptam)
* ATENÇÃO: Menos seguro, use apenas em ambiente controlado
*/
public function fetchFlexible(string $url): string
{
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_PROXY => $this->proxyHost,
CURLOPT_PROXYUSERPWD => $this->proxyAuth,
CURLOPT_TIMEOUT => 30,
// TLS flexível (não verificar certificado do proxy)
CURLOPT_SSL_VERIFYPEER => false,
CURLOPT_SSL_VERIFYHOST => 0,
];
// Aviso de segurança
trigger_error(
'fetchFlexible desabilita verificação TLS. Use apenas em desenvolvimento.',
E_USER_NOTICE
);
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if (curl_errno($ch)) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);
return $response;
}
/**
* Diagnóstico de conectividade TLS
*/
public function diagnoseTls(string $testUrl = 'https://httpbin.org/get'): array
{
$results = [];
// Teste 1: Sem proxy (baseline)
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $testUrl,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_CAINFO => $this->caBundlePath,
]);
$results['direct'] = [
'success' => curl_exec($ch) !== false,
'error' => curl_error($ch),
];
curl_close($ch);
// Teste 2: Com proxy, TLS estrito
try {
$results['proxy_strict'] = [
'success' => true,
'response' => $this->fetchStrict($testUrl),
];
} catch (Exception $e) {
$results['proxy_strict'] = [
'success' => false,
'error' => $e->getMessage(),
];
}
// Informações do CA bundle
$results['ca_bundle'] = [
'path' => $this->caBundlePath,
'exists' => $this->caBundlePath && file_exists($this->caBundlePath),
];
return $results;
}
}
// Exemplo de uso com diagnóstico
$client = new TlsAwareProxyClient('meu_user', 'minha_senha');
// Diagnóstico antes de usar em produção
$diag = $client->diagnoseTls();
print_r($diag);
// Requisição segura
try {
$content = $client->fetchStrict('https://api.exemplo.com/data');
echo "Requisição bem-sucedida\n";
} catch (RuntimeException $e) {
echo "Erro: {$e->getMessage()}\n";
}
Comparação: cURL vs Guzzle vs Symfony HTTP Client
| Característica | cURL nativo | Guzzle | Symfony HTTP Client |
|---|---|---|---|
| Curva de aprendizado | Alta (API verbosa) | Baixa (abstrações claras) | Média (conceitos async) |
| Performance | Máxima (direto) | Boa (wrapper cURL) | Excelente (async nativo) |
| Concorrência | curl_multi (complexo) | Pool (middleware) | Async nativo (simples) |
| Integração Laravel | Manual | Excelente (package oficial) | Boa (bridge disponível) |
| Testabilidade | Difícil (mock complexo) | Excelente (handlers mock) | Boa (mock client) |
| Dependências | Nenhuma (extensão) | psr/http-message | symfony/http-client |
Boas práticas para produção
- Timeouts adequados: Sempre configure
CURLOPT_TIMEOUTeCURLOPT_CONNECTTIMEOUT. Valores recomendados: 30s para timeout total, 10s para conexão. - Retry com backoff: Implemente retry exponencial com jitter para evitar thundering herd em caso de falhas do proxy.
- Logging estruturado: Registre URL, status, latência e IP de saída para auditoria e debugging.
- Rate limiting próprio: Mesmo com proxies, respeite rate limits do target. Adicione delays entre requisições.
- User-Agent realista: Use User-Agents de browsers reais para evitar detecção básica.
- Rotação de sessões: Para scraping longo, alterne sessões periodicamente para evitar blocks por comportamento.
- Monitoramento: Implemente health checks periódicos do proxy e alertas para degradação.
Nota sobre ética: Sempre verifique o
robots.txte os termos de serviço do site alvo. Use delays entre requisições e considere horários de menor tráfego. Para dados sensíveis, consulte um advogado sobre conformidade com LGPD/GDPR.
Key Takeaways
- cURL nativo oferece controle total e máxima performance, mas exige mais código. Ideal para scripts simples ou quando você precisa de cada detalhe.
- Guzzle é a escolha mais produtiva para aplicações Laravel, com middleware para rotação automática e excelente testabilidade.
- Symfony HTTP Client brilha em cenários de alta concorrência com seu modelo async nativo e streaming.
- Multi-cURL é a ferramenta certa quando você precisa de centenas de requisições paralelas em um único processo.
- TLS/SSL requer atenção especial: sempre use verificação estrita em produção, e tenha um CA bundle atualizado.
- Service classes em Laravel permitem encapsular toda a lógica de proxy, com injeção de dependência e uso transparente em jobs e controllers.
Para mais exemplos de uso e casos avançados, confira nossa documentação em /pt/use-cases/web-scraping e /pt/pricing para detalhes dos planos de proxies residenciais.






