需確認遠端圖片、文件、API 端點是否存在?直接下載整個檔案極其浪費頻寬與時間。本文比較三種方法:**HEAD 請求(最佳)**、**串流讀取首位元組**、**get_headers()**,並提供完整封裝函數、逾時控制、重導向處理、快取策略。
| 方法 | 原理 | 流量 | 速度 | 支援重導向 | 備註 |
|---|---|---|---|---|---|
| HEAD 請求 | 只取回應標頭 | 極少(~幾百 bytes) | 最快 | 是 | 標準 REST 檢查方式,需伺服器支援 HEAD |
| 串流讀取 1 byte | GET + Range: bytes=0-0 | 極少 | 快 | 視實作而定 | 某些伺服器不支援 Range 或 HEAD |
get_headers() | 內建函數發送 HEAD/GET | 少 | 中 | 是 | PHP 封裝,易用但難控制細節 |
file_get_contents() 完整讀取 | 下載整個檔案 | 完整檔案大小 | 慢 | 是 | 嚴禁用於單純存在性檢查 |
<?php
/**
* 檢查遠端 URL 是否存在(HTTP 2xx/3xx)
* @param string $url
* @param int $timeout 秒
* @param bool $followRedirects
* @return array ['exists'=>bool, 'code'=>int, 'headers'=>array, 'error'=>string|null]
*/
function checkRemoteFileExists(string $url, int $timeout = 10, bool $followRedirects = true): array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true, // HEAD 請求
CURLOPT_HEADER => true, // 取得標頭
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => $followRedirects,
CURLOPT_MAXREDIRS => 5,
CURLOPT_CONNECTTIMEOUT => $timeout,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_USERAGENT => 'Mozilla/5.0 (FileChecker)',
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = $response ? parseHeaders(substr($response, 0, $headerSize)) : [];
curl_close($ch);
if ($errno) {
return ['exists' => false, 'code' => 0, 'headers' => [], 'error' => $error];
}
// 2xx 成功、3xx 重導向(最終目標存在)、404/410 不存在、其他視為錯誤
$exists = ($httpCode >= 200 && $httpCode < 400) && $httpCode !== 304; // 304 Not Modified 視為存在
return ['exists' => $exists, 'code' => $httpCode, 'headers' => $headers, 'error' => null];
}
function parseHeaders(string $rawHeaders): array
{
$headers = [];
foreach (explode("\n", $rawHeaders) as $line) {
$line = trim($line);
if ($line === '' || str_starts_with($line, 'HTTP/')) continue;
[$key, $value] = array_map('trim', explode(':', $line, 2));
$headers[strtolower($key)] = $value;
}
return $headers;
}
// 使用範例
$result = checkRemoteFileExists('https://example.com/image.png', 5);
if ($result['exists']) {
echo "檔案存在 (HTTP {$result['code']})";
echo "Content-Type: {$result['headers']['content-type'] ?? 'unknown'}";
echo "Content-Length: {$result['headers']['content-length'] ?? 'unknown'}";
} else {
echo "檔案不存在或錯誤: {$result['error']} (HTTP {$result['code']})";
}
?>
<?php
function checkExistsGetHeaders(string $url, int $timeout = 10): bool
{
$ctx = stream_context_create([
'http' => [
'method' => 'HEAD',
'timeout' => $timeout,
'follow_location' => 1,
'max_redirects' => 5,
'user_agent' => 'PHP FileChecker',
],
'ssl' => ['verify_peer' => true, 'verify_peer_name' => true],
]);
$headers = @get_headers($url, 1, $ctx);
if ($headers === false) return false;
// 取得最終 HTTP 狀態碼
$statusLine = $headers[0] ?? '';
if (preg_match('/HTTP\/\d\.\d\s+(\d{3})/', $statusLine, $m)) {
$code = (int)$m[1];
return $code >= 200 && $code < 400;
}
return false;
}
?>
<?php
function checkExistsStream(string $url, int $timeout = 10): bool
{
$ctx = stream_context_create([
'http' => [
'method' => 'GET',
'header' => 'Range: bytes=0-0', // 只請求第一個位元組
'timeout' => $timeout,
'follow_location' => 1,
],
'ssl' => ['verify_peer' => true],
]);
$handle = @fopen($url, 'rb', false, $ctx);
if ($handle) {
fclose($handle);
return true;
}
return false;
}
?>
<?php
function batchCheckUrls(array $urls, int $concurrency = 10): \Generator
{
$mh = curl_multi_init();
$handles = [];
// 初始化所有 handle
foreach ($urls as $url) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_HEADER => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => 3,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
]);
curl_multi_add_handle($mh, $ch);
$handles[$url] = $ch;
}
// 並發執行
$running = null;
do {
curl_multi_exec($mh, $running);
curl_multi_select($mh, 0.1);
} while ($running > 0);
// 收集結果
foreach ($handles as $url => $ch) {
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$errno = curl_errno($ch);
$error = curl_error($ch);
$exists = !$errno && $httpCode >= 200 && $httpCode < 400;
yield $url => ['exists' => $exists, 'code' => $httpCode, 'error' => $errno ? $error : null];
curl_multi_remove_handle($mh, $ch);
curl_close($ch);
}
curl_multi_close($mh);
}
// 使用
foreach (batchCheckUrls($imageUrls, 20) as $url => $result) {
// 處理結果...
}
?>
<?php
function cachedCheck(string $url, int $ttl = 3600): array
{
$key = 'url_exists:' . md5($url);
// $cached = apcu_fetch($key) ?? redis_get($key);
// if ($cached) return $cached;
$result = checkRemoteFileExists($url);
// apcu_store($key, $result, $ttl);
return $result;
}
?>
GET + Range: bytes=0-0 或完整 GET(小檔案)。CURLOPT_MAXREDIRS 限制,避免無限循環。CURLOPT_SSL_VERIFYPEER,生產必須開啟並部署 CA 憑證。curl_multi 或非同步庫(ReactPHP、Swoole),避免阻塞 PHP-FPM 進程。Content-Type、Content-Length 確認檔案類型/大小符合預期。curl_multi。正確檢查遠端檔案存在性,能大幅節省頻寬、降低延遲、提升系統穩定度。