兵どもが、夢のあとさき

JavaScript 系の言語に興味を惹かれ、まったりと更新しております

JSONあれこれ(4)

 JSONを出力するプログラム(json.php)はできましたので、次はそのデータにアクセスし、データを取得・表示する受信側を作成します(ajax.php)。これも『PHP逆引きレシピ 第2版』からの引用です。なお、サーバ環境に関しては『PHP逆引きレシピ 第2版』のままとしていますので、そちらを参照願います。良い本ですよ。

 名称は ajax.php ですが、下記の様に PHP ではなく HTML + jQuery で構成されています。(7,8行目にコメント追加修正)

<!DOCTYPE html>

<html lang="ja">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width,initial-scale=1.0">

<title>JSON形式のデータを返すWeb APIを作りたい</title>

<link href="../../css/style.css" rel="stylesheet">   <!-- 7行目 CSS 読み込み -->

<script src="../../js/jquery-2.0.3.min.js"></script>   <!-- 8行目 jQuery ライブラリ読み込み -->

<!-- Internet Explorer 6~8で動作させる場合はバージョン1系を使用してください -->

<!-- <script src="../../js/jquery-1.10.2.min.js"></script> -->

<script>

  $(function(){

// jQueryを使用して、読込ボタンがクリックされたら処理を行ないます。

    $("#load").on("click", function(){

// Web APIjson.php)で生成したjsonデータを取得して処理します。

      $.getJSON("json.php", function(data){

        for (var i in data) {

// 行のオブジェクトを生成します。

          var tr = $("<tr>");

// 列のオブジェクトを生成して行に追加します。

          var td_item = $("<td>").text(data[i].item);

          tr.append(td_item);

          var td_price = $("<td>").text(data[i].price);

          tr.append(td_price);

          var td_orders = $("<td>").text(data[i].orders);

          tr.append(td_orders);

// 行のオブジェクトをテーブルに追加します。

          $("#listbox").append(tr);

// 読込ボタンを非表示化します。

          $("#load").hide();

        }

      });

    });

  });

</script>

</head>

<body>

<div>

<input type="button" value="読込" id="load">

<table id="listbox">

<tr>

<th>品名</th>

<th>価格</th>

<th>注文数</th>

</tr>

</table>

</div>

</body>

</html>

 http://localhost/php-recipe/07/11/ajax.php を実行すると、下記ページが表示されます。

   f:id:shogorobe:20140213143227j:plain

「読込」ボタンをクリックすると、

    f:id:shogorobe:20140213172731j:plain

 と表示され、json.php で定義した JSONデータが ajax.php によって受信、表示されました。 これは何でもないようなことですが、実は ajax.php から HTTPのGETメソッドを発行し json.php を呼出し、JSONデータ(Content-Type: Application/json)の受信、Unicodeデコード (json.phpエンコードされています) 、javascriptオブジェクト(XMLHttpRequest)化を行っています。そして、そのオブジェクトにアクセスし、<table>タブに挿入することで、画面遷移なしにデータの表示を行っているのです。jQuery の $.getJSON メソッド便利です。

 HTTP/GETメソッドを発行しているということは、例えば ajax.php の16行目のjQuery の表記を下記のように JSONP で取得する様に変更すれば、リモートサーバの JSON データを取得することも可能です(かなりトリッキーですし、jQuery 側のクロスドメイン制限、セキュリティ上の留意点など注意すべき点がありますが)。

$.getJSON("http://リモートサーバ/~/json.php?callback=?", function(data){

 * すいません上記の「読込」後のボタンですが本来は表記されません

CDNの使用

 8行目で jQueryライブラリを読み込んでいるのですが、CDNという機能を使用すると、jQuery をWebサーバ上に置くことなく手軽に利用できます。(その他にもメリットがあります)

 最新の jQuery を読み込むように8行目を下記のように書き換えます。

<script  type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script> 

 メリットとしては、

  • 高性能なサーバ(上記の例では、code.jquery.com)を利用でき、高速化が期待できる
  • 同じサーバを指定すればキャッシュ効果も期待できる
  • 何といっても自サーバの jQuery の設定が不要

が挙げられます。

 

フォールバック

 良いことだらけの CDN に見えますが、デメリットもあります。アクセス先のサーバがダウンしたり、ネットワークに異常が発生した場合 jQuery のライブラリが取得できない場合が発生します(私も1回経験しました)。そこで実運用環境では、下記の様に自サーバ上にも jQuery の環境を整備し、CDNが使用できない場合(window.jQuery が undefined となり、|| 以降が実行される)バックアップ機能を持たせるのが良いのかなあと。3番目のメリットが無くなりますが。

<script type="text/javascript" src="http://code.jquery.com/jquery-2.1.0.min.js"></script>
<script>window.jQuery || document.write('<script src="../../js/jquery-2.0.3.min.js"><\/script>')</script>

それでは次回まとめに入ります。

 

追記

 JSONPによるリモートサーバからの JSON データの取得ですが、一般に公開されている Web API のアクセスに関しては思いの外、大変です。http://weather.livedoor.com/weather_hacks/webservice で公開されている、livedoorの「お天気Webサービス」の情報を上記方法で取得しようとしてもエラーとなります。

 Chomeのデバッガを使用しながら、金沢の情報を取得してみるとデータの取得はできています(下記引用参照)。しかし、「XMLHttpRequest cannot load http://weather.livedoor.com/forecast/webservice/json/v1?city=170010. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access.」というエラーが発生しています。

  1. copyright: {provider:[{link:http://tenki.jp/, name:日本気象協会}], link:http://weather.livedoor.com/,…}
  2. description: {,…}
  3. forecasts: [,…]
  4. link: "http://weather.livedoor.com/area/forecast/170010"
  5. location: {city:金沢, area:信越・北陸, prefecture:石川県}
  6. pinpointLocations: [{link:http://weather.livedoor.com/area/forecast/1720100, name:金沢市},…]
  7. publicTime: "2014-02-15T11:00:00+0900"
  8. title: "石川県 金沢 の天気"
  9.  

 これは、サーバ側で「Access-Control-Allow-Origin」ヘッダを送信していないため、データの取得はしなかった、というエラーです(エラーというか javascript の仕様)。$.getJSON()では、データは XMLHttpRequest オブジェクトで取得されるのですが、これはクロスドメインではアクセスは許されません。つまり同じドメインのみデータのアクセスが可能なわけです。そのためブラウザ(今回はChrome)は、別のドメインから取得した JSON データの取得を拒否したということです。

 回避方法は、いくつかあるようですが、今回の場合は ajax.php と同一ドメインのサーバ上で、 $.getJSON() を実行するサーバ上に「お天気Webサービス」の JSON データを取得する javacsript 以外のプログラムを別途設置するのが実現的かなと考えます。「お天気サービス」では、JSONPの「コールバック」による取得はできないようですし。

 もちろん、セキュアな情報を扱う Web API を構築するのであれば、同一ドメインで管理すべきでしょう(認証、セッション管理を別途行うのはもちろん)。

 Ajax が出力する「X-Requested-With」ヘッダは、クロスドメイン環境では出力されないようです(私が確認したのは Chrome 32.0.1700.107 m)。そのため、JSONハイジャックを防止する目的で、同ヘッダをチェックする方法は採れないようです。

 まとめると

  • Ajax(XMLHttpRequest)でクロスドメインのデータの取得は不可 
  • JSONPを使用すると取得は行うが、ブラウザの javascript でエラー(jQuery の $getJSON() メソッドで .fail()が呼び出される)となる
  • JSONJSONP は全く異なるものである。JSON は単なるデータであるが、JSONPスクリプトである
  • 従って、クロスドメイン環境でも、<script src="..."></script>の形式で、JSONP を呼び出すことが可能
  • JSONPを使用する場合、サーバ側で設定した「コールバック名」を指定することで、ブラウザの  javascriptライブラリ(jQuery の $.getJSON()など)で JSON データを取得することが可能
  • XMLHttpRequest でデータの取得を行うには、サーバ側で「Access-Control-Allow-Origin: *」ヘッダを出力する必要がある。この設定を行えば、クロスドメインのデータの取得は可。(但し JSONPスクリプトであるため、javascriptライブラリは、この設定に影響されずデータを取得してしまう)
  • クロスドメイン環境では、X-Requested-With ヘッダの有無による、XMLHttpRequest からのアクセスであるかのチェックは不可。同一ドメイン環境でのみ可能

(1) JSONP を使用する ⇒ 

 サーバ側の「コールバック名」を指定すれば JSON データを取得できる

 サーバ側でAccess-Control-Allow-Origin: *」の設定も不要

 サーバとクライアント側で、「コールバック名」を一致させる必要がある

 セキュリティに十分留意する必要がある

(2) AjaxJSON データを取得する ⇒

 サーバ側で「Access-Control-Allow-Origin: *」の設定が必要(ブラウザではデータの受信は行うが、javascript ライブラリでブロックする)

 クロスドメイン環境では、X-Requested-With ヘッダが送信されないので、サーバ側での、Ajax からのアクセスチェックには使用できない。同じドメインでのみ使用可能

 

 

 一部修正しました。まだ試行錯誤中です。間違っていたら追記します。