WebOS Goodies

WebOS の未来を模索する、ゲームプログラマあがりの Web 開発者のブログ。

WebOS Goodies へようこそ! WebOS はインターネットの未来形。あらゆる Web サイトが繋がり、共有し、協力して創り上げる、ひとつの巨大な情報システムです。そこでは、あらゆる情報がネットワーク上に蓄積され、我々はいつでも、どこからでも、多彩なデバイスを使ってそれらにアクセスできます。 WebOS Goodies は、さまざまな情報提供やツール開発を通して、そんな世界の実現に少しでも貢献するべく活動していきます。
Subscribe       

CachedProxy : Web 上のファイルを簡単に自サーバーにキャッシュする

最近はさまざまな方法で画像を生成してくれるサービスが出てきましたね。例えば Web ページのサムネイルを生成してくれる SimpleAPI さんとか、 LaTeX で書いた数式を画像化してくれる sitmo.com さんとか。これらのサービスの多くは生成した画像にユニークな URL を割り振るので HTML に直接埋め込むことができますが、それはいろいろな面で好ましくありません。サービスを提供するサーバーに負荷がかかりますし、サービスが停止するとこちらまで影響を受けてしまいます。

かといって、画像をダウンロードして Web サーバーにアップロードしなおすのは、手間もかかるし管理も面倒。せっかく Web で直アクセスできるんだから、 IMG タグを埋め込むだけで表示したいところですよね。ということで、それを実現する CGI を Ruby で書いてみました。特定の URL にアクセスすると元ファイルをキャッシュして表示するのですが、さらにちょっと工夫して、一度キャッシュした後は CGI を起動せずにアクセスできるようにしました。自サーバーの負荷も最小限に抑えることができます。

1 日そこらで作った即席 CGI なので、まだまだ荒削りですが、ぜひ使ってみてください。

動作原理

まず最初に、動作原理を簡単にご紹介しておきます。以下に CachedProxy の動作を図示してみました。

CachedProxy の概念図

※上図で使った PC などの画像は Open Clip Art Library から拝借しました。いつも助かります(^^ゞ

まずクライアントは、自サーバー内のキャッシュファイルに対してリクエストを送りますが、それが初回アクセスの場合はキャッシュファイルが存在しないので、代わりに CachedProxy の CGI が起動されます。すると CachedProxy はキャッシュ対象ファイルを Web からフェッチして、既定の場所に保存すると同時にそのファイルをレスポンスとして返します。

2 回目以降のアクセスでは、すでにキャッシュファイルが存在するため、 Web サーバーはそれを直アクセスしてクライアントにレスポンスを返します。 CGI は一切起動されません。 2 回目以降は非常に単純なプロセスでリクエストが処理されることがわかっていただけるかと思います。

問題はどのようにして 1 回目のアクセスで CGI を起動するかですが、それには Apache の ErrorDocument ディレクティブを使います。これはリクエストでエラーが発生した際に表示される代替コンテンツを指定する機能で、 404 Not Found などのページをカスタマイズするためによく使われます。実はこの ErrorDocument ディレクティブに指定する代替コンテンツとしては、静的な HTML 以外に CGI スクリプトも指定できます。これにより、エラー発生時に任意の処理を実行できます。

ここまでくれば簡単ですね。 ErrorDocument ディレクティブを使って、 404 Not Found が発生した際に CachedProxy が起動されるように設定すればよいわけです。 CachedProxy は対象ファイルをフェッチして、リクエスト URL に該当する場所に保存します。すると、 2 回目以降はファイルが存在するために 404 Not Found は発生せず、保存されたファイルがそのまま返されます。以上が CachedProxy の仕組みです。

動作環境

基本的に以下の環境を想定して作成してあります。

  • Apache 2.0
  • Ruby 1.8
  • Unix 系の OS

ただし、まだ私の使っている XREA のサーバーでしか試していないので、他の環境ではうまく動かないこともあるかと思います。その際は、ぜひフォーラムまたはコメントでご報告いただければと思います。各ソフトウェアのバージョンに加えて、後述するデバッグモードを使って(支障ない範囲で)環境変数の値を教えていただけると助かります。

インストール方法

大変申し訳ありませんが、現在のところインストールは手作業で、少々手間がかかります。といっても、基本的には以下の 3 つの作業を行うだけですので、難しいことはありません。

  • スクリプトを設定し、 Web サーバーにアップロードする。
  • キャッシュファイルを保存するためのディレクトリを作成する。
  • ErrorDocument ディレクティブを記述した .htaccess ファイルを作成する。

以下、順を追ってご説明します。

スクリプトの設定

まずはこちらにあるスクリプトをローカルに保存し(文字コードは UTF-8 にしてください)、先頭にある以下の定数を適切に変更してください。

定数名 内容
PASSWORD キャッシュの生成に必要なパスワード
CACHE_PATH キャッシュディレクトリのフルパス
CACHE_URL キャッシュディレクトリの URL のホスト以下の部分

デフォルトで "http://ホスト名/cache" をキャッシュディレクトリとする設定になっていますので、その "/cache" の部分を適切に変更すればよいでしょう。その下にもいくつか設定がありますが、とりあえず変更なしでやってみてください。さらなるカスタマイズやトラブル時の対処などは後述します。

スクリプトが適切に設定できたら、 Web サーバーの適当な場所にアップロードし、実行権限などを付与して CGI として実行可能な状態にしてください。スクリプトはキャッシュディレクトリとは別の場所に設置する必要があります。キャッシュディレクトリ下に置いてしまうと、キャッシュ生成時にスクリプト自身を上書きされる可能性が(僅かながらも)あるからです。

キャッシュディレクトリの準備

次はキャッシュディレクトリを作成し、適切に設定しましょう。まずは上記の CACHE_PATH で指定したディレクトリを作成し、 CGI スクリプトが書き込み可能な状態にしておきます。そして直下に以下の内容で ".htaccess" を作成してください。

ErrorDocument 404 [CGIスクリプトのURLのローカルパス部分]

例えば、 CGI スクリプトが "http://ホスト名/cgi/cachedproxy.cgi" ならば、内容はこうなります。

ErrorDocument 404 /cgi/cachedproxy.cgi

これで、キャッシュディレクトリ内で 404 Not Found が発生した際に CGI スクリプトが実行されるようになります。

以上でインストールは完了です。

使い方

それでは実際に使ってみましょう。以下では説明の便宜上、スクリプトとキャッシュディレクトリの URL がそれぞれ以下のようになっているものとして話を進めていきます。

スクリプト : http://cache.webos-goodies.jp/cgi/cachedproxy.cgi
キャッシュ : http://cache.webos-goodies.jp/cache

各自の環境に合わせて、読み替えてください。

パスワードを Cookie に記録する

第三者が勝手にファイルをキャッシュするのを防ぐため、 Cookie を使った簡単な認証を行うようになっています。そのため、まずはブラウザの Cookie にパスワードを記録しましょう。あらかじめ該当ドメインで Cookie の保存を許可しておいてください。

設定方法は、ブラウザで普通にスクリプト(http://cache.webos-goodies.jp/cgi/cachedproxy.cgi) にアクセスし、表示されるフォームで(ハッシュをかける前の)パスワードを送信するだけです。「パスワードを設定しました」と表示されれば成功です。複数のマシン・ブラウザを使っている場合は、個々に上記の作業を繰り返す必要があります。少々面倒ですが、ご勘弁ください。

ファイルをキャッシュする

さて、いよいよ本番、実際にファイルをキャッシュしてみましょう。例として、 Flickr にある以下のファイルをキャッシュすることにします。

東京アメッシュガジェット

ファイルをキャッシュするには、以下の手順でキャッシュファイルの URL を作成し、 Web ブラウザでそこにアクセスします。

  1. キャッシュ対象の URL から先頭の "http://" を取り除く。
  2. キャッシュディレクトリの URL の末尾に、 (1) の文字列を付け足す。

今回の例では、キャッシュディレクトリが "http://cache.webos-goodies.jp/cache/" 、キャッシュ対象ファイルが "http://farm2.static.flickr.com/1333/935778630_6e62f2760d_o.jpg" ですから、アクセスすべき URL は以下になります。

http://cache.webos-goodies.jp/cache/farm2.static.flickr.com/1333/935778630_6e62f2760d_o.jpg

ブラウザのアドレスバーに上記 URL を入力して、該当する画像がきちんと表示されればキャッシュ成功です。 Web サーバーを覗いてみれば、ちょうどこの URL の場所に画像ファイルが作成されているのが確認できると思います。これ以降に上記の URL にアクセスすると、単純にこのキャッシュファイルが読み出され、 CGI は実行されません。静的なファイルとまったく同じ負荷でアクセスできるわけです。

前述のとおり、キャッシュは IMG タグなどによるアクセスでも生成されます。例えば、以下のようなタグを含む Web ページを表示すれば、該当するキャッシュが生成されます。

<img src="http://cache.webos-goodies.jp/cache/farm2.static.flickr.com/1333/935778630_6e62f2760d_o.jpg" />

つまり、単純にキャッシュファイルを表示する Web ページを書くだけで、すべてのキャッシュが生成できるのです。きちんと画像が表示されればキャッシュ成功です。お手軽でしょ?(w

高度な使い方

以上のように Flickr のファイルは比較的単純に利用できるのですが、場合によっては少し工夫が必要なこともあります。以下に、そういった高度な使い方の事例をご紹介します。

記号を含む URL をキャッシュする

CachedProxy は基本的にキャッシュ対象の URL をそのままキャッシュファイル名として使うため、 URL として使える文字は英数字と一部の記号のみに制限されています(詳細は WHITE_LIST の正規表現をご覧ください)。もしキャッシュしたい URL が許可されていない文字を含んでいる場合は、 URL エスケープを二重にかけてください。例えば、以下の URL をキャッシュしたい場合。

http://hoge.com/fuga:fuga/image.png

「使い方」の例で使った環境の場合、単純に考えるとキャッシュファイルの URL は以下のようになります。

http://cache.webos-goodies.jp/cache/hoge.com/fuga:fuga/image.png

しかし、 ":" は許容されていないので、エラーになってしまいます。こういうときは、以下のように URL エスケープを 2 回かけます。

http://cache.webos-goodies.jp/cache/hoge.com/fuga%253Afuga/image.png

これで正常にキャッシュできるはずです。ポイントを以下にまとめておきます。

  • 二重エスケープする必要があるのはキャッシュ元 URL に由来する部分だけで、前半のキャッシュディレクトリパスの部分は普通に指定してください。
  • 実際に保存されるキャッシュファイルの名前は 1 回 unescape されたものになります。
  • キャッシュ元のファイルをフェッチするのに使う URL は、 2 回 unescape したもの(つまり元の URL)が使われます。
  • WHITE_LIST などのチェックは 1 回 unescape したもの(つまり保存ファイル名)に対して適用されます。

実際には UNIX 系 OS は大抵の文字がファイル名として使えるので、現在の制限は少し厳しすぎるかもしれません。このあたりは詳細に検討して決めたわけではないので、必要に応じてカスタマイズしてください。ただし、あくまで自己責任でお願いします。

許容されていない拡張子の URL をキャッシュする

CGI などで自動生成されるようなファイルの場合、 URL に適切な拡張子がついていない場合があります。例えば SimpleAPI さんで生成したサムネイル URL の拡張子は元ページの URL と同じになるので、 ".gif" になっていることはまずありません(別にこの仕様を批判しているわけではないですよ)。

このようなファイルをキャッシュする場合は、キャッシュ URL の最後に "=" に続けて正しい拡張子を付加してください。例えば「使い方」の環境で "http://img.simpleapi.net/small/http://webos-goodies.jp/index.html" をキャッシュするなら、 URL は以下のようになります(読み易いように 2 行に分けました)。

http://cache.webos-goodies.jp/cache/img.simpleapi.net/small/
  http%253A%252F%252Fwebos-goodies.jp/index.html=.gif

"://" を二重エスケープするのを忘れないでください。こうすれば、元ファイルをフェッチする際には "=.gif" を削除した URL を使うので、目当てのファイルを正しくキャッシュできます。保存時のファイル名は "=.gif" が付いたものになるので、 Web サーバーがファイルタイプを判別し損なうこともないでしょう。なお、この場合でも mime-type が許可されていないとキャッシュに失敗するのでご注意ください。

キャッシュできるファイルタイプを増やす

デフォルトの設定では、キャッシュ可能なファイル形式は静的な画像ファイル(JPEG, PNG, GIF)のみに限定されています。安全性を優先した結果なのですが、もちろん必要に応じて他の形式をキャッシュすることも可能です。ここでは、例として PDF をキャッシュできるように設定してみましょう。

ファイル形式の制限は、主に拡張子と mime-type によって行われています。まずは許容する拡張子を追加しましょう。スクリプト先頭で定義している定数 WHITE_LIST の最初の正規表現を、以下のように書き換えます。

WHITE_LIST = [
  /\.(?:jpg|jpeg|png|gif|pdf)$/, # 許可する拡張子

これで拡張子が .pdf のファイルをキャッシュ可能になりました。しかし、これだけでは mime-type のチェックで弾かれてしまいますので、 ALLOW_TYPE に "application/pdf" にマッチする正規表現を追加します。

ALLOW_TYPE = [ /^image\/(?:jpeg|png|gif)$/, /^application\/pdf$/ ]

これで PDF ファイルのキャッシュが可能になったはずです。他の形式も同様に追加できるはずなので、必要に応じて変更してください。ただし、セキュリティーに関してはじゅうぶんご注意ください。とくに (X)HTML, XML, SWF といったスクリプト実行が可能な形式をキャッシュする際は、専用にドメインを用意することをお勧めします。

既知の問題

なにせ一日ででっちあげたスクリプトなので、いろいろと制限があります。とりあえず私が把握しているものを挙げると・・・

  • キャッシュしたファイルの管理機能などが一切ない。
  • 元ファイルの変更を反映するには、キャッシュファイルを手動で削除して再度キャッシュしなければならない。
  • "http:" 以外のプロトコル("https:" や "ftp:" など)はキャッシュできない。
  • 原理上、ユニークな URL を持たないファイル(POST でパラメータを受け渡す場合など)はキャッシュできない。

というところでしょうか。すでに個人的なニーズは満たしているのでこんな仕様なのですが、ご要望などありましたらお聞かせください。

トラブルシューティング

前述のとおり、 CachedProxy は XREA のサーバーでしか動作確認していないので、他の環境ではうまく動かないこともあると思います。不具合が発生したときに役に立つであろう情報を以下に記載しておきます。

デバッグモードについて

スクリプトの先頭で定義されている定数 "ENABLE_DEBUG" に true を設定するとデバッグモードになり、キャッシュに失敗した際のエラーページに失敗の原因と CGI に渡された環境変数が表示されるようになっています。また、デバッグモードでは元ファイルのフェッチは行うもののキャッシュファイルは保存しません。もしキャッシュに失敗する場合は、試しにデバッグモードで実行してみてください。

Apache のバージョンによる問題?

CachedProxy の機能は Apache のカスタムエラーレスポンスの機能によって実現されています。しかし、 XREA で試した限り、設定される環境変数がドキュメントとだいぶ食い違っているようでした。とくに重要なのはエラーが発生した URL を受け渡すための環境変数なのです。ドキュメントでは "REDIRECT_URL" で渡されるとなっていますが、実際には "REQUEST_URI" を参照する必要がありました。

単に私が勘違いしているだけかもしれませんが、 Apache のバージョンによって違う可能性もあるのかなと思っています。もし上記のデバッグモードで表示される環境変数を見て、 "REDIRECT_URI" 以外の変数にリダイレクト元の URL が格納されているようなら、スクリプト先頭で定義されている定数 "REDIRECT_URI" に正しい URL を設定するように変更してください。そして、できれば Apache のバージョンと正しい環境変数名をご報告いただけると助かります。

タイムアウトに関して

レンタル Web サーバーでは、 CGI スクリプトの実行時間などが制限されている場合があります。例えば XREA では 30 秒を経過すると CGI プロセスが強制終了されてしまうようです。このようなサーバーでは、ファイルサイズが大きい場合などにタイムアウトでキャッシュに失敗する可能性があります。

こればかりはスクリプト側での対処は困難ですので、あまり大きなファイルはキャッシュしないようにしてください。

ご意見・バグ報告などはフォーラムまで

CachedProxy に関するご意見やご要望・バグ報告などは、以下のフォーラムにお願いします。この記事にコメントいただいてもかまいませんが、更新のお知らせなどもフォーラム経由で行いますので、ぜひご登録をお願いします。

Google グループ Beta
WebOS Goodiesに参加
メール アドレス:

以上、本日は Web 上の公開ファイルをキャッシュする CGI スクリプト「CachedProxy」をご紹介しました。 Web 上のツールを使って生成した画像などを手軽に気兼ねなく使う手段として、ご活用いただければと思います。また、このように ErrorDocument を利用して CGI を起動する方法は、他にもいろいろと応用が効きそうな気がします。これを利用した新しいサービスを考えてみるのも、なかなか面白いのではないでしょうか。

関連記事

この記事にコメントする

Recommendations
Books
「Closure Library」の入門書です。
詳しくはこちらの記事をどうぞ!
Categories
Recent Articles