處理短網址、防盜鏈、CDN 跳轉等場景時,常需解析重定向鏈路獲取最終落地頁。本文提供基於 Socket 與 cURL 兩種方案的完整實現。
發送 HEAD 請求解析 Location 標頭,遞迴追蹤完整重定向鏈:
<?php
class RedirectUrl {
private $url;
public function __construct($url) {
$this->url = $url;
}
/** 獲取單次重定向目標 */
private function getRedirectUrl($url) {
$parts = @parse_url($url);
if (!$parts || !isset($parts['host'])) return false;
$parts['path'] = $parts['path'] ?? '/';
$sock = fsockopen($parts['host'], $parts['port'] ?? 80, $errno, $errstr, 30);
if (!$sock) return false;
$request = "HEAD {$parts['path']}" . (isset($parts['query']) ? '?' . $parts['query'] : '') . " HTTP/1.1\r\n";
$request .= "Host: {$parts['host']}\r\n";
$request .= "Connection: Close\r\n\r\n";
fwrite($sock, $request);
$response = '';
while (!feof($sock)) $response .= fread($sock, 8192);
fclose($sock);
if (preg_match('/^Location: (.+?)$/m', $response, $matches)) {
return trim($matches[1]);
}
return false;
}
/** 遞迴獲取所有重定向 */
private function getAllRedirects($url) {
$redirects = [];
while ($newUrl = $this->getRedirectUrl($url)) {
if (in_array($newUrl, $redirects)) break; // 防循環
$redirects[] = $newUrl;
$url = $newUrl;
}
return $redirects;
}
/** 獲取最終 URL */
public function getFinalUrl() {
$redirects = $this->getAllRedirects($this->url);
return $redirects ? array_pop($redirects) : $this->url;
}
}
// 使用範例
$obj = new RedirectUrl('https://short.url/abc');
echo $obj->getFinalUrl();
更健壯、支援 HTTPS、自動處理壓縮、Cookie、並行請求:
function getFinalUrlCurl($url, $maxRedirects = 10) {
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_HEADER => true,
CURLOPT_NOBODY => true, // HEAD 請求
CURLOPT_FOLLOWLOCATION => false, // 手動處理遞迴
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP Redirect Resolver)',
]);
$redirects = 0;
$finalUrl = $url;
while ($redirects < $maxRedirects) {
$response = curl_exec($ch);
if ($response === false) break;
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode < 300 || $httpCode >= 400) break; // 非重定向
// 從回應標頭提取 Location
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $headerSize);
if (preg_match('/^Location:\s*(.+)$/mi', $header, $matches)) {
$finalUrl = trim($matches[1]);
// 處理相對 URL
if (strpos($finalUrl, 'http') !== 0) {
$parts = parse_url($url);
$finalUrl = $parts['scheme'] . '://' . $parts['host'] .
(strpos($finalUrl, '/') === 0 ? '' : dirname($parts['path']) . '/') . $finalUrl;
}
curl_setopt($ch, CURLOPT_URL, $finalUrl);
$url = $finalUrl;
$redirects++;
} else {
break;
}
}
curl_close($ch);
return $finalUrl;
}
function getShowPic($url, $noImg = 'noimg.gif', $fallback = 'https://example.com/default.jpg') {
$finalUrl = getFinalUrlCurl($url);
// 若最終圖為預設佔位圖,回傳備用圖
return strpos($finalUrl, $noImg) !== false ? $fallback : $url;
}
// 使用
echo "<img src='" . getShowPic('https://i2.ku6img.com/encode/picpath/.../5.jpg') . "'/>";
| 特性 | Socket 方案 | cURL 方案 |
|---|---|---|
| 依賴 | 無 (核心函式) | 需 php-curl 擴充 |
| HTTPS 支援 | 需自行實現 SSL | 原生支援 |
| 效能 | 單請求較快 | 連線複用、並行優勢 |
| 健壯性 | 需自行處理編碼、壓縮 | 完善錯誤處理 |
| 推薦場景 | 簡單環境、無 cURL 限制 | 生產環境、高併發 |
maxRedirects 防止無限循環Location 標頭Refresh 標頭或 JS 跳轉curl_multi 並行處理