Sunday, January 9, 2011

AJPR.js: 非同期JSONPリクエスト用ライブラリ

JSONPを簡単にリクエストできるライブラリを作ってみました。

AjaxとJSONP

Ajaxの利点と欠点

数年前から大流行のAjax。サーバーから動的かつ非同期的にデータを取得してページに反映するための重要な手法です。

さてこのAjaxもブラウザ間で書き方が異なったりして複雑でしたが、Prototype.jsなどのフレームワークを使えば以下のように簡単に書くことができるようになりました。

HTML
<p id="myfield1"></p>
<p id="myfield2"></p>

<script type="text/javascript" src="prototype.js"></script>
<script type="text/javascript">
    var myfield1 = $('myfield1');
    var myfield2 = $('myfield2');
    myfield1.update('読み込んでいます...');
    // Ajaxリクエスト
    Ajax.request( 'file.json', {
        // 成功した時
        onSuccess: function(request){
            // 返ってきたJSONテキストをパース
            var json = eval('('+ request.responseText +')');
            // myfieldの内容を更新
            myfield1.update( json.item1 );
            myfield2.update( json.item2.join());
        },
        // 失敗した時
        onFailure: function(){
            myfield1.update('読み込みに失敗しました。');
        }
    });
</script>
JSONファイル
{
    "item1": "Lorem Ipsum Dolor",
    "item2": [
        "Sit",
        "Amet"
    ]
}

これだけ。簡単ですね。

しかしAjaxにも欠点があり、Javascriptのセキュリティー制限から「異なるドメインからはデータを取得できない(同一生成元ポリシー)」ようになっています。

たとえばRSSを取得して表示するためには、そのRSSファイルを同じドメインに設置するか、CGIプロキシなどを介してアクセスしなければなりません。

その解決策の一つとして挙げられるのがJSONPです。

JSONP

異なるドメイン上のデータを取得するのにAjaxは使えないため、代替案として<iframe><script>から読み込むことが挙げられます。

このうちJSONPでは後者の<script>での読み込みが用いられます。まず具体例を見てみましょう。

<p id="myfield"></p>

<script type="text/javascript">
    var myfield = document.getElementById('myfield');
    function respond(response){
        myfield.innerHTML = response;
    }
</script>
<script type="text/javascript" src="jsonp.js" async="async"></script>
JSONPファイル
respond('Lorem Ipsum Dolor');

あれ、これだけ?と思われるかもしれません。見慣れたファイル読み込み、関数呼び出しがあるだけですね。しかし<script>での読み込みはドメインの制限がなく、また目的とするデータ渡しも完璧に行われており、まさにJSONPの目指すものが実現できています。

* <script>に指定したasync属性はファイルの非同期読み込みを指示するものです。これが指定されていないと同期的読み込みになるため、ファイルの読み込み・解析・実行が完了するまでこの行以降のHTML解析・レンダリングが停止(ブロック)されてしまい、ページの表示が遅くなってしまいます。ただし現在はまだ互換性が完全ではなく、モダンブラウザ以外(IEなど)はこの属性を無視します。このあたりの非同期の性質、重要性はまた後日別記事で書きましょう。

JSONPの欠点

欠点というか実装上の注意点なのですが、たとえばWikipediaの当該記事にもあるように、気を付けて実装しないとCSRFによる脆弱性の原因にもなり得るわけです。Ajaxのセキュリティー制限は不便な反面安全性を確保するために規定されたものなので、その垣根を超えてアクセス可能にする以上はそれなりに細心の注意を払わなければなりません。

AJPR.js

今回はこのJSONPを安全に利用し、非同期でロードするライブラリを作成しました。複数ファイルのロードにも対応しています。

こんな感じです

* サンプルで読み込んでいるJSONPはサーバー側で意図的に読み込みを2秒遅延させています。

ライブラリのダウンロードはこちら
  • ajpr.js (右クリックで保存してください)
使い方

あらかじめAJPR.jsを読み込んでおきます。

次に、必要なときにAJPRequestを生成します。

HTML
<script type="text/javascript">
    // 非同期JSONPリクエスト
    AJPRequest( './path/to/jsonp/resource.php', function(resource){
        // 読み込み後の処理
        // 第一引数にJSONデータが渡されます。
        var value1 = resource.item1[0];
        var value2 = resource.item2.key1;
        var value3 = resource.item3(2,4);
    });
</script>

これだけ。簡単ですね。

読み込むJSONデータは、URIに自動的に付加されるランダムなリクエスト識別子を含めたデータを返す必要があります。具体的にはPHPなどを使って以下のように処理してください。

PHP
<?php
    $id = $_GET['RequestID'];
    if( ! preg_match( '/^AJPR[0-9]+$/u', $id )) die( 'Invalid request ID.' );
    header('Content-type: text/javascript; charset=utf-8');
?>

AJPRequest.responder( "<?php echo $id; ?>", (

    {
        item1: [
            'lorem',
            'ipsum',
            'dolor'
        ],
        item2: {
            key1: 'foo',
            key2: 'bar',
            key3: 'baz'
        },
        item3: function(a,b){
            return a+b;
        }
    }

));

* もしPHPなどを設置できないときは、AJPRequestの呼び出しで第3引数に任意の値を与えることでリクエスト識別子を固定できます。その際は上記PHPの<?php echo $id; ?>の部分をその値に置き換えた静的な.jsファイルを設置してください。

デモ
HTML
<p id="myfield1"></p>
<p id="myfield2"></p>

<script type="text/javascript" src="ajpr.js"></script>
<script type="text/javascript">
    var myfield1 = document.getElementById('myfield1');
    var myfield2 = document.getElementById('myfield2');
    myfield1.innerHTML = '読み込んでいます...';
    // 非同期JSONPリクエスト
    AJPRequest( 'sample.php', function(json){
        myfield1.innerHTML = json.item1;
        myfield2.innerHTML = json.item2.join();
    });
</script>
PHP
<?php
    $id = $_GET['RequestID'];
    if( ! preg_match( '/^AJPR[0-9]+$/u', $id )) die( 'Invalid request ID.' );
    header('Content-type: text/javascript; charset=utf-8');
?>

AJPRequest.responder( "<?php echo $id; ?>", (
    {
        item1: 'Lorem Ipsum Dolor',
        item2: [
            "Sit",
            "Amet"
        ]
    }
));
ご利用について

AJPR.jsはとってもユルいMITライセンスで公開しています。

ご自由にお使いください。

No comments:

Post a Comment