読者です 読者をやめる 読者になる 読者になる

HHeLiBeXの日記 正道編

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

短縮URLの展開処理を各言語で書いてみた

Java PHP Node.js

別にぜんぜんなんてことはないのだけど、なんとなく(謎)。
展開処理とは言っても、リクエストを出してLocationヘッダの内容を取っているだけだけど。

Java

java.net.HttpURLConnectionクラスを使用して、こんな感じで。

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public class ShortURL {

    private static final Set<URL> services;
    static {
        try {
            Set<URL> tmp = new HashSet<URL>();
            tmp.add(new URL("http://t.co/"));
            tmp.add(new URL("http://bit.ly/"));
            tmp.add(new URL("http://goo.gl/"));
            services = Collections.unmodifiableSet(tmp);
        } catch (MalformedURLException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    public static URL expand(URL shortUrl) throws IOException {
        boolean isTarget = false;
        for (URL service : services) {
            if (service.getProtocol().equals(shortUrl.getProtocol())
                    && service.getHost().equals(shortUrl.getHost())) {
                isTarget = true;
                break;
            }
        }
        if (!isTarget) {
            return shortUrl;
        }

        HttpURLConnection conn = (HttpURLConnection) shortUrl.openConnection();
        try {
            conn.setInstanceFollowRedirects(false);
            conn.setRequestMethod("HEAD");
            conn.setDoOutput(false);
            int statusCode = conn.getResponseCode();
            if (statusCode == 301) {
                String tmpUrlStr = conn.getHeaderField("Location");
                return new URL(tmpUrlStr);
            } else {
                return shortUrl;
            }
        } finally {
            conn.disconnect();
        }
    }
}

呼び出しのサンプル。

try {
    String urlStr = "http://t.co/vXCdwgSB";
    URL url = ShortURL.expand(new URL(urlStr));
    System.out.println(urlStr);
    System.out.println("    => " + url);
} catch (MalformedURLException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

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("http://bit.ly/");
        self::$services[] = parse_url("http://goo.gl/");
    }

    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;
        }

        $port = isset($parsed['port']) && $parsed['port'] > 0
                    ? $parsed['port'] : 80;
        $sock = fsockopen($parsed['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: /", $header)) {
                $returnUrl = preg_replace("/^Location: /", "", $header);
            }
        }
        fclose($sock);
        if ($returnUrl) {
            return $returnUrl;
        } else {
            return $shortUrl;
        }
    }
}

呼び出しのサンプル。

$urlStr = 'http://bit.ly/sopihq';
printf("%s\n", $urlStr);
printf("    => %s\n", ShortURL::expand($urlStr));

JavaScript(Node.js)

ネットワーク通信が発生するので、コールバック関数を指定する以下のような形で書いてみたけど、これでいいのかな(謎)。

var http = require('http');
var url = require('url');

var services = Array(
    url.parse('http://t.co/'),
    url.parse('http://bit.ly/'),
    url.parse('http://goo.gl/')
);

module.exports = {
    expand:
        function(urlStr, callback) {
            var parsed = url.parse(urlStr);
            var isTarget = false;
            for (var i in services) {
                if (services[i].protocol == parsed.protocol
                        && services[i].hostname == parsed.hostname) {
                    isTarget = true;
                    break;
                }
            }
            if (!isTarget) {
                callback(urlStr);
            }
            var target = {
                host: parsed.hostname,
                port: parsed.port,
                path: parsed.pathname + (parsed.search || '')};
            http.get(target, function(_res) {
                if (_res.statusCode == 301) {
                    callback(_res.headers.location);
                } else {
                    callback(urlStr);
                }
            });
        },
};

呼び出しのサンプル。(xxx.xxx.xxx.xxx は環境に合わせたIPアドレスに、yyyy はポート番号にそれぞれ書き換え)

var http = require('http');
var ShortURL = require('./ShortURL');

http.createServer(function (req, res) {
    var urlStr = 'http://goo.gl/r1yBJ';
    ShortURL.expand(urlStr, function(expanded) {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.write(urlStr + '\n');
        res.write('    => ' + expanded + '\n');
        res.end();
    });
}).listen(yyyy, "xxx.xxx.xxx.xxx");
console.log('Server running at http://xxx.xxx.xxx.xxx:yyyy/');

(おまけ)シェルスクリプト

curlコマンドを使って。

function short_url_expand() {
    local url=$1

    return $(curl -s -i ${url} | grep '^Location: ' | sed -e 's/^Location: //')
}