【问题标题】:How to get final URL after following HTTP redirections in pure PHP?在纯 PHP 中遵循 HTTP 重定向后如何获取最终 URL?
【发布时间】:2011-04-17 11:33:11
【问题描述】:

我想做的是找出跟随重定向后的最后/最终 URL 是什么

我不想使用 cURL。我想坚持使用纯 PHP(流包装器)。

现在我有一个 URL(比如说http://domain.test),我使用 get_headers() 从该页面获取特定的标题。 get_headers 还将返回多个 Location: 标头(请参阅下面的编辑)。有没有办法使用这些标头来构建最终 URL?还是有一个 PHP 函数会自动执行此操作?

编辑: get_headers() 遵循重定向并返回每个响应/重定向的所有标头,因此我拥有所有 Location: 标头。

【问题讨论】:

  • 单个响应中有 多个 Location: 标头?
  • get_headers 确实默认自动遵循重定向,所以我得到多个 Location: 标头。我想要的是完整的最终 URL (domain.test/final/page.ext?attr...)
  • 我没看懂问题:(
  • 尝试将 HTTP 流上下文中的 max_redirects 设置为 1 (php.net/manual/en/context.http.php)。这应该禁用自动重定向,您可以自己跟踪重定向。
  • @Webolde:位置标头被定义为绝对 URI,但现在每个人和他们的狗都使用相对 URI;因此,如果它是绝对 URI,则只需重定向,如果不是,则获取上一页的域 + 路径,然后从中生成新的绝对 URI 和 Location 字段。

标签: php http http-headers


【解决方案1】:
function getRedirectUrl ($url) {
    stream_context_set_default(array(
        'http' => array(
            'method' => 'HEAD'
        )
    ));
    $headers = get_headers($url, 1);
    if ($headers !== false && isset($headers['Location'])) {
        return $headers['Location'];
    }
    return false;
}

另外...

正如评论中提到的,$headers['Location'] 中的 final 项将是您在所有重定向后的最终 URL。不过,重要的是要注意,它不会总是 是一个数组。有时它只是一个普通的非数组变量。在这种情况下,尝试访问最后一个数组元素很可能会返回一个字符。不理想。

如果您只对最终 URL 感兴趣,在所有重定向之后,我建议您更改

return $headers['Location'];

return is_array($headers['Location']) ? array_pop($headers['Location']) : $headers['Location'];

...这只是if short-hand

if(is_array($headers['Location'])){
     return array_pop($headers['Location']);
}else{
     return $headers['Location'];
}

此修复将处理任何一种情况(数组、非数组),并且无需在调用函数后清除最终 URL。

在没有重定向的情况下,该函数将返回false。同样,该函数还将为无效的 URL 返回false(由于任何原因无效)。因此,重要的是check the URL for validity在运行此函数之前,否则将重定向检查合并到您的验证中。

【讨论】:

  • 这是否遵循所有重定向并返回最终 URL?
  • 伟大的 yar.. 节省了很多时间。 +1
  • 此问题的标记答案未遵循所有重定向。但这个解决方案做到了。
  • 对此答案的注意事项。 get_headers 认为 locationLocation 是不同的标题。如果您正在跟踪使用不同情况的一系列重定向,则无法分辨哪个标头是“最后一个”标头,因为您将在 $headers['location']$headers['Location'] 中都有网址
  • 虽然它可以正常工作,但请注意在您的代码中使用此随机数,因为stream_context_set_default 任何以下文件操作(如file_get_contents)都会失败。一种解决方法是使用 stream_context_get_default 缓冲原始上下文,并在完成后将其重置回原来的上下文。
【解决方案2】:
/**
 * get_redirect_url()
 * Gets the address that the provided URL redirects to,
 * or FALSE if there's no redirect. 
 *
 * @param string $url
 * @return string
 */
function get_redirect_url($url){
    $redirect_url = null; 

    $url_parts = @parse_url($url);
    if (!$url_parts) return false;
    if (!isset($url_parts['host'])) return false; //can't process relative URLs
    if (!isset($url_parts['path'])) $url_parts['path'] = '/';

    $sock = fsockopen($url_parts['host'], (isset($url_parts['port']) ? (int)$url_parts['port'] : 80), $errno, $errstr, 30);
    if (!$sock) return false;

    $request = "HEAD " . $url_parts['path'] . (isset($url_parts['query']) ? '?'.$url_parts['query'] : '') . " HTTP/1.1\r\n"; 
    $request .= 'Host: ' . $url_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)){
        if ( substr($matches[1], 0, 1) == "/" )
            return $url_parts['scheme'] . "://" . $url_parts['host'] . trim($matches[1]);
        else
            return trim($matches[1]);

    } else {
        return false;
    }

}

/**
 * get_all_redirects()
 * Follows and collects all redirects, in order, for the given URL. 
 *
 * @param string $url
 * @return array
 */
function get_all_redirects($url){
    $redirects = array();
    while ($newurl = get_redirect_url($url)){
        if (in_array($newurl, $redirects)){
            break;
        }
        $redirects[] = $newurl;
        $url = $newurl;
    }
    return $redirects;
}

/**
 * get_final_url()
 * Gets the address that the URL ultimately leads to. 
 * Returns $url itself if it isn't a redirect.
 *
 * @param string $url
 * @return string
 */
function get_final_url($url){
    $redirects = get_all_redirects($url);
    if (count($redirects)>0){
        return array_pop($redirects);
    } else {
        return $url;
    }
}

并且一如既往地给予信任:

http://w-shadow.com/blog/2008/07/05/how-to-get-redirect-url-in-php/

【讨论】:

  • 您先生,刚刚节省了我几个小时的搜索时间。一切都按预期工作。
  • 我不得不说,对于我的测试,这个带有 CURL 的解决方案更可靠:stackoverflow.com/questions/17472329/…
  • 我看到 fsockopen 设置为端口 80,这不支持 SSL/HTTPS 443 端口重定向吗?
【解决方案3】:

虽然 OP 想要避免使用 cURL,但最好在可用时使用它。这是一个具有以下优点的解决方案

  • 使用 curl 完成所有繁重的工作,因此可以使用 https
  • 处理返回小写 location 标头名称的服务器(xaav 和 webjay 的答案都不能处理此问题)
  • 让您在放弃前控制自己想要走多远

函数如下:

function findUltimateDestination($url, $maxRequests = 10)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
    curl_setopt($ch, CURLOPT_MAXREDIRS, $maxRequests);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);

    //customize user agent if you desire...
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Link Checker)');

    curl_setopt($ch, CURLOPT_URL, $url);
    curl_exec($ch);

    $url=curl_getinfo($ch, CURLINFO_EFFECTIVE_URL);

    curl_close ($ch);
    return $url;
}

这是一个更详细的版本,它允许您检查重定向链而不是让 curl 跟随它。

function findUltimateDestination($url, $maxRequests = 10)
{
    $ch = curl_init();

    curl_setopt($ch, CURLOPT_HEADER, true);
    curl_setopt($ch, CURLOPT_NOBODY, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);

    //customize user agent if you desire...
    curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Link Checker)');

    while ($maxRequests--) {

        //fetch
        curl_setopt($ch, CURLOPT_URL, $url);
        $response = curl_exec($ch);

        //try to determine redirection url
        $location = '';
        if (in_array(curl_getinfo($ch, CURLINFO_HTTP_CODE), [301, 302, 303, 307, 308])) {
            if (preg_match('/Location:(.*)/i', $response, $match)) {
                $location = trim($match[1]);
            }
        }

        if (empty($location)) {
            //we've reached the end of the chain...
            return $url;
        }

        //build next url
        if ($location[0] == '/') {
            $u = parse_url($url);
            $url = $u['scheme'] . '://' . $u['host'];
            if (isset($u['port'])) {
                $url .= ':' . $u['port'];
            }
            $url .= $location;
        } else {
            $url = $location;
        }
    }

    return null;
}

作为这个函数处理的重定向链示例,但其他函数不处理,试试这个:

echo findUltimateDestination('http://dx.doi.org/10.1016/j.infsof.2016.05.005')

在撰写本文时,这涉及 4 个请求,其中混合了 Locationlocation 标头。

【讨论】:

  • 第二个功能对我有用。谢谢
【解决方案4】:

xaav回答很好;除了以下两个问题:

  • 不支持 HTTPS 协议 => 解决方案是在原站评论中提出的:http://w-shadow.com/blog/2008/07/05/how-to-get-redirect-url-in-php/

  • 某些网站无法运行,因为它们无法识别底层用户代理(客户端浏览器) => 这可以通过添加 User-agent 标头字段来解决:我添加了一个 Android 用户代理(您可以在这里找到 http://www.useragentstring.com/pages/useragentstring.php 其他用户代理示例,根据您的需要):

    $request .= "User-Agent: Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari /534.30\r\n";

这是修改后的答案:

/**
 * get_redirect_url()
 * Gets the address that the provided URL redirects to,
 * or FALSE if there's no redirect. 
 *
 * @param string $url
 * @return string
 */
function get_redirect_url($url){
    $redirect_url = null; 

    $url_parts = @parse_url($url);
    if (!$url_parts) return false;
    if (!isset($url_parts['host'])) return false; //can't process relative URLs
    if (!isset($url_parts['path'])) $url_parts['path'] = '/';

    $sock = fsockopen($url_parts['host'], (isset($url_parts['port']) ? (int)$url_parts['port'] : 80), $errno, $errstr, 30);
    if (!$sock) return false;

    $request = "HEAD " . $url_parts['path'] . (isset($url_parts['query']) ? '?'.$url_parts['query'] : '') . " HTTP/1.1\r\n"; 
    $request .= 'Host: ' . $url_parts['host'] . "\r\n"; 
    $request .= "User-Agent: Mozilla/5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160L Build/IML74K) AppleWebkit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30\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)){
        if ( substr($matches[1], 0, 1) == "/" )
            return $url_parts['scheme'] . "://" . $url_parts['host'] . trim($matches[1]);
        else
            return trim($matches[1]);

    } else {
        return false;
    }

}

/**
 * get_all_redirects()
 * Follows and collects all redirects, in order, for the given URL. 
 *
 * @param string $url
 * @return array
 */
function get_all_redirects($url){
    $redirects = array();
    while ($newurl = get_redirect_url($url)){
        if (in_array($newurl, $redirects)){
            break;
        }
        $redirects[] = $newurl;
        $url = $newurl;
    }
    return $redirects;
}

/**
 * get_final_url()
 * Gets the address that the URL ultimately leads to. 
 * Returns $url itself if it isn't a redirect.
 *
 * @param string $url
 * @return string
 */
function get_final_url($url){
    $redirects = get_all_redirects($url);
    if (count($redirects)>0){
        return array_pop($redirects);
    } else {
        return $url;
    }
}

【讨论】:

  • 执行此脚本时出现错误 500。
  • 在第三行到最后一行的 else 条件之后需要一个 }
  • 该死的没有一个答案显示超过 1 个重定向 :( 甚至是 curl 的那些
【解决方案5】:

添加到来自答案@xaav 和@Houssem BDIOUI 的代码中:404 错误案例和 URL 无响应时的案例。 get_final_url($url) 在这种情况下返回字符串:'Error: 404 Not Found' 和 'Error: No Responce'。

/**
 * get_redirect_url()
 * Gets the address that the provided URL redirects to,
 * or FALSE if there's no redirect,
 * or 'Error: No Responce',
 * or 'Error: 404 Not Found'
 *
 * @param string $url
 * @return string
 */
function get_redirect_url($url)
{
    $redirect_url = null;

    $url_parts = @parse_url($url);
    if (!$url_parts)
        return false;
    if (!isset($url_parts['host']))
        return false; //can't process relative URLs
    if (!isset($url_parts['path']))
        $url_parts['path'] = '/';

    $sock = @fsockopen($url_parts['host'], (isset($url_parts['port']) ? (int)$url_parts['port'] : 80), $errno, $errstr, 30);
    if (!$sock) return 'Error: No Responce';

    $request = "HEAD " . $url_parts['path'] . (isset($url_parts['query']) ? '?' . $url_parts['query'] : '') . " HTTP/1.1\r\n";
    $request .= 'Host: ' . $url_parts['host'] . "\r\n";
    $request .= "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36\r\n";
    $request .= "Connection: Close\r\n\r\n";
    fwrite($sock, $request);
    $response = '';
    while (!feof($sock))
        $response .= fread($sock, 8192);
    fclose($sock);

    if (stripos($response, '404 Not Found') !== false)
    {
        return 'Error: 404 Not Found';
    }

    if (preg_match('/^Location: (.+?)$/m', $response, $matches))
    {
        if (substr($matches[1], 0, 1) == "/")
            return $url_parts['scheme'] . "://" . $url_parts['host'] . trim($matches[1]);
        else
            return trim($matches[1]);

    } else
    {
        return false;
    }

}

/**
 * get_all_redirects()
 * Follows and collects all redirects, in order, for the given URL.
 *
 * @param string $url
 * @return array
 */
function get_all_redirects($url)
{
    $redirects = array();
    while ($newurl = get_redirect_url($url))
    {
        if (in_array($newurl, $redirects))
        {
            break;
        }
        $redirects[] = $newurl;
        $url = $newurl;
    }
    return $redirects;
}

/**
 * get_final_url()
 * Gets the address that the URL ultimately leads to.
 * Returns $url itself if it isn't a redirect,
 * or 'Error: No Responce'
 * or 'Error: 404 Not Found',
 *
 * @param string $url
 * @return string
 */
function get_final_url($url)
{
    $redirects = get_all_redirects($url);
    if (count($redirects) > 0)
    {
        return array_pop($redirects);
    } else
    {
        return $url;
    }
}

【讨论】:

    【解决方案6】:

    在阅读 Stackoverflow 数小时并尝试人们编写的所有自定义函数以及尝试所有 cURL 建议后,只做了 1 次重定向,我设法做了一个我自己的逻辑,它有效。

    $url = 'facebook.com';
    // First let's find out if we just typed the domain name alone or we prepended with a protocol 
    if (preg_match('/(http|https):\/\/[a-z0-9]+[a-z0-9_\/]*/',$url)) {
        $url = $url;
    } else {
        $url = 'http://' . $url;
        echo '<p>No protocol given, defaulting to http://';
    }
    // Let's print out the initial URL
    echo '<p>Initial URL: ' . $url . '</p>';
    // Prepare the HEAD method when we send the request
    stream_context_set_default(array('http' => array('method' => 'HEAD')));
    // Probe for headers
    $headers = get_headers($url, 1);
    // If there is a Location header, trigger logic
    if (isset($headers['Location'])) {
        // If there is more than 1 redirect, Location will be array
        if (is_array($headers['Location'])) {
            // If that's the case, we are interested in the last element of the array (thus the last Location)
            echo '<p>Redirected URL: ' . $headers['Location'][array_key_last($headers['Location'])] . '</p>';
            $url = $headers['Location'][array_key_last($headers['Location'])];
        } else {
            // If it's not an array, it means there is only 1 redirect
            //var_dump($headers['Location']);
            echo '<p>Redirected URL: ' . $headers['Location'] . '</p>';
            $url = $headers['Location'];
        }
    } else {
        echo '<p>URL: ' . $url . '</p>';
    }
    // You can now send get_headers to the latest location
    $headers = get_headers($url, 1);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-08
      • 1970-01-01
      • 2011-03-05
      • 1970-01-01
      • 2022-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多