RubyOnRailsとjQueryで簡単クロスドメインajax

Railsのサーバーと非RailsWebサービス(どちらも自前で管理)のクロスドメインajaxをやってみたら、思いのほか簡単だったので、記事にしてみます。

なお、クロスドメインに関するセキュリティ的な話題はいろいろあると思いますので、そのあたりは自分で調べてみてください。

サンプルコードに関して

こちらにサンプルコードを載せたので、もしよければ参考にしてください。 https://github.com/sakairyota/jsonp_example

本文中のコードは下記のファイルに含まれています

app/controllers/entries_controller.rb
config/routes.rb
public/test_jsonp.html

public/test_jsonp.html はRailsのサーバーから開くと同一ドメインの通信、ファイルシステムから開くとクロスドメインとなり、気軽にそれぞれを試すことができるようになっています。

クロスドメイン制約

JavaScriptにはクロスドメインの制約があります。たとえは、example.comにあるファイルを、はてな(hatena.ne.jp)のページに配置したJavaScriptから通信して取得することはできないようになっています。

JSONP

しかし、JSONPを使うとクロスドメインの壁を超えることができます。JSONPではsrcにファイルのURLを含めたscriptタグを生成して、JavaScriptを実行させます。たとえば、hatena.ne.jpで、srcにexample.com/hogeのファイルを指定したscriptタグを生成すると、生成した時点で、example.com/hogeJavaScriptが実行されます。

JavaScriptでの通信はドメインを超えることができないのに対し、Scriptタグで外部のドメインスクリプトを読み込むことができることを利用しています。

詳しくは、http://ja.wikipedia.org/wiki/JSONP

RailsJSON

Railsでは、標準ではviewsに配置されているhtmlのテンプレートを元にhtmlを返しますが、respond_toを使うと、JSONxmlを返すことができるようになります。

class EntriesController < ApplicationController
  def show
    @entry = {id: params[:id], title: "entry#{params[:id]}", content: "This is content ##{params[:id]}"}
    respond_to do |format|
      format.json { render json: @entry}
    end
  end
end

説明のため @entry にハッシュを代入していますが、実際には Entry.find params[:id] などが入ります。

resources :entries, only: :show

ルーティングを上のように設定すると、http://localhost:3000/entries/1.json というように.jsonを付けてJSONを取得することができます。

{
    "id": "1",
    "title": "entry1",
    "content": "This is content #1"
}

しかし、これではただのJSONJSONPではありません。

RailsJSONP

そこで、コードを次のように変更します。

class EntriesController < ApplicationController
  def show
    @entry = {id: params[:id], title: "entry#{params[:id]}", content: "This is content ##{params[:id]}"}
    respond_to do |format|
      format.json { render json: @entry, callback: params[:callback]}
    end
  end
end

差分

-      format.json { render json: @entry}
+      format.json { render json: @entry, callback: params[:callback]}

すると、JSONPが使えるようになります。http://localhost:3000/entries/1.json?callback=hoge でアクセスすると次のようなJavaScriptが返ってきます。

hoge(
    {
        "id": "1",
        "title": "entry1",
        "content": "This is content #1"
    }
)

参考にした記事(2年も前ですが): http://labs.s-koichi.info/blog/archives/2008/03/04/2231-60.php

jQueryからajax(JSONP)

jQueryでは $.ajax を使って、簡単にAjaxを実装することができます。

例えば、次のように $.ajax に url と success のコールバックを指定すると、先ほどのjsonを読み込んで、htmlに書き出すことができます。

$.ajax({
    url: 'http://localhost:3000/entries/1.json',
    success: function(json){
        $('#entry .entry_id').text(json['id']);
        $('#entry .title').text(json['title']);
        $('#entry .content').text(json['content']);
    }
});

これは、JSONを使ったajaxなので、クロスドメインにはできません。

jQueryからajax(JSON)

そこで、$.ajax で dataType のパラメータにjsonpを指定します。すると、JSONPでのアクセスとなり、クロスドメインAjaxが実現できます。

$.ajax({
    url: 'http://localhost:3000/entries/1.json',
    dataType: 'jsonp',
    success: function(json){
        $('#entry_jsonp .entry_id').text(json['id']);
        $('#entry_jsonp .title').text(json['title']);
        $('#entry_jsonp .content').text(json['content']);
    }
});

差分

+    dataType: 'jsonp',

ね、簡単でしょう?

最後に

ajaxでデータを取得するところまでは簡単にできました。

これを実践した時には、この後の受け取ったデータを処理するところに手間取ったということは、「簡単」をうたっているのに対して水をさすので、 この記事では書かないでおきます。