HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

短縮URLの展開処理(PHP)の改版

古き良き時代は終わりを告げ、Webサイトへの接続はHTTP over SSL/TLSが当たり前になった。

hhelibex.hatenablog.jp

hhelibex.hatenablog.jp

そんなわけで、久々に引っ張り出した短縮URL展開プログラムのPHP版が、このままじゃダメだ!という状況になっていたので改版。

<?php
class ShortURL {
    private static $services = null;
    private static function initServices() {
        if (!is_null(self::$services)) {
            return;
        }
        self::$services = array();
        self::$services[] = parse_url("http://t.co/");
        self::$services[] = parse_url("https://t.co/");
        self::$services[] = parse_url("http://bit.ly/");
        self::$services[] = parse_url("https://bit.ly/");
        self::$services[] = parse_url("http://goo.gl/");
        self::$services[] = parse_url("https://goo.gl/");
        self::$services[] = parse_url("http://tinyurl.com/");
        self::$services[] = parse_url("https://tinyurl.com/");
        self::$services[] = parse_url("http://tiny.one/");
        self::$services[] = parse_url("https://tiny.one/");
        self::$services[] = parse_url("http://htn.to/");
        self::$services[] = parse_url("https://htn.to/");
    }

    public static function expand($shortUrl) {
        self::initServices();

        $parsed = parse_url($shortUrl);
        if (!$parsed) {
            return $shortUrl;
        }
        $isTarget = false;
        foreach (self::$services as $service) {
            if ($service['scheme'] === $parsed['scheme']
                    && $service['host'] === $parsed['host']) {
                $isTarget = true;
                break;
            }
        }
        if (!$isTarget) {
            return $shortUrl;
        }

        $defaultPort = $service['scheme'] === 'https' ? 443 : 80;
        $host = ($service['scheme'] === 'https'
                    ? 'ssl://' : '') . $parsed['host'];
        $port = isset($parsed['port']) && $parsed['port'] > 0
                    ? $parsed['port'] : $defaultPort;
        $sock = fsockopen($host, $port);
        if (!$sock) {
            throw new Exception($shortUrl . ': Connection failed.');
        }

        fwrite($sock, "GET {$parsed['path']} HTTP/1.1\r\n");
        fwrite($sock, "Host: {$parsed['host']}\r\n");
        fwrite($sock, "Accept: */*\r\n");
        fwrite($sock, "\r\n");
        $returnUrl = null;
        while (($header = fgets($sock))) {
            $header = trim($header);
            if (!$header) {
                break;
            }
            if (preg_match("/^Location: /i", $header)) {
                $returnUrl = preg_replace("/^Location: /i", "", $header);
            }
        }
        fclose($sock);
        if ($returnUrl) {
            return $returnUrl;
        } else {
            return $shortUrl;
        }
    }
}

ポイントは、HTTP over SSL/TLSの際には「デフォルトポート番号が443」ということと、「fsockopenを使うときは "ssl://" を前に付ける必要がある」ということかしら。 後は、Locationヘッダを全て小文字で返してくる輩もいたので、その対応も追加(preg_match/preg_replaceの "i" オプションの指定)。

呼び出しのサンプル。 これに適当にHTMLコードを付け加えてあげると、最大100段まで展開・表示してくれます。

<pre><?php
if (isset($_POST['url'])) {
    include('./ShortURL.php');
    $url = $_POST['url'];
    try {
        printf("%s\r\n", htmlspecialchars($url));
        $indent = "┗";
        for ($i = 0; $i < 100 && ($newUrl = ShortURL::expand($url)) !== $url; ++$i) {
            printf("%s\r\n", $indent . htmlspecialchars($newUrl));
            $indent = " " . $indent;
            $url = $newUrl;
        }
    } catch (Exception $e) {
        echo htmlspecialchars($e->getMessage());
    }
}
?></pre>

実際に動くものをサンプルコードと合わせて提示するサイトを作りたいと思いながら幾星霜・・・