Entry: ある意味、世界一のURL短縮サービスを作った
ある意味、世界一のURL短縮サービスを作った
[2009.09.30] Asiajinで取り上げていただきました。ありがとうございます。そして、同じネタがすでにあったと知りました。リサーチ不足。
長いURLの文字数を削減してくれるURL短縮サービスはたくさんあるけど、世界中のどの方式よりも文字数を短縮できる方法を発明しました。
サイト名ですでにネタばれですが、これは各URLに割り振るIDをマルチバイト文字の組み合わせにすることで、文字数的には短いでしょ、というネタです。ついったーなんかは完全に文字数に依存なので有効かも。
IDに使用する文字は日本の常用漢字1945字です。シーケンシャルに割り振り、使い切ったら繰り上がるので、最初の370万件は2桁に収まり、最初の73億件も3桁に収まります。
注意事項としては
- Firefox3.5, IE7, Chrome, iPhoneで正常動作を確認しました。逆に、Androidエミュレータ, DoCoMoやAUのブラウザ(フルブラウザ含む)は日本語ドメインに未対応のようです。
- IDはシーケンシャルに割り振られるので使用済みのIDは容易に推測できます。
- 「悪」などというイヤなIDが当たってしまったら
リロードして再割当してください。「縮AGAIN」ボタンを押してください。 - これはジョークサービスです。本サービスの利用に起因または関連する利用者の損害について管理者は何ら責任を負いません。
Entry: 縮.jpに外国人向けの機能をいくつかつけた
Entry: 国際化ドメイン名をHTMLに記述する際の記述方法
国際化ドメイン名をHTMLに記述する際の記述方法
国際化ドメイン名をHTMLに記述する際の記述方法に関するメモ。間違いあったら指摘して。
(1) <a href="http://%E7%B8%AE.jp/">http://縮.jp/</a> (誤り) (2) <a href="http://縮.jp/">http://縮.jp/</a> (3) <a href="http://縮.jp/">http://縮.jp/</a> (4) <a href="http://xn--jj0a.jp/">http://縮.jp/</a>
(1)パーセントエンコード。誤り。
(2)マルチバイト文字を直接記述する。一番正しくて手軽な記述。問題点は、UAがIDNに対応していないため通信時に正しくpunycode変換されないかもしれないこと。
(3)マルチバイト文字を数値文字参照で記述する。敢えてこう記述するメリットはあまりないけど、例えばマルチバイト文字を扱えないシステムとの相互運用を想定しているためにHTML内に日本語を直接記述したくない場合には、このように記述して構わない。
(4)punycode変換して記述する。リンクを正しく動作させるという点では一番安全かもしれない記述。ただし、エンドユーザーが知りうるURLが全てデコードされるかどうかはブラウザの実装次第。例えば「リンクをコピー」したときにpunycodeのまま出力されてしまってはIDNの利点が台無し。punycode変換はUAが通信時に行うことが推奨されているため、HTMLに記述する時点で行う必要はない。
Entry: 縮.jpに待望の「縮AGAIN」ボタンを搭載
縮.jpに待望の「縮AGAIN」ボタンを搭載
縮.jp。7月に作って放置してたら10月になって話題になった。人生で一番集客した。そんな訳で、ネタを必要以上に膨らませるべく、システムを改修しました。見た目的にはあまり変化ありませんが、中身はほぼ丸々変わってます。特筆すべき変化は。。
待望の「縮AGAIN」ボタンを搭載
生成された短縮URLが気に入らなかった場合、このボタンを押すと別のURLを生成してくれます。なお、縮AGAINした場合、古いURLはしばらくして解放されます。
既設の短縮URLがあれば、短い順にご提案
今までは湯水のように新規作成してましたが、縮ボタンを押した際、まずは既設の短縮URLを提示するようになりました。縮AGEINすると短い順に既設URLが表示され、なくなれば新規作成されます。
転送時、末尾のゴミを可能な限り取り除く
Twitterでhttp://縮.jp/上これすげーみたいなつぶやきが多発していたので、こういう場合はhttp://縮.jp/上を検知して転送します。Perlでいうと、
$id =~ s/¥P{Han}.*//;
となっています。漢字を表す正規表現なんて初めて知りました。
ちなみに、ファイルベースからPostgresqlに移行
パフォーマンスいいかなと思って、1件1ファイルなどというデータ管理をしてたんですが、IDがシーケンシャルに固定されるとか、逆引きできないとか、色々問題あったのでPostgresqlにしました。なお、ファイルベースのキャッシュの仕組みを導入したので転送時のパフォーマンスは以前と変わらない。はず。
APIにも変更あり
offsetというパラメータが新設されました。これは前述の既設URLの再利用と関連するもので、任意の既設URLを取り出すためのパラメータです。offsetを十分に大きくすると自動的に新規作成されます。通常は指定しないでください。
既知の問題
- 元URLがbit.ly等ですでに短縮済みだった場合、iPhoneで転送されない。確かソフトバンクの仕様でiPhoneで多段リダイレクトできない。
- Twitter用のいくつかのGreaseMonkeyスクリプトやアドオンで縮.jpへ飛べない。FirefoxのJavascriptのバグっぽい挙動が原因と思われる。対処法はこちら
- Twitter周辺サービスで日本語ドメインが誤ってパーセントエンコードされているケースも見かけます。サーバサイドでの処理に問題があるのではないか(憶測)。
- iPhoneの多くのアプリで開けない。おそらくアプリからwebkitだかのAPI的なものに渡すURLをpunycode変換してないのではないか(憶測)。対処法はこちらの12月15日の記事くらいしか見当たらなかった。
今後の予定
任意のIDを指定可能に- 不人気文字を避ける仕組み
- 元URLがすでに短縮URLだった場合に展開してから短縮
- スパム対策
Entry: 開発小ネタその2(改)
開発小ネタその2(改)
開発小ネタ集その2のネタ、なんか違和感あると思ったらゼロがなかったんだ。という訳で書き直した。66進数。縮.jpは差し詰め、1945進数か。
use strict; use warnings; ### 文字リスト our @ascii = qw( 0 1 2 3 4 5 6 7 8 9 a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z _ + - = ); my $num1 = str2num("0"); ### 0 my $num2 = str2num("="); ### 65 my $num3 = str2num("10"); ### 66 my $num4 = str2num("A359"); ### 10363263 my $str = num2str(10363263); ### A359 ### -------------- ### 文字列を数値に変換 ### -------------- sub str2num { my $num = 0; my @id_array = split(//, $_[0]); @id_array = reverse(@id_array); for (my $i = 0; $i < scalar @id_array; $i++) { for (my $j = 0; $j < scalar @ascii; $j++) { if ($ascii[$j] eq $id_array[$i]) { $num += (scalar @ascii ** $i) * $j; last; } if ($j == scalar @ascii - 1) { return; } } } return $num; } ### -------------- ### 数値を文字列に変換 ### -------------- sub num2str { my $sho = int($_[0] / scalar @ascii); my $upper = ($sho > 0) ? &num2str($sho) : ""; my $current = $ascii[($_[0] % scalar @ascii)]; return $upper. $current; }
Entry: GreaseMonkey用pbtweetでIDNを正常動作させる
GreaseMonkey用pbtweetでIDNを正常動作させる
縮.jpがFirefoxのGreaseMonkyスクリプト「pbtweet」のv1.4.10 GreaseMonkey 005810で動作しなかったので、pbtweet側を直してみた。
[2009.10.12追記] 本家に取り込んで頂いたのでこちらから最新版をDLするといいと思います。ちなみに、同様の現象はTwitter本家の検索結果でも起きていて、pbtweetの最新版を使うとこれを回避できます。
231-236行に下記を追加。
//jamadam added below var links = entry[i] .getElementsByClassName('entry-content')[0] .getElementsByTagName('a'); for (var cnt = 0; cnt < links.length; cnt++) { links[cnt].href=decodeURI(links[cnt].href); } // jamadam added above
うちだけの問題かもしれないけど、Mac版Firefox3.5.3のJavascriptでinnerHTMLを取得した場合、子要素のaタグのhref属性が、パーセントエンコードされてしまっている。上記ではこの現象をキャンセルしている。
HTML
<body id="main"> <a title="縮" href="http://縮.jp/">http://縮.jp/</a> </body>
Javascript
var hoge1 = document.getElementById('main').innerHTML; var hoge2 = document.getElementById('main').getElementByTagName('a')[0].href; alert(hoge1); alert(hoge2);
Firefoxの場合
<a title="縮" href="http://%E7%B8%AE.jp/">http://縮.jp/</a> http://縮.jp/
Safariの場合
<a title="縮" href="http://縮.jp/">http://縮.jp/</a> http://縮.jp/
Entry: 縮 REST API
縮 REST API
「縮 REST API」は、URL短縮/転送サービス「縮.jp」の機能を、アプリケーションから利用するためのインターフェースを提供します。
全てのAPIは下記のURL以下で提供されます。
http://api.xn--jj0a.jp/
現在サポートされている操作は、短縮URLの生成(generate.jsonへのPOST)と短縮URLの展開(lookup.jsonへのGET)です。いずれもレスポンスとしてJSONを返します。また、クエリーとしてcallbackを渡すことでJSONPを受け取ることもできるかもしれません。
GENERATE
任意のURLを渡すことで、新規に短縮URLを生成します。
パラメータ
- fUrl(必須)
短縮対象のURL。http://かhttps://で始まる必要があります。 - customId(オプション)
任意の漢字の組み合わせで短縮URLの識別子を指定します。utf8でパーセントエンコードしてください。 - offset(オプション)
fUrlで指定したURLに対する既設の短縮URLがひとつ以上存在する場合、このパラメータに0以上の数値を指定することで任意の候補を選択することができます。既設URLは文字数の短い順に並んでいます。offsetが既設URLの数より大きいとき、新しいURLが生成されます。このパラメータは特殊な目的がない場合は指定しないでください。
リクエスト例
POST /generate.json
fUrl=http://example.com&id=%E4%B8%83
レスポンス例
{"result":{"fUrl":"http://example.com/","generated":"http://縮.jp/七"}}
LOOKUP
既存の短縮URLの識別子を受け取り、元のURLを返します。識別子はUTF-8でパーセントエンコードしてください。
パラメータ
- id(必須)
URLの識別子を指定します。 - hash
後方互換性のための、idの別名です。(廃止の予定)
リクエスト
GET /lookup.json?id=%E4%B8%83
レスポンス
{"result":{"fUrl":"http://example.com"}}
共通
HTTPレスポンスコード
正常/エラーに関わらず「200 OK」を返します。
エラー時のレスポンスボディ
下記の書式でJSONデータを返します。
{"error":"[エラーコード] [説明]"}
エラーコード一覧
- 400: generateの際、fUrlの書式が正しくない、customIdに不正な文字が含まれる、など。
- 409: generateの際、customIdがすでに使用されている、など。
- 404: lookupの際、指定のidが存在しない、など。
実装例
generate.jsonを利用して短縮URLを取得するPerlコードは以下のようになります。
use LWP::UserAgent; use JSON::XS; my $generated = _shuku('http://example.com'); sub _shuku { my $ua = LWP::UserAgent->new; my $res = $ua->post('http://api.xn--jj0a.jp/generate.json', {'fUrl' => $_[0]}); my $obj = decode_json $res->content(); my $decoded = $obj->{result}->{generated}; utf8::decode($decoded); return $decoded; }
Entry: 縮.jpがカスタムID機能を搭載!
Entry: judeのMacOSX版
Entry: 縮.jpのバグを直した
縮.jpのバグを直した
先日、縮.jpを国際化ドメイン対応にした際にパス名がぶった切られてたのに気づいたので直した。そして、せっかくなので国際化ドメインへの対応方法について、ちらしの裏的メモ。
Locationヘッダにutf8のバイト列を流し込んでもうまく行かなかったのでpunycodeに変換した。
use Net::IDN::Punycode; my $url = 'http://例示.日本/path/'; my $fixed_url = ''; my ($schema, $domain, $uri) = ($url =~ m!(https?)://([^/]+)(.+)!); foreach my $part (split(/¥./, $domain)) { if ($part =~ /¥P{IsASCII}/) { $fixed_url .= 'xn--'. encode_punycode($part). '.'; } else { $fixed_url .= $part. '.'; } } chop($fixed_url); printf("Location: %s://%s¥n¥n", $schema, $fixed_url. $uri);
cpanでいくつかモジュールが見つかるけど、Net::IDN::Punycodeが正解。IDNA::Punycodeは古いから利用率高いけどバグもの。
上記の結果は
http://xn--fsq192h.xn--wgv71a/path/
ちなみに国際化ドメインと国際化URI(って呼んでいいのか?)への対処方法は全く別で、さらに国際化URIは国際化ファイル名と国際化クエリーに対処しないとならない気がするので、その辺を今度整理してみよう。
Subscribe to my RSS feed
