Cliente HTTP em PHP
LidoNesta aula, você aprenderá a fazer requisições HTTP em PHP usando cURL, file_get_contents com contexto e a biblioteca Guzzle, além de técnicas para tratar respostas de forma eficiente e segura.
Fazer requisições HTTP é uma tarefa fundamental no desenvolvimento web moderno, seja para consumir APIs externas, integrar serviços ou automatizar tarefas. Nesta aula, vamos explorar as principais formas de realizar requisições HTTP em PHP, desde as ferramentas nativas até bibliotecas robustas como Guzzle. Você aprenderá a escolher a ferramenta certa para cada situação e a tratar as respostas de maneira adequada.
Vamos começar entendendo o que é um cliente HTTP e por que ele é importante. Em seguida, mergulharemos em cada abordagem, com exemplos práticos e boas práticas.
cURL
O cURL é uma extensão do PHP que permite transferir dados usando diversos protocolos, incluindo HTTP. Ele é extremamente flexível e poderoso, oferecendo controle fino sobre a requisição, como cabeçalhos, métodos HTTP, autenticação e muito mais. É a escolha clássica para desenvolvimento de clientes HTTP personalizados.
Para usar cURL, você precisa garantir que a extensão esteja habilitada no seu ambiente (geralmente já vem habilitada em instalações padrão). O fluxo básico envolve quatro passos: iniciar uma sessão com curl_init(), configurar opções com curl_setopt(), executar a requisição com curl_exec() e fechar a sessão com curl_close().
Vamos ver um exemplo simples de requisição GET:
<?php
// URL da API que queremos consumir
$url = 'https://jsonplaceholder.typicode.com/posts/1';
// Inicializa a sessão cURL
$ch = curl_init($url);
// Define opções
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Retorna o resultado como string
curl_setopt($ch, CURLOPT_HEADER, false); // Não inclui cabeçalhos na resposta
// Executa a requisição e armazena a resposta
$response = curl_exec($ch);
// Verifica se houve erro
if (curl_errno($ch)) {
echo 'Erro: ' . curl_error($ch);
} else {
// Decodifica o JSON (se for o caso)
$data = json_decode($response, true);
print_r($data);
}
// Fecha a sessão
curl_close($ch);
?>
Além de GET, você pode fazer POST, PUT, DELETE etc., alterando a opção CURLOPT_CUSTOMREQUEST ou usando constantes como CURLOPT_POST. Para enviar dados JSON, você pode usar CURLOPT_POSTFIELDS com uma string JSON e definir o cabeçalho Content-Type: application/json.
<?php
$data = ['title' => 'Novo Post', 'body' => 'Conteúdo do post'];
$ch = curl_init('https://jsonplaceholder.typicode.com/posts');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'Content-Length: ' . strlen(json_encode($data))
]);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
print_r($result);
?>
Para tratar erros de rede ou HTTP, você pode obter o código de status com curl_getinfo($ch, CURLINFO_HTTP_CODE). Isso permite tomar decisões baseadas no resultado.
<?php
// ... depois de executar
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($status === 200) {
// Sucesso
} elseif ($status === 404) {
// Recurso não encontrado
} else {
// Outro erro
}
?>
cURL é poderoso, mas seu código pode ficar verboso para requisições simples. Para projetos complexos, bibliotecas como Guzzle abstraem muito desse trabalho.
file_get_contents com contexto
Uma alternativa mais simples ao cURL é usar a função file_get_contents() combinada com um stream de contexto. Essa abordagem é útil para requisições GET rápidas e sem muitos requisitos. Ela é nativa do PHP e não requer extensões adicionais.
A função file_get_contents() lê um arquivo (ou URL) e retorna seu conteúdo como string. Para fazer requisições HTTP com cabeçalhos personalizados, usamos stream_context_create() para definir opções como método, cabeçalhos e corpo.
Exemplo de requisição GET:
<?php
$url = 'https://jsonplaceholder.typicode.com/posts/1';
$context = stream_context_create([
'http' => [
'method' => 'GET',
'header' => "Accept: application/json\r\n"
]
]);
$response = file_get_contents($url, false, $context);
if ($response !== false) {
$data = json_decode($response, true);
print_r($data);
} else {
echo 'Erro ao fazer a requisição';
}
?>
Para requisições POST, você precisa incluir o corpo e o cabeçalho Content-Type e Content-Length:
<?php
$url = 'https://jsonplaceholder.typicode.com/posts';
$data = ['title' => 'Novo Post', 'body' => 'Conteúdo'];
$json = json_encode($data);
$context = stream_context_create([
'http' => [
'method' => 'POST',
'header' => "Content-Type: application/json\r\n" .
"Content-Length: " . strlen($json) . "\r\n",
'content' => $json
]
]);
$response = file_get_contents($url, false, $context);
if ($response !== false) {
echo $response;
} else {
$error = error_get_last();
echo 'Erro: ' . $error['message'];
}
?>
Uma limitação importante do file_get_contents() é que, por padrão, ele não trata erros HTTP de forma elegante; por exemplo, um status 404 pode retornar o corpo da página de erro, mas a função ainda retorna o conteúdo, a menos que você configure ignore_errors no contexto. Para verificar o status, você pode usar $http_response_header (uma variável mágica que recebe os cabeçalhos da resposta).
<?php
$response = file_get_contents($url, false, $context);
if (isset($http_response_header) && strpos($http_response_header[0], '200') !== false) {
// Sucesso
} else {
// Erro
}
?>
Outra limitação é que file_get_contents() não permite configurações avançadas como tempo limite de conexão, certificados SSL personalizados etc. Nesses casos, prefira cURL ou Guzzle.
Guzzle (visão geral)
Guzzle é uma biblioteca PHP que facilita o envio de requisições HTTP e a integração com serviços web. Ela fornece uma API simples e elegante, abstraindo os detalhes de baixo nível, e é amplamente adotada na comunidade. Guzzle é instalada via Composer: composer require guzzlehttp/guzzle.
Com Guzzle, você pode fazer requisições com poucas linhas de código, tratar respostas de forma consistente e usar recursos avançados como middleware, clientes assíncronos e pools de requisições.
Exemplo de requisição GET:
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://jsonplaceholder.typicode.com',
'timeout' => 5.0
]);
$response = $client->request('GET', '/posts/1');
if ($response->getStatusCode() === 200) {
$body = $response->getBody();
$data = json_decode($body, true);
print_r($data);
}
?>
Para POST, você pode passar um array JSON no parâmetro json:
<?php
$response = $client->request('POST', '/posts', [
'json' => [
'title' => 'Novo Post',
'body' => 'Conteúdo'
]
]);
$status = $response->getStatusCode(); // 201 em caso de sucesso
?>
Guzzle também oferece métodos convenientes como get(), post(), put(), etc., e permite configurar cabeçalhos, autenticação, query strings e muito mais por meio de opções.
$response = $client->get('/search', [
'query' => ['q' => 'php'],
'headers' => ['X-API-KEY' => 'seu-token']
]);
Uma das grandes vantagens do Guzzle é o tratamento de erros: ele lança exceções como GuzzleHttp\Exception\ClientException para erros 4xx e ServerException para 5xx, permitindo capturá-los com try-catch.
try {
$response = $client->get('/posts/999999');
} catch (GuzzleHttp\Exception\ClientException $e) {
echo 'Erro 404: ' . $e->getMessage();
$response = $e->getResponse();
// Você ainda pode acessar o corpo da resposta de erro
}
Guzzle é a escolha recomendada para aplicações modernas, especialmente quando se trabalha com APIs complexas e se deseja um código limpo e manutenível.
Tratamento de respostas
Independentemente da ferramenta usada, é crucial tratar as respostas HTTP de forma adequada. Isso envolve verificar o código de status, decodificar o conteúdo (geralmente JSON) e lidar com erros de forma amigável.
Uma boa prática é centralizar o tratamento de respostas em funções ou classes, para que a lógica não se espalhe pelo código. Vamos criar um exemplo de função que faz uma requisição e retorna os dados decodificados ou lança uma exceção em caso de erro.
function getJson($url, $method = 'GET', $data = null) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json'
]);
}
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status < 200 || $status >= 300) {
throw new Exception("Erro HTTP $status: $response");
}
return json_decode($response, true);
}
// Uso
try {
$posts = getJson('https://jsonplaceholder.typicode.com/posts');
print_r($posts);
} catch (Exception $e) {
echo 'Falha na requisição: ' . $e->getMessage();
}
Além de verificar o status, é importante validar se o conteúdo é o esperado. Por exemplo, se a API retorna JSON, você pode verificar se a decodificação foi bem-sucedida usando json_last_error().
$json = json_decode($response, true);
if (json_last_error() !== JSON_ERROR_NONE) {
// Tratar erro de parsing
throw new Exception('JSON inválido: ' . json_last_error_msg());
}
Outra consideração é o tratamento de timeouts e erros de rede. cURL permite definir um tempo limite com CURLOPT_TIMEOUT e CURLOPT_CONNECTTIMEOUT. Em Guzzle, você pode configurar o tempo limite na criação do cliente ou por requisição.
// cURL
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); // 5 segundos para conectar
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // 10 segundos para a requisição completa
// Guzzle
$client = new Client(['timeout' => 10, 'connect_timeout' => 5]);
Boas práticas adicionais incluem: usar HTTPS sempre que possível, validar certificados SSL (não desative essa verificação em produção), limitar o tamanho da resposta (se possível) e fazer logging das requisições e respostas para depuração.
Referências
- Documentação oficial do cURL no PHP
- file_get_contents - Manual do PHP
- Guzzle Documentation
- Contexto HTTP - Manual do PHP
- curl_setopt - Manual do PHP
- Códigos de status HTTP - MDN
Exercícios
Usando cURL, faça uma requisição GET para
https://jsonplaceholder.typicode.com/userse imprima o nome de cada usuário. Trate erros de rede.✓ Resposta:<?php $ch = curl_init('https://jsonplaceholder.typicode.com/users'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if (curl_errno($ch)) { echo 'Erro: ' . curl_error($ch); } else { $users = json_decode($response, true); foreach ($users as $user) { echo $user['name'] . PHP_EOL; } } curl_close($ch); ?>Repita o exercício anterior, mas usando
file_get_contents()com contexto. Verifique se o status HTTP é 200 antes de processar.✓ Resposta:<?php $url = 'https://jsonplaceholder.typicode.com/users'; $context = stream_context_create(['http' => ['method' => 'GET']]); $response = file_get_contents($url, false, $context); if ($response !== false && isset($http_response_header) && strpos($http_response_header[0], '200') !== false) { $users = json_decode($response, true); foreach ($users as $user) { echo $user['name'] . PHP_EOL; } } else { echo 'Falha na requisição'; } ?>Com Guzzle, faça uma requisição POST para
https://jsonplaceholder.typicode.com/postsenviando['title' => 'Novo', 'body' => 'Corpo']e exiba o código de status e o corpo da resposta.✓ Resposta:<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; $client = new Client(); $response = $client->request('POST', 'https://jsonplaceholder.typicode.com/posts', [ 'json' => ['title' => 'Novo', 'body' => 'Corpo'] ]); echo 'Status: ' . $response->getStatusCode() . PHP_EOL; echo 'Corpo: ' . $response->getBody() . PHP_EOL; ?>Crie uma função que receba uma URL e um método HTTP (GET, POST, PUT, DELETE) e use cURL para executar a requisição. A função deve retornar um array com o status HTTP e o corpo decodificado. Inclua tratamento de erros.
✓ Resposta:function request($url, $method = 'GET', $data = null) { $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); if ($data !== null) { curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); } $response = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); return ['status' => $status, 'body' => json_decode($response, true)]; } // Exemplo de uso $result = request('https://jsonplaceholder.typicode.com/posts', 'GET'); print_r($result); ?>Usando Guzzle, escreva um código que capture exceções de cliente (4xx) e de servidor (5xx) separadamente, imprimindo mensagens diferentes.
✓ Resposta:<?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; use GuzzleHttp\Exception\ServerException; $client = new Client(); try { $response = $client->get('https://jsonplaceholder.typicode.com/posts/1'); echo 'Sucesso!'; } catch (ClientException $e) { echo 'Erro do cliente: ' . $e->getMessage(); } catch (ServerException $e) { echo 'Erro do servidor: ' . $e->getMessage(); } catch (Exception $e) { echo 'Outro erro: ' . $e->getMessage(); } ?>
Boas Práticas e Observações Finais
Ao trabalhar com clientes HTTP em PHP, é essencial adotar boas práticas para garantir segurança, desempenho e manutenibilidade. Sempre utilize HTTPS em produção e valide certificados SSL. Evite desabilitar a verificação SSL, a menos que esteja em ambiente de testes com plena consciência dos riscos.
Centralize a lógica de requisições em serviços ou classes reutilizáveis, em vez de espalhar chamadas diretas. Isso facilita testes e manutenção. Considere usar injeção de dependência para passar o cliente HTTP (como Guzzle) aos seus serviços, permitindo mockar em testes unitários.
Trate erros de forma consistente, registrando logs e fornecendo feedback amigável ao usuário. Defina timeouts adequados e limite o tamanho das respostas quando possível. Lembre-se de que APIs podem mudar, então mantenha a documentação atualizada e use versionamento de endpoints.
Por fim, escolha a ferramenta certa para cada caso: cURL é flexível e nativo, file_get_contents é simples para GETs rápidos, e Guzzle é robusto e recomendado para projetos sérios. Dominar essas técnicas fará de você um desenvolvedor PHP mais completo.