如何在WordPress中用PHP追踪单个URL的所有重定向
WordPress重定向追踪功能故障排查与修复
问题背景
在WordPress开发的Web应用中,需实现接收URL并列出所有重定向记录的功能。尝试过curl、wp_remote_head、get_header及file_get_contents等方法,效果不佳,还遇到部分站点返回的header中location参数大小写不一致的问题。
功能通过Ajax调用实现,结果存入transient防止请求轰炸,最终以无序列表展示结果,但当前无法列出重定向记录,始终显示“未找到重定向”。
期望输出格式:
<ul> <li>Origin: <a href="http://marketingtechblog.com" rel="nofollow noreferrer">http://marketingtechblog.com</a></li> <li>Redirect (301): <a href="http://martech.zone" rel="nofollow noreferrer">http://martech.zone</a></li> <li>Redirect (301): <a href="https://martech.zone" rel="nofollow noreferrer">https://martech.zone</a></li> <li>Destination (200): <a href="https://martech.zone" rel="nofollow noreferrer">https://martech.zone</a></li> </ul>
当前问题代码
function traceredirects() { check_ajax_referer( 'traceredirects_nonce', 'nonce' ); $url = $_POST['url']; $valid_url = filter_var($url, FILTER_VALIDATE_URL); if (!$valid_url) { echo '<strong>Error:</strong> Please enter a valid URL with http:// or https://'; wp_die(); } $host = parse_url($url, PHP_URL_HOST); if(!checkdnsrr($host, 'ANY')) { echo '<strong>Error:</strong> Could not find the domain ' . $host; wp_die(); } $transient_name = $url . '_traceredirect'; $traced = get_transient( $transient_name ); if (false === $traced) { $args = array( 'redirection' => 10, // follow up to 10 redirects ); $redirects = array(); $redirects[] = $url; $response = wp_remote_get( $url, $args ); $headers = wp_remote_retrieve_headers( $response ); $http_code = wp_remote_retrieve_response_code( $response ); $traced = '<ul>'; if ( ! is_wp_error( $response ) ) { if (!isset($headers['location'])) { $traced .= '<li>No redirects found</li>'; } else { while (isset($headers['location'])) { if ( in_array( $headers['location'], $redirects ) ) { echo 'Error: Redirect loop detected'; break; } $redirects[] = $headers['location']; $response = wp_remote_get( $headers['location'], $args ); $headers = wp_remote_retrieve_headers( $response ); $http_code = wp_remote_retrieve_response_code( $response ); $traced .= '<li>HTTP code: ' . $http_code . ', URL: ' . $headers['location'] . '</li>'; } } } else { echo 'Error: ' . $response->get_error_message(); wp_die(); } $traced .= '</ul>'; set_transient( $transient_name, $traced, HOUR_IN_SECONDS ); } echo $traced; wp_die(); } add_action( 'wp_ajax_traceredirects', 'traceredirects' ); add_action( 'wp_ajax_nopriv_traceredirects', 'traceredirects' );
问题分析
- 自动跟随重定向导致丢失中间记录:
wp_remote_get的redirection参数设为10时,WordPress会自动跟随所有重定向,直接返回最终响应,无法获取中间重定向的header信息。 - Location头大小写不兼容:部分站点返回
Location(大写开头)而非location,原代码仅检查小写键,导致无法识别重定向地址。 - 逻辑漏洞:循环中未处理最终响应的状态,且输出格式不符合期望,同时未处理相对路径的Location地址。
修复后的代码
function traceredirects() { check_ajax_referer( 'traceredirects_nonce', 'nonce' ); $url = $_POST['url']; $valid_url = filter_var($url, FILTER_VALIDATE_URL); if (!$valid_url) { echo '<strong>Error:</strong> 请输入包含http://或https://的有效URL'; wp_die(); } $host = parse_url($url, PHP_URL_HOST); if(!checkdnsrr($host, 'ANY')) { echo '<strong>Error:</strong> 无法找到域名 ' . $host; wp_die(); } $transient_name = md5($url) . '_traceredirect'; // 用md5避免URL特殊字符导致的键名问题 $traced = get_transient( $transient_name ); if (false === $traced) { $args = array( 'redirection' => 0, // 禁止自动跟随,手动处理每一步重定向 'timeout' => 10, ); $redirect_chain = array(); $current_url = $url; $max_redirects = 10; $redirect_count = 0; $has_error = false; // 记录原始URL $redirect_chain[] = array( 'url' => $current_url, 'code' => null, 'type' => 'origin' ); while ($redirect_count < $max_redirects) { $response = wp_remote_get( $current_url, $args ); if ( is_wp_error( $response ) ) { $traced = '<ul><li><strong>Error:</strong> ' . $response->get_error_message() . '</li></ul>'; $has_error = true; break; } $http_code = wp_remote_retrieve_response_code( $response ); $headers = wp_remote_retrieve_headers( $response ); // 统一将header键转为小写,解决大小写不一致问题 $lowercase_headers = array_change_key_case( (array)$headers, CASE_LOWER ); $location = isset($lowercase_headers['location']) ? $lowercase_headers['location'] : null; // 处理相对路径的Location地址 if ($location && !filter_var($location, FILTER_VALIDATE_URL)) { $current_parsed = parse_url($current_url); $location = $current_parsed['scheme'] . '://' . $current_parsed['host'] . rtrim($current_parsed['path'], '/') . '/' . ltrim($location, '/'); } if ($http_code >= 300 && $http_code < 400 && $location) { // 记录重定向步骤 $redirect_chain[] = array( 'url' => $location, 'code' => $http_code, 'type' => 'redirect' ); // 检测重定向循环 if (in_array($location, array_column($redirect_chain, 'url'))) { $traced = '<ul><li><strong>Error:</strong> 检测到重定向循环</li></ul>'; $has_error = true; break; } $current_url = $location; $redirect_count++; } else { // 记录最终目的地 $redirect_chain[] = array( 'url' => $current_url, 'code' => $http_code, 'type' => 'destination' ); break; } } if (!$has_error) { $traced = '<ul>'; foreach ($redirect_chain as $item) { $url_html = '<a href="' . esc_url($item['url']) . '" rel="nofollow noreferrer">' . esc_html($item['url']) . '</a>'; switch ($item['type']) { case 'origin': $traced .= '<li>Origin: ' . $url_html . '</li>'; break; case 'redirect': $traced .= '<li>Redirect (' . $item['code'] . '): ' . $url_html . '</li>'; break; case 'destination': $traced .= '<li>Destination (' . $item['code'] . '): ' . $url_html . '</li>'; break; } } $traced .= '</ul>'; } set_transient( $transient_name, $traced, HOUR_IN_SECONDS ); } echo $traced; wp_die(); } add_action( 'wp_ajax_traceredirects', 'traceredirects' ); add_action( 'wp_ajax_nopriv_traceredirects', 'traceredirects' );
修复说明
- 将
redirection参数设为0,禁止自动跟随重定向,确保能获取每一步的响应头信息。 - 使用
array_change_key_case统一处理header键的大小写,解决Location头大小写不一致问题。 - 增加相对路径转绝对URL的逻辑,避免无法识别相对地址的重定向。
- 重构重定向链记录逻辑,生成完全符合期望格式的HTML输出。
- 用md5处理transient名称,避免URL中特殊字符导致的键名异常。
内容的提问来源于stack exchange,提问作者douglaskarr
相关产品推荐
相关产品推荐

