PHP 向外部服务发起 HTTP 请求是微服务、支付回调、爬虫、第三方 API 集成的基础操作。本文系统对比三种主流方案:原生 cURL、原生 Socket、Guzzle HTTP Client (推荐),并给出生产级封装建议。
function httpGet(string $url, array $headers = [], int $timeout = 10): string
{
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_SSL_VERIFYPEER => true, // 生产必须验证 SSL
CURLOPT_FOLLOWLOCATION => true, // 支持 301/302 跳转
CURLOPT_MAXREDIRS => 5,
CURLOPT_USERAGENT => 'Mozilla/5.0 (PHP/' . PHP_VERSION . ')',
]);
$response = curl_exec($ch);
if ($response === false) {
throw new \RuntimeException('cURL error: ' . curl_error($ch), curl_errno($ch));
}
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400) {
throw new \RuntimeException("HTTP $httpCode: $response");
}
return $response;
}
function httpPost(string $url, array $data, bool $isJson = true, array $headers = [], int $timeout = 15): string
{
$ch = curl_init();
$opts = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_SSL_VERIFYPEER => true,
];
if ($isJson) {
$opts[CURLOPT_POSTFIELDS] = json_encode($data, JSON_UNESCAPED_UNICODE);
$headers[] = 'Content-Type: application/json; charset=utf-8';
} else {
$opts[CURLOPT_POSTFIELDS] = http_build_query($data);
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
}
$opts[CURLOPT_HTTPHEADER] = $headers;
curl_setopt_array($ch, $opts);
$response = curl_exec($ch);
if ($response === false) {
throw new \RuntimeException('cURL error: ' . curl_error($ch), curl_errno($ch));
}
curl_close($ch);
return $response;
}
function httpSocket(string $method, string $url, array $data = [], array $headers = []): string
{
$parsed = parse_url($url);
$host = $parsed['host'];
$port = $parsed['port'] ?? ($parsed['scheme'] === 'https' ? 443 : 80);
$path = ($parsed['path'] ?? '/') . ($parsed['query'] ? '?' . $parsed['query'] : '');
$body = $method === 'POST' ? ($data ? (is_array($data) ? http_build_query($data) : $data) : '') : '';
if ($method === 'GET' && $data) {
$path .= (strpos($path, '?') ? '&' : '?') . http_build_query($data);
}
$request = "$method $path HTTP/1.1\r\n";
$request .= "Host: $host\r\n";
$request .= "User-Agent: PHP-Socket/" . PHP_VERSION . "\r\n";
$request .= "Connection: Close\r\n";
if ($body) {
$request .= "Content-Length: " . strlen($body) . "\r\n";
$request .= "Content-Type: application/x-www-form-urlencoded\r\n";
}
foreach ($headers as $k => $v) {
$request .= "$k: $v\r\n";
}
$request .= "\r\n$body";
$scheme = $parsed['scheme'] === 'https' ? 'ssl://' : '';
$fp = fsockopen($scheme . $host, $port, $errno, $errstr, 10);
if (!$fp) throw new \RuntimeException("Socket connect failed: $errstr ($errno)");
fwrite($fp, $request);
$response = stream_get_contents($fp);
fclose($fp);
// 简易分离头体
list($header, $body) = explode("\r\n\r\n", $response, 2);
return $body;
}
缺点:需手动处理 SSL、分块传输、重定向、Cookie、Keep-Alive,极易出 Bug,不推荐生产使用。
composer require guzzlehttp/guzzle
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
$client = new Client([
'base_uri' => 'https://api.example.com/',
'timeout' => 10,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'MyApp/1.0',
],
// 中间件:自动重试、日志、认证等
]);
try {
// GET
$response = $client->get('users/1', ['query' => ['include' => 'posts']]);
// POST JSON
$response = $client->post('orders', [
'json' => ['product_id' => 123, 'qty' => 2],
'headers' => ['Authorization' => 'Bearer ' . $token],
]);
// 流式下载大文件
$client->get('export.csv', ['sink' => fopen('storage/export.csv', 'w')]);
$data = json_decode($response->getBody()->getContents(), true);
$status = $response->getStatusCode();
} catch (RequestException $e) {
if ($e->hasResponse()) {
$errBody = $e->getResponse()->getBody()->getContents();
$errCode = $e->getResponse()->getStatusCode();
}
// 记录日志、抛出业务异常
}
| 维度 | cURL 原生 | Socket 原生 | Guzzle |
|---|---|---|---|
| 性能 | ★★★★★ | ★★★☆☆ | ★★★★☆ (内部用 cURL) |
| 功能完整度 | 手动实现 | 极简 | 开箱即用 (重试、中间件、异步、流、Cookie、认证) |
| 代码量 | 中 | 大 | 少 |
| 维护性 | 易漏配置 | 极难 | 优秀 |
| 依赖 | php-curl 扩展 | 无 | Composer 包 |
| 推荐场景 | 简单项目、无 Composer、极致性能 | 教学、极简环境 | 所有现代项目首选 |
<?php
namespace App\Service;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Psr\Log\LoggerInterface;
class HttpClient
{
private Client $client;
private LoggerInterface $logger;
public function __construct(LoggerInterface $logger, array $config = [])
{
$this->logger = $logger;
$this->client = new Client(array_merge([
'timeout' => 10,
'connect_timeout' => 3,
'http_errors' => false, // 不抛异常,统一业务层处理
], $config));
}
public function request(string $method, string $uri, array $options = []): array
{
$start = microtime(true);
try {
$response = $this->client->request($method, $uri, $options);
$body = $response->getBody()->getContents();
$code = $response->getStatusCode();
$this->log($method, $uri, $code, microtime(true) - $start);
return ['code' => $code, 'data' => json_decode($body, true) ?? $body];
} catch (GuzzleException $e) {
$this->logger->error('HTTP request failed', [
'method' => $method, 'uri' => $uri, 'error' => $e->getMessage(),
]);
throw new \RuntimeException("HTTP 请求失败: {$e->getMessage()}", 0, $e);
}
}
private function log(string $method, string $uri, int $code, float $ms): void
{
$this->logger->info('HTTP Request', compact('method', 'uri', 'code', 'ms'));
}
}
现代 PHP 项目请直接使用 Guzzle。它封装了 cURL 的复杂性,提供 PSR-7/18 标准接口,支持中间件扩展(重试、熔断、限流、签名、日志),是微服务架构下的 HTTP 通信基石。仅在无法安装扩展/Composer 的极简环境下,才考虑原生 cURL 封装。