web-dev-qa-db-ja.com

純粋なPHPでHTTPリダイレクトを実行した後に最終的なURLを取得するにはどうすればよいですか?

私がやりたいのはリダイレクトをたどった後の最後/最後のURLを見つけることです

cURLは使いたくないです。純粋なPHP(ストリームラッパー))に固執したいと思います。

現在、私はURLを持っており(たとえば http://domain.test )、get_headers()を使用してそのページから特定のヘッダーを取得しています。 get_headersは、複数のLocation:ヘッダーも返します(以下のEditを参照)。これらのヘッダーを使用して最終的なURLを作成する方法はありますか?または、これを自動的に行うPHP関数はありますか?

編集:get_headers()はリダイレクトに従い、各応答/リダイレクトのすべてのヘッダーを返すため、すべてのLocation:ヘッダーがあります。

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

33
xaav
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になります。ただし、配列ではないことに注意することが重要です常に。場合によっては、それは単なるありふれた非配列変数です。この場合、最後の配列要素にアクセスしようとすると、ほとんどの場合1文字が返されます。理想的ではありません。

最終的なURLのみに関心がある場合は、すべてのリダイレクトの後、変更することをお勧めします

return $headers['Location'];

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

...これは 速記の場合 for

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

この修正により、いずれかの場合(配列、非配列)が処理され、関数の呼び出し後に最終的なURLを削除する必要がなくなります。

リダイレクトがない場合、関数はfalseを返します。同様に、この関数は無効なURL(何らかの理由で無効)に対してもfalseを返します。そのため、 RLの有効性を確認するbeforeこの関数を実行するか、リダイレクトチェックを検証のどこかに組み込むことが重要です。

44
webjay

OPは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ヘッダーが混在しています。

4
Paul Dixon

xaav 答えはとても良いです。次の2つの問題を除いて:

  • HTTPSプロトコルをサポートしていません=>ソリューションは元のサイトのコメントとして提案されました: http://w-shadow.com/blog/2008/07/05/how-to-get-redirect- url-in-php /
  • 一部のサイトは、基盤となるユーザーエージェント(クライアントブラウザ)を認識しないため、機能しません=>これは、ユーザーエージェントヘッダーフィールドを追加することで簡単に修正されます: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、Geckoなど)バージョン/4.0モバイル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;
}
3
Houssem BDIOUI

回答@xaavおよび@HoussemBDIOUIからのコードに追加:404エラーの場合とURLが応答なしの場合。その場合、get_final_url($url)は文字列を返します: 'エラー:404が見つかりません'および 'エラー:応答なし'。

/**
 * 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;
    }
}
0
mature