[index] [bottom]

トランザクション制御

トランザクション制御

アプリケーションの実行環境には、信頼できる初期処理と終了処理が付き物ですが、HTTP+HTMLという環境でも、onload・onunloadイベントという形で、初期処理と終了処理を実装する箇所が提供されています。

onload・onunloadイベント
<body onload="onloadFunc()" onunload="onunloadFunc()">

onloadイベントは、よく使われるイベントですが、onunloadイベントは、あまり見かけません。そもそも、サーバとの通信がページの遷移を伴うWebアプリケーションの場合、onunloadイベントはそれほど利用価値が有るイベントとはいえません(ページの遷移をサーバ側でコントロールするので、わざわざページが"unload"されたことをサーバに通知する必要はありませんし、クライアント側には、そもそもこのイベントの後には何も残らないので、終了処理など必要ありません)。

しかし、通信にXmlHttpリクエストを使用して、サーバとの通信にページ遷移が伴わないとすれば、onunloadイベントは俄然利用価値が膨らみます。

複数のページを含むHTMLドキュメントをロードし、複数のサーバ通信を含むトランザクション処理を行い、そのページを終了することでトランザクションを完了するような、いかにもアプリケーションらしい動きを実装できます。

嬉しいことに、現在(2005/末)一般に使われているWebブラウザは、onunloadイベントを問題なく実装しています。このイベントはページの遷移だけではなく、リロードやxボタンなどによるWebブラウザの終了でも呼び出されるので、マシンダウンやOSのフリーズなど、ごくまれ(ごくまれですよね)なケース以外では正しく呼び出されることになります。

問題は、onunloadイベントがイベントハンドラ終了後、速やかに終了されてしまうことです。通常のサーバアクセス(form要素に対するsubmitなど)処理は、サーバへの通信が確立する前に終了されてしまいます。そこで、XmlHttpリクエストの同期通信を使います。

同期通信の場合、サーバへリクエストしレスポンスを受けるまで、scriptの動作がブロックされるため、通常であればサーバとの通信を確立することを保証できないonunloadイベントの中でも、サーバとの通信を保証できます。

但し、そもそもそのページを参照しているユーザから見れば、全く不必要なサーバ通信が、他のページへの遷移や、Webブラウザの終了を妨げるわけですから、作法として掛かる時間を最小限にする必要があります。

HTMLサンプル(抜粋)
<head>
    <title>トランザクション</title>
    <script id="trnjs" type="text/javascript"></script>
    <script type="text/javascript">
        document.getElementById("trnjs").src =
        "../cgi-bin/loadpage.cgi?" + new Date().getTime();
        function pageUnload() {
            if (window.XMLHttpRequest) {
                httpRequest = new XMLHttpRequest();
            }
            else {
                httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
            }
            httpRequest.open("POST", "../cgi-bin/nph-unloadpage.cgi", false);
            httpRequest.send(getTrnId());
        }
    </script>
</head>
<body onunload="pageUnload()">
loadpage.cgi(抜粋)
use Time::HiRes qw (usleep ualarm gettimeofday tv_interval);

($tsec, $micro) = gettimeofday;
$trnId = $tsec + $micro / 1_000_000;

print "Content-Type: text/javascript\r\n\r\n";
print "function getTrnId() { return \"\"+$trnId; }";
nph-unloadpage.cgi(抜粋)
print "HTTP/1.1 204 Not Content\r\n\r\n";
                  ・
                  ・
時間がかかるいろいろな処理など

上記の例では、onloadイベントではなく、javascriptのソースにcgiを指定し、その中でトランザクションIDを生成しています。これは、通常onloadイベントでサーバからデータの取得などを行いますが、このときにトランザクションIDが必要なので、その前に取得したいからです。

loadpage.cgiの後ろのクエリ文字列は、Webブラウザやプロキシなどのキャッシュにヒットすることを防ぐためのものです。

loadpage.cgiのなかでトランザクションIDを採番し、これを返すjavascriptのコードを生成します。

このトランザクションIDの採番ロジックは、マーフィーの法則を恐れるのであれば間違い(同じトランザクションIDが複数生成される可能性がある)ですが、例としては充分でしょう。

pageUnload関数の中で、nph-unloadpage.cgiを呼び出しています。わざわざNPHスクリプトを使っているのは訳があります。NPHスクリプトは、Webサーバのバッファリングを回避してWebブラウザにレスポンスを送ることができるので、CGIスクリプトの終了を待たずにWebブラウザが結果を受け取ることができます。

nph-unloadpage.cgiは、唯一HTTP/1.1 204 Not Content\r\n\r\nだけをレスポンスしますが、これはレスポンスデータが存在しないことをWebブラウザに通知するもので、Webブラウザはこのレスポンスを受けた時点で、処理を終了します。CGIスクリプトの中でその後時間のかかる処理を実行したり、サーバの負荷が高くて結果時間がかかってしまっても、クライアントへの影響は最小限で済むことになります(皆が正しい実装を行っているとすればですが)。

但し、この処理方式では、onunloadイベントを実行した直後に、Webブラウザの「戻る」ボタンで戻った時に、まだ直前のonunloadでリクエストしたCGIの処理が終わっていなかった場合、正しく動作しない可能性があります。リクエストされる処理は、極力軽くする必要があります。

また、万全を期す場合は、onloadイベントでgetTrnId()メソッドの有無を判定することも必要でしょう。

[index] [top]