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

PHP cURL 取得 302 重導向後的最終網址

     編輯:PHP 相關知識

處理短網址、防盜鏈、OAuth 回調等場景時,需追蹤 HTTP 301/302 重導向鏈並取得最終 URL。本文示範 cURL 正確設定、重導向限制、Header 擷取、錯誤處理,並提供可重用函式。

一、核心原理

  • CURLOPT_FOLLOWLOCATION = true:啟用自動追蹤重導向
  • CURLOPT_MAXREDIRS:限制最大重導向次數 (預設 20,防無限迴圈)
  • curl_getinfo($ch, CURLINFO_EFFECTIVE_URL):取得最終有效 URL (經過所有重導向後)

二、完整函式實作

<?php
/**
 * 取得 URL 經過重導向後的最終網址
 *
 * @param string $url 目標網址
 * @param array  $options 額外 cURL 選項
 * @param int    $timeout 逾時秒數
 * @return string|false 最終網址,失敗回傳 false
 */
function getFinalUrl(string $url, array $options = [], int $timeout = 10): string|false {
    $ch = curl_init();

    $defaultOptions = [
        CURLOPT_URL            => $url,
        CURLOPT_RETURNTRANSFER => true,     // 回傳字串而非直接輸出
        CURLOPT_FOLLOWLOCATION => true,     // 追蹤重導向
        CURLOPT_MAXREDIRS      => 10,       // 最大重導向次數
        CURLOPT_CONNECTTIMEOUT => $timeout, // 連線逾時
        CURLOPT_TIMEOUT        => $timeout, // 總逾時
        CURLOPT_SSL_VERIFYPEER => true,     // 生產環境必須驗證憑證
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 (compatible; UrlResolver/1.0)',
        CURLOPT_HEADER         => false,    // 不需回應 Header
        CURLOPT_NOBODY         => true,     // 僅 HEAD 請求,不下載 Body (節省頻寬)
    ];

    curl_setopt_array($ch, array_merge($defaultOptions, $options));

    $response = curl_exec($ch);
    $errno    = curl_errno($ch);
    $info     = curl_getinfo($ch);
    curl_close($ch);

    if ($errno) {
        error_log("cURL 錯誤 ($errno): " . curl_strerror($errno) . " for URL: $url");
        return false;
    }

    // HTTP 狀態碼檢查 (2xx, 3xx 視為成功到達)
    $httpCode = $info['http_code'] ?? 0;
    if ($httpCode < 200 || $httpCode >= 400) {
        error_log("HTTP 狀態碼異常: $httpCode for URL: $url");
        return false;
    }

    // 取得最終有效 URL
    $finalUrl = $info['url'] ?? $url; // 'url' 即 CURLINFO_EFFECTIVE_URL

    // 可選:驗證最終 URL 是否為預期域名 (防開放式重導向攻擊)
    // if (!str_starts_with($finalUrl, 'https://expected-domain.com/')) { return false; }

    return $finalUrl;
}

// 使用範例
$shortUrl = 'https://bit.ly/3xYz';
$final = getFinalUrl($shortUrl);
if ($final) {
    echo "最終網址: $final";
} else {
    echo "解析失敗";
}

三、進階:手動處理重導向 (需擷取中間 Header/Set-Cookie)

function traceRedirects(string $url, int $maxRedirects = 10): array {
    $history = [];
    $currentUrl = $url;

    for ($i = 0; $i < $maxRedirects; $i++) {
        $ch = curl_init($currentUrl);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => false, // 手動處理
            CURLOPT_HEADER         => true,  // 需要 Header
            CURLOPT_NOBODY         => true,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_TIMEOUT        => 10,
        ]);

        $response = curl_exec($ch);
        $info = curl_getinfo($ch);
        $errno = curl_errno($ch);
        curl_close($ch);

        if ($errno) {
            $history[] = ['error' => curl_strerror($errno)];
            break;
        }

        $httpCode = $info['http_code'];
        $headers = substr($response, 0, $info['header_size']);
        $headerLines = explode("\r\n", $headers);

        $location = null;
        $setCookies = [];
        foreach ($headerLines as $line) {
            if (stripos($line, 'Location:') === 0) {
                $location = trim(substr($line, 9));
            } elseif (stripos($line, 'Set-Cookie:') === 0) {
                $setCookies[] = trim(substr($line, 11));
            }
        }

        $history[] = [
            'url' => $currentUrl,
            'http_code' => $httpCode,
            'location' => $location,
            'cookies' => $setCookies,
        ];

        if (!in_array($httpCode, [301, 302, 303, 307, 308]) || !$location) {
            break; // 無重導向或非重導向狀態碼
        }

        // 處理相對路徑 Location
        $currentUrl = resolveRelativeUrl($currentUrl, $location);
    }

    return $history;
}

function resolveRelativeUrl(string $base, string $relative): string {
    $baseParts = parse_url($base);
    if (str_starts_with($relative, 'https://') || str_starts_with($relative, 'https://')) {
        return $relative;
    }
    if (str_starts_with($relative, '/')) {
        return "{$baseParts['scheme']}://{$baseParts['host']}$relative";
    }
    // 相對路徑
    $basePath = dirname($baseParts['path'] ?? '/');
    return "{$baseParts['scheme']}://{$baseParts['host']}$basePath/$relative";
}

四、常見坑點與解決

問題原因解決
CURLOPT_FOLLOWLOCATION 無效開啟 open_basedir 或 safe_mode 時被限制關閉 open_basedir 或改用手動重導向 (上方 traceRedirects)
HTTPS 憑證驗證失敗缺少 CA 憑證束php.ini 設定 openssl.cafile、curl.cainfo;或暫時關閉驗證 (僅測試)
無限重導向迴圈伺服器端配置錯誤設定 CURLOPT_MAXREDIRS (建議 10-20)
取得的 URL 為原始短網址未啟用 FOLLOWLOCATION 或用錯 curl_getinfo 參數使用 CURLINFO_EFFECTIVE_URL (對應 $info['url'])
HEAD 請求被拒 (405/403)部分伺服器不支援 HEAD移除 CURLOPT_NOBODY,改用 GET 但設定 CURLOPT_RANGE => '0-0' 僅讀 1 byte

五、應用場景

  1. 短網址還原:bit.ly, t.co, short.io 等
  2. 防盜鏈驗證:確認圖片/影片最終來源域名
  3. OAuth 回調驗證:檢查 redirect_uri 是否符合預期
  4. 爬蟲去重:以最終 URL 作為唯一鍵
  5. 連結健康檢查:定期掃描資料庫連結,更新失效/重導向 URL

六、總結

關鍵在於 CURLOPT_FOLLOWLOCATION + CURLOPT_MAXREDIRS + curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) 三件套。生產環境**必須**啟用 SSL 驗證、設定逾時、限制重導向次數、記錄錯誤日誌。需擷取中間 Header/Cookie 時改用手動模式 (FOLLOWLOCATION=false) 逐步追蹤。

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