Как получить окончательный URL после следующих http-перенаправлений в чистом PHP?

то, что я хотел бы сделать, это узнайте, что является последним / окончательным URL-адресом после перенаправления.

Я бы предпочел не использовать cURL. Я хотел бы придерживаться чистого PHP (stream wrappers).

прямо сейчас у меня есть URL (скажем http://domain.тест), и я использую get_headers () для получения определенных заголовков с этой страницы. get_headers также вернет несколько Location: заголовки (см. редактировать ниже.) Есть ли способ использовать эти заголовки для создания окончательного URL-адреса? или есть функция PHP, которая автоматически сделает это?

Edit: get_headers () следует за перенаправлениями и возвращает все заголовки для каждого ответа/перенаправления, поэтому у меня есть все Location: заголовки.

4 ответов


/**
 * 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/


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'] будет ваш окончательный URL после всех перенаправлений. Важно отметить, однако, что это не всегда быть массивом. Иногда это просто заурядная переменная без массива. В этом случае попытка доступа к последнему элементу массива, скорее всего, вернет один символ. Не идеальный.

если вы только интересует конечный URL, после всех переадресаций, я бы предложил изменить

return $headers['Location'];

to

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

... что просто если короче-руки на

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

это исправление позаботится о любом случае (массив, не массив) и устранит необходимость отсеять окончательный URL-адрес после вызова функции.

в случае, когда нет перенаправления, функция будет возвращать false. Аналогично, функция также будет возвращение false для недопустимых url (недопустимый по любой причине). Поэтому важно проверьте URL на действительность до запуск этой функции или включение проверки перенаправления где-то в вашу проверку.


xaav ответ очень хороший, за исключением следующих двух вопросов:

  • он не поддерживает протокол HTTPS => решение было предложено в качестве комментария на исходном сайте:http://w-shadow.com/blog/2008/07/05/how-to-get-redirect-url-in-php/
  • некоторые сайты не будут работать, так как они не распознают базовый агент пользователя (браузер клиента) => Это просто исправлено путем добавления поля заголовка User-agent: я добавил Android User agent (вы можете найти здесь http://www.useragentstring.com/pages/useragentstring.php другие примеры агента потребителя согласовывая вас ваша потребность):

    $запрос .= "User-Agent: Mozilla / 5.0 (Linux; U; Android 4.0.3; ko-kr; LG-L160l Build/IML74K) AppleWebkit/534.30 (KHTML, как и Gecko) версия/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;
}

в то время как 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 запроса, со смесью Location и location заголовки участвует.