最近の話ですが、このサイトにも広告を載せるようになりました。
Google Adsense の方はタグ貼るだけで終わりの簡単なものなのですが、縁あってレバレジーズ株式会社の広告を載せることになり、これはこのサイトでバナーを供給しています。
このサイトは MovableType + DynamicMTML で運用していますので、普通に考えれば PHP で直接広告タグを書けばいいだけなのですが、サーバー負荷の観点からいまいちだし、第一余りおもしろくありません。
そこで、広告供給サーバーを別に立て、JSONP で供給するようにしてみました。
JSONP とは
JSONP とは“JSON with Padding”の略で……と書くよりも Wikipedia 見てもらった方が確実ですね。
JSONP – Wikipedia
http://ja.wikipedia.org/wiki/JSONPJSONP(JSON with padding)とは、scriptタグを使用してクロスドメインなデータを取得する仕組みのことである。
今回作った画像供給サーバーは img.remora.cx
というドメインにあり、このブログのドメイン blog.delphinus.dev
とは別の場所にあります。
広告は Ajax を使って動的に取得したいのですが、同一生成元ポリシーにより別ドメインへの XMLHttpRequest は許可されていません。
ここが危ない!Web2.0のセキュリティ:第2回 Same-Originポリシーと迂回技術|gihyo.jp … 技術評論社
http://gihyo.jp/dev/serial/01/web20sec/0002
これを回避するにはリバースプロクシを使ったり Flash を使ったり……という方法があるのですが、この中で一番簡単に実装できそうなのが、JSONP を使った方法です。
ソースコード
と言うわけで、書いたコードはこれ。
delphinus35/BannerServer · GitHub
https://github.com/delphinus35/BannerServer
サーバー部分
実装には Amon2::Lite
を使っています。長々と書いてありますが、簡単にすると次のようになるでしょうか。
use Amon2::Lite; get '/get' => sub { my $c = shift; my $p = $c->req->parameters; # HTML を作成して JSON 化 my $html = $c->create_view->render('img.tt', $p); my $js = to_json(+{content => $html}); # callback が指定してあればそれでラップ $js = "$p->{callback}($js);" if defined $p->{callback}; return $c->create_response( 200, [ 'Content-Type' => 'application/json', 'Content-Length' => length $js, ], $js, ); }; get '/img/{filename}' => sub { my ($c, $args) = @_; # $args->{filename} にファイル名が入っているので、 # その内容を $content に読む return $c->create_response( 200, [ 'Content-Type' => "image/$ext", 'Content-Length' => -s $args->{filename}, ], $content, ); }; __DATA__ @@ img.tt <a target="_blank" href="[% path %]"> <img src="http://img.example.com[% uri_for('/img/' _ filename) %]"> </a>
クライアント部分
これに対してクライアント部分は次のようになります。今回作成したもののうち、ポイントとなる部分を抜き出すと次のようになります。
$.getJSON('http://img.remora.cx/get?callback=?', {}, function(data) { $this.html(data.content); } );
ここでは記述を簡単にするため、jQuery の getJSON()
メソッドを使っています。
jQuery.getJSON() – jQuery API
http://api.jquery.com/jQuery.getJSON/jQuery.getJSON( url [, data] [, success(data, textStatus, jqXHR)] )
url: リクエストが送られる URL
data: リクエストパラメータ
success(data, textStatus, jqXHR): 成功時に実行される関数
このメソッド自体は単にサーバーから JSON を得るだけなのですが、URL に ?callback=?
という文字列を付加すると JSONP が使えるようになります。
上に書いた Javascript を実行すると次のような感じで処理されます。(実際の処理を簡略化しています。)
- URL の最後の
?
がランダムな文字列に置き換えられ、サーバーにリクエストされる。http://img.remora.cx/get?callback=jQuery12345_67890
- サーバーは JSON を生成し、それを
callback
パラメータの文字列で包んで返す。jQuery12345_67890({"content":"<p>aiueo!</p>"});
- クライアントは
getJSON()
の第三パラメータ(success()
)を上記ランダム文字列の変数に代入している。var jQuery12345_567890 = function(data){ $('#aaa').html(data.content); };
- サーバーから持ち帰った JSON を元にして
<script>
タグが生成、実行される。// こんな HTML がページに挿入される <script> var jQuery12345_67890 = function(data){ $('#aaa').html(data.content); }; jQuery12345_67890({"content":"<p>aiueo!</p>"}); </script>
形としては外部のサーバーから読み込んだスクリプトを実行しているだけなのに、処理自体はリクエストに従ってサーバーで動的に作られるのです。うまいこと考えたもんですね。
丁度この文に下に現れているのが広告です。うまく表示されているかな?