このエントリーをはてなブックマークに追加
スポンサーリンク

ajax入門

ajaxとは

ajaxは、エイジャックスとかアジャックスとか呼ばれます。新しい言語というよりは、技術(手法)の名前というのが正しいようです。

動的ページは、条件によって出力する内容を変化させるウェブページの事ですが、大きくわけて2種類あります。

1.サーバーサイドスクリプト
2.クライアントサイドスクリプト


サーバーサイドスクリプトは、CGIやPHPのようにサーバー側で動作するスクリプト言語です。サーバーサイドにあるデーターベースにアクセスする事ができるため、簡単な掲示板スクリプトから通販サイトや受発注ページなど、データーベースがらみの処理に多く利用されます。

クライアントサイドスクリプトは、JavaScriptに代表される端末側で実行させるスクリプトです。サーバー側にあるデーターベースへのアクセスはできませんが、リロードなしで画面を書き換えられるといった、クライアントサイドならではの機能が使えます。
サーバー側にあるDBへアクセス リロードなしで画面の書き換え
サーバーサイドスクリプト できる できない
クライアントサイドスクリプト できない できる
というように、サーバーサイドスクリプト、クライアントサイドスクリプトにはそれぞれ長所と短所があります。

そこである人は考えました。サーバーサイドスクリプトでデーターベースへアクセスし、その結果をクライアントサイドで読み取ることができれば、リロードなしでデーターベースから情報を読み取って画面を書き換えることができるのではないか、と。という事で、JavaScriptからサーバーサイドスクリプトにアクセスしてデーターベースの状況を読み取り、何らかの変更があればリアルタイムで画面を書き換える、そんな手法ができました。それをajaxと名づけました。

ajaxの仕組み

サーバーと端末が常に通信する・・とはいえ、rsyncのような常に同期を取るというわけではなく、JavaScriptのsetIntervalコマンドを使い、定期的にサーバーサイドスクリプト変更の指示があるかどうかを確認に行きます。


のように、JavaScriptのsetIntervalコマンドで一定間隔毎にWebサーバーにアクセスする関数を実行します。呼ばれるのは、サーバー側にあるPHPかCGIスクリプトです。呼ばれたサーバーサイドスクリプトはデーターベースへのselectを行い、何か変更があったかどうか確認します。

変更があれば画面を書き換える指示をJavaScript側に返し、それを受け取ったJavaScriptがリロードなしに画面を更新します。

この場合、PHPからselectを発行するのであまり頻繁にアクセスするとDBサーバーへの負担が多くなってしまいます。なのでアクセスは1秒毎ぐらいにしておきます。という事で、本当の意味での”リアル更新”ではなくなってしまうのですが、大抵はタイムラグ1秒以内であれば、そう問題にはならないでしょう。

ajaxの問題点

ajaxを使うために、クライアントサイド側でJavaScriptで走らせる必要があります。JavaScriptからサーバーへアクセスする事は、ブラウザのバージョンが低い頃でも可能ではありました。しかし、しばらくそれをやろうとする人はいませんでした。それにはさまざまな理由がありました。

ブラウザ依存

JavaScriptは昔から個人サイトで多く使われてきましたが、業務サイト、特に企業サイトではフラッシュが使われることがほとんどでした。その理由は、JavaScriptのブラウザ依存の多さです。つまり、かつて8ビット機時代にROM-BASICが機種(PC-8801/FM-7/MSXなど)でさまざまな方言が存在したのと同じく、ブラウザ毎にかなりの方言というか、解釈の違いが存在します。

さらに困ったことに、マイクロソフトが独自にインターネットエクスプローラでのみ使える命令を拡張しました。が、インターネットエクスプローラはWindows標準で搭載されているためインターネットエクスプローラー独自の命令を使ったとしても閲覧可能な人が多く、そのためインターネットエクスプローラ独自の命令を使うサイトも増えてきました。それに伴い、他のブラウザもできる限りインターネットエクスプローラと互換性を持たせようとしました。

という経緯もあって、ブラウザ毎だけでなく、同一ブラウザでもバージョン毎に使える命令/使えない命令が色々とできてしまいました。その事が、多くのサイトでJavaScriptを企業サイトに使う事に消極的になってしまいました。

Googleマップの登場

「JavaScriptはお遊び程度にしか使えない」「本格的にリアル更新のサイトを作るにはJavaかフラッシュしかない」という認識を改革するサイトが現れました。それがGoogleマップです。GoogleマップではJavaScript+サーバーサイドスクリプトを使い、画面をリロードする事なく地図を切り替えて表示させています。それも、ほとんどのブラウザに対応して。

実はGoogleマップは、現存する多くのブラウザ専用ルーチンを、それぞれ別々に作っていたのでした。そして、JavaScriptでブラウザの種類やバージョンを判別し、それぞれ専用のスクリプトを実行させていたのです。

「やればできる」

今までJavaScriptで本格的な企業サイトを作るのに消極的だった人たちも、これを期にブラウザ毎の関数を用意する事で業務用ソフトにJavaScriptを取り入れるようになりました。

ajaxオブジェクトを作成

JavaScriptからサーバーサイドスクリプトを呼ぶためには、ajaxというオブジェクトを作成します。
ajax = false;
if(window.XMLHttpRequest) {
        ajax = new XMLHttpRequest();
} else if(window.ActiveXObject) {
        try {
                ajax = new ActiveXObject("Msxml2.XMLHTTP");
        } catch(e) {
                ajax = new ActiveXObject("Microsoft.XMLHTTP");
        }
}
サーバーと交信をするための、XMLHttpRequestというオブジェクトを生成します・・・・が、これはインターネットエクスプローラでは使えません。なので、これから作るページはインターネットエクスプローラ非対応のページとします・・

・・・なんてワケにはいきません。基本的にajaxはなるべく多くのブラウザで動作するような記述をします

if(window.XMLHttpRequest)で、XMLHttpRequestが使えるならそれをオブジェクトとして使い、使えなければMsxml2.XMLHTTPというアクティブXをオブジェクトとして使います。ところが、Msxml2.XMLHTTPはインターネットエクスプローラ6.0以降でしか使えないため、それ以前のバージョンではMicrosoft.XMLHTTPというオブジェクトを使うように指示します。ただし、Microsoft.XMLHTTPは動作が遅いため、最も優先順位を低くします。

シリアルを取得する

画面をリアル更新させる際に、クライアントスクリプトに「情報が更新された」という事を教えないといけません。しかし、データーベースにはなるべく負荷をかけたくありません。

そこで、PostgreSQLであらかじめシーケンスを作っておき、サーバーサイドスクリプトで情報が更新された時にシリアルを更新させます。クライアントサイドスクリプトでシリアルの更新を判別して画面を更新します。

クライアントサイドでは、
setInterval( "getserial('serial')",1000);
を定義しておき、1000ミリ秒(=1秒)おきにgetserialという関数が呼ばれるようにします。
function getserial(){
        if (get_f && firefox==1){
                get_f=0;
                getbody();
        } else {
                ajax.open("GET", "http://シリアルを取得するURL");
                ajax.onreadystatechange = function() {
                        if (ajax.readyState == 4 && ajax.status == 200) {
                                serial = eval(ajax.responseText);
                                if (serial>maxserial){
                                        maxserial=serial;
                                        get_f=1;
                                }
                        }
                }
                ajax.setRequestHeader("If-Modified-Since", last_modified);
                ajax.send(null);
        }

        if (get_f && firefox==0){
                get_f=0;
                getbody();
        }
}
ajax.open("GET", "http://シリアルを取得するURL
には実際にはアクセスすると現在のシリアルのみを返すPHPかCGIを用意しておき、ここで指定します。

ただし、そのままではキャッシュが効いて同じ値しか帰ってこないので、ブラウザの「最終更新日」をいじります。
ajax.setRequestHeader("If-Modified-Since", last_modified);
ajax.send(null);
で最終更新日をリセットしています。最終更新日は、別途関数の外で
var last_modified = "Thu, 01 Jun 1970 00:00:00 GMT";
で、最終更新日を1970年1月1日0時0分0秒として、UNIX時間でいうところの一番若い日時をセットしておきます。そうすると、ブラウザは現在取得したページの最終更新日が1970年1月1日0時0分0秒だと認識するため、次に取得する際にもキャッシュを使わず新しく取得しなおします。

URLに?time=現在のUNIXタイムスタンプをつけてもキャッシュを無効化できますが、そうすると1秒毎にブラウザが取得したページを新しいページとみなしてキャッシュしてしまうので、キャッシュディレクトリがあっという間にいっぱいになってしまいます。するとブラウザが重くなってしまうし、キャッシュをクリアする際にも非常に時間がかかってしまうので、今回はその手法は用いません。

サーバーサイドの方では
$sql="select last_value from chat_number_seq";
if($result=pg_exec($handle,$sql)) {$rows=pg_numrows($result);}else{$rows=0;}
if ($rows>0){
        $last_value=pg_result($result, 0, 0);
}
print $last_value;
のように最大のシリアル値を取得してブラウザに返すようにします。本来シリアル値のlast_valueを直接取得するのは良くない事とされていますが、それは次のシリアル値を取得する場合であって、ここではシリアルが変わったかどうかを判別したいだけで他のプロセスと同じ値を取得しても構わないので、このような取得方式でも大丈夫です。

ファイアフォックスでは、1回の割り込みでシリアルと中身の2回を同時に取得する事ができないので、ファイアフォックスの場合はget_fフラグをたてたまま関数を抜けます。ファイアフォックスの場合は、次回の割り込みでget_fフラグが立ってたら本体を取得するようにします。

今回の例ではMSIEとファイアフォックスのみですが、本来はオペラやサファリなどさまざまなブラウザ用のルーチンも組み込んでおきます。

中身を取得する

シリアルの更新を確認したら、今度は中身を取得し、画面を書き換えます。
function getbody(){
        ajax.open("GET", "本体を取得するURL");
        ajax.onreadystatechange = function() {
                if (ajax.readyState == 4 && ajax.status == 200) {
                        var obj = document.getElementById("maintext");
                        obj.innerHTML = ascii2str(ajax.responseText);
                }
        }
        ajax.setRequestHeader("If-Modified-Since", last_modified);
        ajax.send(null);
} 
基本的にはシリアルの取得と同じですが、取得した結果をinnerHTMLというJavaScriptの命令を使ってリアル更新させている点が異なります。ここではサーバーサイドでは全てをアスキーコードで返すようにして、クライアントサイドのascii2strという関数で文字に変換しています。
function ascii2str(str){
        var kekka="";
        var sumi=0;
        len=str.length;
        for(cnt=0;cnt<len;cnt+=4){
                moji1=str.substr(cnt,1);
                moji2=str.substr(cnt+1,1);
                moji3=str.substr(cnt+2,1);
                moji4=str.substr(cnt+3,1);
                moji="0x"+moji1+moji2+moji3+moji4;
                moji=String.fromCharCode(moji);
                kekka=kekka.concat(moji);
        }
        return kekka;
}
この関数では16進表記された文字列を、4文字ずつ取得し、16進数とした後String.fromCharCode関数で文字に戻しています。

ただし、String.fromCharCodeは文字コードをUTF-8とみなしますので、サーバーサイドからの情報は全てUTF-8に変換してから返す必要があります。その際に全角の−などは化けてしまうので、化ける文字は別途別のコードを割り当てて、本来の文字コードとは別に判別させるといった処理が必要になります。

ajaxの今後

ajaxを扱っていると、どうしてもブラウザに標準に搭載された機能がかえって邪魔になってしまうケースが多くあります。 それは、キャッシュでありActiveXに対する警告であり・・。 というより、もともとブラウザがそういう使い方をする事を想定して作られておらず、ブラウザをだましだまし使ってるという感があります。

Googleマップの登場で一躍脚光を浴びたajaxですが、それでもまだ使われているサイトは少ないです。 それはJavaScriptの解釈がブラウザによって異なる場合が多いためです。 また、これからブラウザやHTMLやJavaScriptのバージョンがどんどん新しくなるにつれ、どこのバージョンまでをサポートするかという問題に直面する事もあります。

今後ajaxが普及するためには、ブラウザごとのJavaScriptの解釈やバージョンごとの差異を少なくしていく必要があると思われます。
スポンサーリンク