本文目录导读:

- cURL 请求
- Guzzle HTTP Client(推荐)
- 文件流获取(大文件下载重试)
- 异步请求(ReactPHP/Swoole)
- 通用重试函数
- 超时设置的注意事项
- 完整示例:带重试的API请求类
- 最佳实践建议
在PHP中进行HTTP请求时,设置超时和重试机制非常重要,以下是各种场景下的详细配置方法:
cURL 请求
基础配置
function curlRequest($url, $options = []) {
$defaults = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5, // 连接超时(秒)
CURLOPT_TIMEOUT => 30, // 总超时(秒)
CURLOPT_FOLLOWLOCATION => true, // 跟随重定向
CURLOPT_MAXREDIRS => 5, // 最大重定向次数
];
$ch = curl_init();
curl_setopt_array($ch, array_replace($defaults, $options));
$result = curl_exec($ch);
if (curl_errno($ch)) {
$error = curl_error($ch);
curl_close($ch);
throw new Exception("Curl error: $error");
}
curl_close($ch);
return $result;
}
重试机制实现
function requestWithRetry($url, $maxRetries = 3, $options = []) {
$retry = 0;
$delay = 1; // 初始延迟(秒)
while ($retry < $maxRetries) {
try {
$response = curlRequest($url, $options);
return $response;
} catch (Exception $e) {
$retry++;
// 达到最大重试次数
if ($retry >= $maxRetries) {
throw new Exception("Request failed after $maxRetries attempts: " . $e->getMessage());
}
// 指数退避策略
$sleepTime = $delay * pow(2, $retry - 1);
echo "Retry $retry in {$sleepTime} seconds...\n";
sleep($sleepTime);
}
}
}
// 使用示例
$url = "https://api.example.com/data";
try {
$data = requestWithRetry($url, 3, [
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 3,
]);
} catch (Exception $e) {
echo "Error: " . $e->getMessage();
}
Guzzle HTTP Client(推荐)
安装
composer require guzzlehttp/guzzle
基础配置和重试
use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\RetryMiddleware;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
// 创建客户端
$client = new Client([
'base_uri' => 'https://api.example.com',
'timeout' => 10, // 总超时
'connect_timeout' => 5, // 连接超时
'verify' => false, // 开发环境可关闭SSL验证
'http_errors' => true, // 是否抛出HTTP错误异常
]);
// 简单请求
try {
$response = $client->request('GET', '/data', [
'timeout' => 30, // 覆盖默认超时
]);
$body = $response->getBody()->getContents();
} catch (RequestException $e) {
echo "Request failed: " . $e->getMessage();
if ($e->hasResponse()) {
$statusCode = $e->getResponse()->getStatusCode();
// 根据状态码决定是否重试
if (in_array($statusCode, [408, 429, 500, 502, 503, 504])) {
// 可以在这里重试
}
}
}
Guzzle 重试中间件
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
function retryMiddleware($maxRetries = 3, $delay = 1) {
return function($handler) use ($maxRetries, $delay) {
return function($request, $options) use ($handler, $maxRetries, $delay) {
$attempt = 0;
do {
try {
$response = $handler($request, $options);
// 检查状态码
$statusCode = $response->getStatusCode();
if ($statusCode >= 500 || $statusCode == 408 || $statusCode == 429) {
if ($attempt >= $maxRetries - 1) {
return $response;
}
$attempt++;
sleep($delay * $attempt);
continue;
}
return $response;
} catch (RequestException $e) {
if ($attempt >= $maxRetries - 1) {
throw $e;
}
$attempt++;
sleep($delay * $attempt);
}
} while ($attempt < $maxRetries);
};
};
}
// 使用重试中间件
$handler = HandlerStack::create();
$retryConfig = [
'max' => 3, // 最大重试次数
'delay' => 1000, // 基础延迟(毫秒)
'retry_if' => function($retries, $response, $request) {
// 自定义重试条件
if ($retries >= 3) {
return false;
}
if ($response) {
return in_array($response->getStatusCode(), [429, 500, 503]);
}
return true; // 网络错误时重试
}
];
$handler->push(Middleware::retry($retryConfig['retry_if'], $retryConfig['delay']));
$client = new Client([
'handler' => $handler,
'timeout' => 30,
]);
文件流获取(大文件下载重试)
function downloadFileWithRetry($url, $destination, $maxRetries = 3) {
$retry = 0;
while ($retry < $maxRetries) {
try {
$ch = curl_init($url);
$fp = fopen($destination, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 3600); // 较长超时
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30); // 连接超时
if (curl_exec($ch) === false) {
throw new Exception("Download failed: " . curl_error($ch));
}
curl_close($ch);
fclose($fp);
// 验证文件大小
if (filesize($destination) > 0) {
return true;
}
throw new Exception("Downloaded file is empty");
} catch (Exception $e) {
$retry++;
if ($retry >= $maxRetries) {
throw new Exception("File download failed after $maxRetries attempts");
}
// 清理部分下载的文件
if (file_exists($destination)) {
unlink($destination);
}
echo "Retry downloading... Attempt $retry\n";
sleep(2 * $retry);
}
}
}
异步请求(ReactPHP/Swoole)
Swoole 协程版本
use Swoole\Coroutine\Http\Client;
function asyncRequestWithRetry($url, $maxRetries = 3) {
$retry = 0;
while ($retry < $maxRetries) {
$client = new Client('api.example.com', 443, true);
$client->set([
'timeout' => 5,
'connect_timeout' => 3,
]);
$client->get('/path/to/resource');
if ($client->statusCode === 200) {
$result = $client->body;
$client->close();
return $result;
}
$retry++;
$client->close();
if ($retry < $maxRetries) {
co::sleep(500 * $retry); // 毫秒
}
}
return null;
}
通用重试函数
/**
* 通用重试函数
*/
function retry($callback, $maxAttempts = 3, $delay = 1000, $backoff = true) {
$attempts = 0;
while ($attempts < $maxAttempts) {
try {
return $callback();
} catch (Exception $e) {
$attempts++;
if ($attempts >= $maxAttempts) {
throw $e;
}
$waitTime = $backoff ? $delay * pow(2, $attempts - 1) : $delay;
echo "Attempt $attempts failed. Retrying in {$waitTime}ms...\n";
usleep($waitTime * 1000); // 转换为微秒
}
}
}
// 使用方式
$result = retry(function() {
return file_get_contents("https://api.example.com/data");
}, 5, 1000); // 最多重试5次,基础延迟1秒
超时设置的注意事项
合理设置超时
$config = [
// 连接超时:建立TCP连接的最大时间
'connect_timeout' => 5, // 建议 2-10 秒
// 总超时:整个请求的最大时间
'timeout' => 30, // 建议 10-60 秒
// 读超时:接收数据的最大间隔
'read_timeout' => 5, // 建议 2-10 秒
// 写超时:发送数据的最大间隔
'write_timeout' => 5, // 建议 2-10 秒
];
完整示例:带重试的API请求类
class ApiClient {
private $client;
private $maxRetries = 3;
private $delay = 1000; // 毫秒
public function __construct() {
$this->client = new Client([
'timeout' => 30,
'connect_timeout' => 5,
'headers' => [
'Accept' => 'application/json',
'User-Agent' => 'API-Client/1.0'
]
]);
}
public function request($method, $endpoint, $data = null) {
$attempts = 0;
while ($attempts < $this->maxRetries) {
try {
$response = $this->client->request($method, $endpoint, [
'json' => $data
]);
return json_decode($response->getBody(), true);
} catch (RequestException $e) {
$attempts++;
if ($attempts >= $this->maxRetries) {
throw new Exception("API request failed: {$e->getMessage()}");
}
$waitTime = $this->delay * pow(2, $attempts - 1);
echo "Retrying request to $endpoint in {$waitTime}ms...\n";
usleep($waitTime * 1000);
} catch (Exception $e) {
// 非HTTP异常直接抛出
throw $e;
}
}
}
// 设置重试参数
public function setRetryPolicy($maxRetries, $delay = 1000) {
$this->maxRetries = $maxRetries;
$this->delay = $delay;
}
}
最佳实践建议
- 超时设置:连接超时建议 5 秒,总超时建议 30 秒,根据业务需求调整
- 重试次数:一般建议 3-5 次,过多会增加服务器压力
- 退避策略:使用指数退避(如 1s, 2s, 4s)而不是固定延迟,避免雪崩效应
- 只重试安全请求:GET、HEAD 等幂等操作可以重试;POST、PUT 等要谨慎
- 记录日志:记录重试次数和原因,便于调试
- 并发控制:高并发时应考虑限流,避免重试造成服务过载
这些方法应该能覆盖大多数 PHP HTTP 请求的超时和重试需求。