jQuery - CORS:クロスドメインなAjaxでJSON / JSONPの各種ブラウザ対応まとめ

忘れてしまうのでまとめておく。

  • 基本的に jsonp であれば動く
  • json + モダンブラウザは、Access-Control-Allow-Originを設定すれば動く
  • json + IE8,9では、XDomainRequestを使えば動く
  • IE6,7は死亡(もういいよね)
  • プロトコル(http|https) / FQDN / ポート番号、いずれかが違う場合、クロスドメイン
  • BASIC認証があると、別途対応が必要(後述)

サーバサイド

jsonを返すサーバ側にAccess-Control-Allow-Originを設定する。

<?php
    header('Access-Control-Allow-Origin: *');
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');

    // CakePHPの場合
    $this->header('Access-Control-Allow-Origin: *');
    $this->header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept');

BASIC認証をしている場合

クレデンシャルを許可するといけるが、Originにワイルドカードは指定できない。

<?php
    header('Access-Control-Allow-Origin: http://example.com');
    header('Access-Allow-Control-Credentials: true');
    header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization');

クライアントサイド

jQuery版

// IE8,9でXMLHttpRequestの代わりにXDomainRequestを使う
(function( jQuery ) {

if ( window.XDomainRequest ) {
    jQuery.ajaxTransport(function( s ) {
        if ( s.crossDomain && s.async ) {
            if ( s.timeout ) {
                s.xdrTimeout = s.timeout;
                delete s.timeout;
            }
            var xdr;
            return {
                send: function( _, complete ) {
                    function callback( status, statusText, responses, responseHeaders ) {
                        xdr.onload = xdr.onerror = xdr.ontimeout = xdr.onprogress = jQuery.noop;
                        xdr = undefined;
                        complete( status, statusText, responses, responseHeaders );
                    }
                    xdr = new XDomainRequest();
                    xdr.open( s.type, s.url );
                    xdr.onload = function() {
                        callback( 200, "OK", { text: xdr.responseText }, "Content-Type: " + xdr.contentType );
                    };
                    xdr.onerror = function() {
                        callback( 404, "Not Found" );
                    };
                    xdr.onprogress = function() {};
                    if ( s.xdrTimeout ) {
                        xdr.ontimeout = function() {
                            callback( 0, "timeout" );
                        };
                        xdr.timeout = s.xdrTimeout;
                    }
                    xdr.send( ( s.hasContent && s.data ) || null );
                },
                abort: function() {
                    if ( xdr ) {
                        xdr.onerror = jQuery.noop();
                        xdr.abort();
                    }
                }
            };
        }
    });
}
})( jQuery );

jQuery.getJSON('http://example.com/search.json',
    {
        'hoge': 'piyo'
    },
    function (data) {
        console.log(data);
    }
});

参照 : https://github.com/tlianza/ajaxHooks/blob/master/src/ajax/xdr.js

JavaScript版

function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest();
    if ("withCredentials" in xhr) {
        xhr.open(method, url, true);
    } else if (typeof XDomainRequest != "undefined") {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    } else {
        xhr = null;
    }
    return xhr;
}

var xhr = createCORSRequest('GET', 'http://example.com/search.json');
if (!xhr) {
    throw new Error('CORS not supported');
}
xhr.onload = function(){};
xhr.onerror = function(){};
xhr.send();

参照 : http://www.html5rocks.com/en/tutorials/cors/

jsonpの場合、サーバサイドではjsonp形式で返すだけで特に設定は必要ない。ただし、jsonと違いJavaScriptのコードをクライアントサイドで直接実行するため、jsonpの提供APIに悪意のあるコードが含まれていた場合は恐ろしいことになる。callbackで指定できるパラメータを制限するなど、ある程度の工夫が必要。

クライアントサイド

jQuery版

jQuery.getJSON('http://example.com/search.json?callback=?',
    {
        'hoge': 'piyo'
    },
    function (data) {
        console.log(data);
    }
});

calback=?とすることで、jQueryが勝手にコールバック名を生成してアクセスしてくれる。

JavaScript版

var target = document.createElement('script');
target.charset = 'utf-8';
target.src = 'http://example.com/search.json?callback=hoge';
document.body.appendChild(target);

function hoge(result) {
    console.log(result);
}