PHP 內建 get_headers() **不支援超時參數**,在網路異常、目標主機無回應時會阻塞腳本長達預設 Socket 超時 (通常 60 秒),嚴重影響可用性。本文提供基於 cURL 的 get_url_headers() 替代實現,支援毫秒級超時控制、完整 Header 解析、重定向處理,並對比原生函數效能差異。
default_socket_timeout (php.ini),粒度粗<?php
/**
* 獲取 URL 回應 Header,支援超時控制
* @param string $url 目標 URL
* @param float $timeout 總超時秒數 (支援小數,如 0.5 = 500ms)
* @param float $connectTimeout 連線超時秒數
* @param bool $followLocation 是否跟隨重定向
* @param int $maxRedirects 最大重定向次數
* @return array|false 關聯陣列 [header_name => value] 或 false 失敗
*/
function get_url_headers(
string $url,
float $timeout = 5.0,
float $connectTimeout = 2.0,
bool $followLocation = true,
int $maxRedirects = 5
): array|false {
$ch = curl_init();
// 核心配置
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_HEADER => true, // 包含 Header 在輸出中
CURLOPT_NOBODY => true, // HEAD 請求,不下載 Body
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => (int)($timeout * 1000), // 總超時 (毫秒)
CURLOPT_CONNECTTIMEOUT_MS => (int)($connectTimeout * 1000), // 連線超時
CURLOPT_FOLLOWLOCATION => $followLocation,
CURLOPT_MAXREDIRS => $maxRedirects,
CURLOPT_USERAGENT => 'PHP-cURL-Headers/' . phpversion(),
CURLOPT_ENCODING => '', // 接受所有編碼
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
// 執行
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($errno) {
// 可選:記錄錯誤
// error_log("get_url_headers error [$errno]: $error | URL: $url");
return false;
}
// 解析 Header
// 回應格式: "HTTP/1.1 200 OK\r\nHeader1: value1\r\nHeader2: value2\r\n\r\n"
$headerText = $response;
$headers = [];
// 分割多重回應 (重定向鏈)
$parts = preg_split('/\r\n\r\n(?=HTTP\/)/', $headerText);
$lastPart = end($parts); // 取最終回應 Header
$lines = explode("\n", $lastPart);
foreach ($lines as $line) {
$line = trim($line);
if ($line === '') continue;
// 狀態行: HTTP/1.1 200 OK
if (preg_match('/^HTTP\/\d\.\d\s+(\d{3})/', $line, $m)) {
$headers[0] = $line; // 保留狀態行
$headers['http_code'] = (int)$m[1];
continue;
}
// 一般 Header: Name: value
if (($pos = strpos($line, ':')) !== false) {
$name = trim(substr($line, 0, $pos));
$value = trim(substr($line, $pos + 1));
// 標準化 Header 名稱 (首字大寫、連字號分隔)
$name = implode('-', array_map('ucfirst', explode('-', strtolower($name))));
// 處理重複 Header (如 Set-Cookie, Content-Type)
if (isset($headers[$name])) {
if (!is_array($headers[$name])) {
$headers[$name] = [$headers[$name]];
}
$headers[$name][] = $value;
} else {
$headers[$name] = $value;
}
}
}
return $headers;
}
<?php
require 'get_url_headers.php';
// 基本用法 (5秒超時)
$headers = get_url_headers('https://httpbin.org/headers', 5.0);
if ($headers === false) {
echo "請求失敗或超時\n";
} else {
echo "HTTP 狀態: " . ($headers['http_code'] ?? 'N/A') . "\n";
echo "Content-Type: " . ($headers['Content-Type'] ?? 'N/A') . "\n";
echo "Server: " . ($headers['Server'] ?? 'N/A') . "\n";
print_r($headers);
}
// 快速檢查 (500ms 總超時, 200ms 連線超時)
$headers = get_url_headers('https://api.example.com/health', 0.5, 0.2);
if ($headers && $headers['http_code'] === 200) {
echo "服務正常\n";
} else {
echo "服務異常或超時\n";
}
// 自定義 Header
$headers = get_url_headers('https://api.example.com/data', 3.0, 1.0, true, 3);
| 場景 | get_headers() | get_url_headers() (cURL) |
|---|---|---|
| 正常回應 (200ms) | ~210 ms | ~215 ms |
| 目標無回應 (黑洞) | **阻塞 60 秒** (預設) | **5 秒返回 false** (可控) |
| DNS 解析失敗 | 阻塞至 DNS 超時 | 受 CURLOPT_CONNECTTIMEOUT 控制 |
| SSL 握手慢 | 不可控 | 受總超時控制 |
| 重定向鏈 (5 次) | 不可控總時間 | 受總超時控制,可限制次數 |
| 記憶體佔用 | 較高 (內部緩衝) | 較低 (串流處理) |
function get_url_headers_ex(string $url, array $customHeaders = [], float $timeout = 5.0): array|false {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => (int)($timeout * 1000),
CURLOPT_HTTPHEADER => $customHeaders, // ['Authorization: Bearer xxx', 'X-API-Key: yyy']
]);
// ... 其餘同前
}
function get_multi_headers(array $urls, float $timeout = 3.0): array {
$mh = curl_multi_init();
$handles = [];
foreach ($urls as $i => $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => (int)($timeout * 1000),
]);
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, $timeout);
} while ($running > 0);
$results = [];
foreach ($handles as $i => $ch) {
$response = curl_multi_getcontent($ch);
$errno = curl_errno($ch);
$results[$urls[$i]] = $errno ? false : parse_headers($response);
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_cleanup($mh);
return $results;
}
位於 ext/standard/url.c,核心流程:
php_stream_contextphp_stream_open_wrapper_ex() 開啟 HTTP 串流wrapperdata (Header 中繼資料)關鍵點:底層使用 PHP Streams,受 default_socket_timeout 控制,無獨立超時 API 暴露給用戶層。
curl_getinfo 的 total_time, namelookup_time, connect_time, pretransfer_timecurl_multi 或異步隊列以 cURL 替代 get_headers() 是提升 PHP 服務韌性的關鍵微優化。**可控的超時、完整的錯誤資訊、靈活的 Header 控制**,讓你在面對不穩定的外部依賴時游刃有餘。