HTTP 302 Found 回應會在 Location 標頭指示新位址。cURL 預設**不會**自動追蹤重導向,需啟用 CURLOPT_FOLLOWLOCATION。若需取得最終有效 URL,可透過 curl_getinfo() 的 CURLINFO_EFFECTIVE_URL 取得。
<?php
/**
* 發送 POST 請求並取得最終有效 URL (追蹤 302/301 重導向)
*
* @param string $url 目標網址
* @param array|string $postFields POST 欄位資料
* @param array $options 額外 cURL 選項
* @return string|false 最終網址或 false(失敗)
*/
function curlPostFollowRedirect($url, $postFields = [], $options = [])
{
$ch = curl_init();
$defaultOptions = [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_RETURNTRANSFER => true, // 回傳字串而非直接輸出
CURLOPT_FOLLOWLOCATION => true, // 關鍵:自動追蹤重導向
CURLOPT_MAXREDIRS => 10, // 最大重導向次數防迴圈
CURLOPT_SSL_VERIFYPEER => false, // 開發環境可關閉驗證,生產請開啟並設定 CA
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_TIMEOUT => 30, // 總逾時秒數
CURLOPT_CONNECTTIMEOUT => 10, // 連線逾時
CURLOPT_USERAGENT => 'Mozilla/5.0 (compatible; PHP cURL Bot)',
CURLOPT_HEADER => false, // 不包含回應標頭於內容中
];
curl_setopt_array($ch, array_merge($defaultOptions, $options));
$response = curl_exec($ch);
if ($response === false) {
$errno = curl_errno($ch);
$error = curl_error($ch);
error_log("cURL Error [$errno]: $error (URL: $url)");
curl_close($ch);
return false;
}
// 取得最終有效 URL (經過所有重導向後)
$effectiveUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// 可選:根據 HTTP 狀態碼判斷成功
if ($httpCode >= 400) {
error_log("HTTP $httpCode from final URL: $effectiveUrl");
return false;
}
return $effectiveUrl;
}
// 使用範例
$finalUrl = curlPostFollowRedirect('https://example.com/login', [
'username' => 'user',
'password' => 'pass',
]);
if ($finalUrl) {
echo "最終導向: $finalUrl";
} else {
echo "請求失敗";
}
?>
| 選項 | 用途 |
|---|---|
CURLOPT_FOLLOWLOCATION | 啟用自動追蹤 Location 標頭 (301/302/303/307/308) |
CURLOPT_MAXREDIRS | 限制最大重導向次數,防止無限迴圈 (預設 20) |
CURLINFO_EFFECTIVE_URL | 取得最後實際請求的 URL (含重導向後) |
CURLOPT_AUTOREFERER | 自動設定 Referer 標頭於重導向請求中 |
若目標站檢查 Referer 或需手動控制 Cookie,可關閉自動追蹤並自行迴圈:
<?php
function curlManualFollow($url, $postFields = [], $maxRedirects = 10)
{
$ch = curl_init();
$cookieJar = tempnam(sys_get_temp_dir(), 'cookie_');
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postFields,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false, // 關閉自動追蹤
CURLOPT_HEADER => true, // 需要標頭以解析 Location
CURLOPT_COOKIEJAR => $cookieJar,
CURLOPT_COOKIEFILE => $cookieJar,
CURLOPT_SSL_VERIFYPEER => false,
]);
$currentUrl = $url;
$redirectCount = 0;
do {
curl_setopt($ch, CURLOPT_URL, $currentUrl);
$response = curl_exec($ch);
if ($response === false) break;
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
if (!in_array($httpCode, [301, 302, 303, 307, 308])) {
break; // 非重導向,結束
}
// 解析 Location 標頭
if (preg_match('/^Location:\s*(.+)$/im', $headers, $matches)) {
$location = trim($matches[1]);
// 處理相對網址
$currentUrl = (parse_url($location, PHP_URL_SCHEME) ? $location : rtrim($currentUrl, '/') . '/' . ltrim($location, '/'));
$redirectCount++;
} else {
break; // 無 Location 標頭
}
} while ($redirectCount < $maxRedirects);
$finalUrl = curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);
curl_close($ch);
@unlink($cookieJar);
return $finalUrl;
}
?>
CURLOPT_FOLLOWLOCATION 會失效,需改用手動模式CURLOPT_CAINFO 指向 CA 憑證束,勿長期關閉驗證