首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 判斷遠端檔案是否存在:HEAD 請求、串流檢查與效能最佳實踐

     編輯:PHP 相關知識

需確認遠端圖片、文件、API 端點是否存在?直接下載整個檔案極其浪費頻寬與時間。本文比較三種方法:**HEAD 請求(最佳)**、**串流讀取首位元組**、**get_headers()**,並提供完整封裝函數、逾時控制、重導向處理、快取策略。

方法比較

方法原理流量速度支援重導向備註
HEAD 請求只取回應標頭極少(~幾百 bytes)最快是標準 REST 檢查方式,需伺服器支援 HEAD
串流讀取 1 byteGET + Range: bytes=0-0極少快視實作而定某些伺服器不支援 Range 或 HEAD
get_headers()內建函數發送 HEAD/GET少中是PHP 封裝,易用但難控制細節
file_get_contents() 完整讀取下載整個檔案完整檔案大小慢是嚴禁用於單純存在性檢查

1. 最佳實作:cURL HEAD 請求(完整控制)

<?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']})";
}
?>

2. 替代:get_headers() 簡潔版

<?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;
}
?>

3. 串流讀取首位元組(適用不支援 HEAD 的伺服器)

<?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;
}
?>

常見問題排查

  1. 伺服器不支援 HEAD:回傳 405 Method Not Allowed。改用 GET + Range: bytes=0-0 或完整 GET(小檔案)。
  2. 重導向鏈過長:設定 CURLOPT_MAXREDIRS 限制,避免無限循環。
  3. CDN/防火牆攔截:回傳 403/503。需模擬真實瀏覽器 UA、Cookie、Referer,或使用住宅代理 IP。
  4. SSL 憑證問題:開發環境可暫時關閉 CURLOPT_SSL_VERIFYPEER,生產必須開啟並部署 CA 憑證。
  5. 大量並發檢查:使用 curl_multi 或非同步庫(ReactPHP、Swoole),避免阻塞 PHP-FPM 進程。

最佳實踐總結

  • 預設使用 cURL HEAD:最標準、流量最小、資訊最完整。
  • 失敗時降級:HEAD 405 → GET Range → 完整 GET(最後手段)。
  • 永遠設定逾時(連線 + 總逾時),防止掛起。
  • 驗證 Content-Type、Content-Length 確認檔案類型/大小符合預期。
  • 結果快取(TTL 視業務而定),批次檢查用 curl_multi。

正確檢查遠端檔案存在性,能大幅節省頻寬、降低延遲、提升系統穩定度。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved