JSONP で広告を供給するサーバーを書く


最近の話ですが、このサイトにも広告を載せるようになりました。

Google Adsense の方はタグ貼るだけで終わりの簡単なものなのですが、縁あってレバレジーズ株式会社の広告を載せることになり、これはこのサイトでバナーを供給しています。

このサイトは MovableType + DynamicMTML で運用していますので、普通に考えれば PHP で直接広告タグを書けばいいだけなのですが、サーバー負荷の観点からいまいちだし、第一余りおもしろくありません。

そこで、広告供給サーバーを別に立て、JSONP で供給するようにしてみました。

JSONP とは

JSONP とは“JSON with Padding”の略で……と書くよりも Wikipedia 見てもらった方が確実ですね。

JSONP – Wikipedia
http://ja.wikipedia.org/wiki/JSONP

JSONP(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 を実行すると次のような感じで処理されます。(実際の処理を簡略化しています。)

  1. URL の最後の ? がランダムな文字列に置き換えられ、サーバーにリクエストされる。
    http://img.remora.cx/get?callback=jQuery12345_67890
    
  2. サーバーは JSON を生成し、それを callback パラメータの文字列で包んで返す。
    jQuery12345_67890({"content":"<p>aiueo!</p>"});
    
  3. クライアントは getJSON() の第三パラメータ(success())を上記ランダム文字列の変数に代入している。
    var jQuery12345_567890 = function(data){ $('#aaa').html(data.content); };
    
  4. サーバーから持ち帰った JSON を元にして <script> タグが生成、実行される。
    // こんな HTML がページに挿入される
    <script>
    var jQuery12345_67890 = function(data){ $('#aaa').html(data.content); };
    jQuery12345_67890({"content":"<p>aiueo!</p>"});
    </script>
    

形としては外部のサーバーから読み込んだスクリプトを実行しているだけなのに、処理自体はリクエストに従ってサーバーで動的に作られるのです。うまいこと考えたもんですね。

丁度この文に下に現れているのが広告です。うまく表示されているかな?

コメントを残す