お仕事で使うには、まだまだ大変です

HTMLでもちゃんと終了処理

onunloadイベント

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

onloadイベントについては、知らない人もいないでしょうから、簡単にonunloadイベントをご紹介します。

onunloadイベントは、通常のページ遷移だけではなく、☓ボタンなどによるWebブラウザの終了でも呼び出されます。マシンダウンやOSのフリーズなど、ごくまれ(ごくごくまれですよね)なケース以外では正しく呼び出されることになります。

onloadイベントとonunloadイベントでログを取得するなどして、使用時間やエラーの発生状況などを取得することができます。

また、ユーザがどのページを表示しているか、不正なページ遷移をしていないかなどのチェックも可能でしょう。

問題は、onunloadイベントのイベントハンドラ終了後、当該ページの処理が速やかに終了してしまうことです。サーバへアクセスしようとしても、通常の方法(form要素に対するsubmitXMLHttpRequestの非同期通信など)では、サーバへの通信が確立する前に終了してしまいます。そこで、XMLHttpRequestの同期通信を使います。使っていました、昔は。

最近は、ちゃんと良いものが用意されているんです。

最近の方法(Beacon API)

殆ど、「onunloadイベントでサーバと通信するため」だけに策定されたと言っても良い機能が、Beacon APIです。

2行目からの関数で、トランザクションIDを取得します。

この関数は、9行目で実行されます。

Beacon APIに関しては、説明する必要もないほど簡単ですね。11行目unloadpage.phptransactionIdをPOSTで送信します。

13行目windowunloadイベントにBeacon APIを呼び出している関数を登録します。

transaction.phpは、あまり意味がないので割愛します。unloadpage.phpは、以下のようになります。

レスポンスは必要ないので、ステータスコードは204 No Contentを返します。

3行目で、POSTで受信したbody部分を取得しています。Beacon APIは、単純なテキストをデータとして渡された場合、Content-Typetext/plainとして送信するため、この様になります。

一般的なPOSTデータとして取得するためには、Content-Typeapplication/x-www-form-urlencodedとする必要があるので、Beacon APIの呼び出しを、以下のようにする必要があります。

こうすれば、一般的なPOSTデータの取得方法で、データを取得できるようになります。

どこまでWebブラウザが対応してくれるのかは分かりませんが、少なくとも数秒程度時間がかかる処理でも、途中でリセットされること無く通信は正常に終了します。

これは、Webブラウザを終了したことで呼び出された場合でも同様です。終了された後もプロセスが残って、通信処理を行っているということになるので、やはりBeacon APIで呼び出す処理は、極力短時間で終了することが大切でしょう。

昔ながらの方法(XMLHttpRequest同期通信)

Beacon APIは簡単で便利なのですが、新しい仕様なのでもちろんInternet Explorerは実装していません。

Internet Explorerをサポートしなくても良ければ、何も問題ありませんが、未だに現役で特に企業ではまだまだ主力です。

POSTする先は、訳あってCGIです。onunloadイベントでは、XMLHttpRequestの非同期通信は使えないため、同期通信を使うことになるのですが、同期通信はスクリプトの処理をブロックするので、結果としてページの遷移がブロックされてしまいます。

サーバの処理に時間がかかると、ユーザビリティとしては最悪です。そこで、NPH(non-parsed header)と呼ばれるCGIを使用します。

本格的なアプリケーションサーバをバックエンドに持つシステムであれば、キューを使用した非同期制御で対応するのでしょうが、ここは、当コンテンツで実現できる程度の方式をご紹介します。

スクリプトのファイル名がnph-で始まっている場合に、WebサーバはNPHスクリプトと判断します。

NPHスクリプトは、Webサーバのバッファリングを回避してレスポンスを返すことができるので、CGIスクリプトの終了を待たずにWebブラウザが結果を受け取ることができます。

実際のスクリプトは、以下のようになります。

3行目のおまじないは、出力のバッファリングを回避するためのものです。

このスクリプトは、唯一HTTP/1.1 204 Not Content\r\n\r\nだけをレスポンスします。このレスポンスを受けた時点で、XMLHttpRequestの同期通信から処理が返ります。CGIスクリプトの中でその後時間のかかる処理を実行したり、サーバの負荷が高くて結果時間がかかってしまっても、クライアントへの影響は最小限で済むことになります。