Ajaxのサーバとクライアントを実装する際の基礎事項(通信キャッシュ)を検討する

なお,このページの内容はIE(Internet Explorer 6)で検証したものです.他のブラウザでは動作が異なる可能性があるので注意してください.

キャッシュによるAjax通信の負荷軽減

Ajax通信は,通常の画面遷移に伴うリクエストより高い頻度で発生する傾向があるので, キャッシュを考えた方が良い. 考えておかないと,自分のサーバの負荷を増やしてしまうことになるし, 通信が遅いとせっかくのAjaxの操作感を損ねる可能性がある.

基本的にはApacheのmod_cacheに任せるが,ページがCTRL+F5によってno-cacheリロードされたタイミングで,そのページで行っているAjax通信のキャッシュも更新したい.

以下に書いてあるように

  • CGIプログラムのレスポンスヘッダを制御する
  • JKL.ParseXMLを改造する

ことによって,ページの一部がAjaxで描画されているという画面では,そのページでCTRL+F5すれば,Ajaxで描画している部分もApacheのキャッシュをスルーして最新化されるようになった.

(1) サーバ側でのキャッシュ

Apacheのキャッシュ(mod_cache)を使う.この場合mod_cacheの仕様上, 以下の条件を満たす必要がある.
  1. XMLHttpRequestによるサーバへのリクエストを,POSTではなくGETで行う必要がある.
  2. サーバからのHTTP応答に,以下のヘッダを含める必要がある.
    • Last-Modified
    • Expires

それぞれのヘッダに設定する値は,カモランドでは以下のようにしている.

HTTPヘッダ名
Last-Modified現在日時
Expires現在日時+86400秒

こうすれば,

  • キャッシュはいつでもブラウザから強制更新することが可能 (no-cacheリクエストの送信時)
  • 更新をしなければ,キャッシュは最長で86400秒(=1日)有効

という動きになる. (mod_cacheでWiki(CGI)の高速化参照)

Ajax通信のサーバ側プログラムでは,この内容のヘッダをHTTPレスポンスに設定する.

(2) クライアント側でのキャッシュ

クライアント側では,Ajax通信で受け取った結果をhiddenに格納するようにしている. そしてbodyのonLoadイベントで,もしhiddenに値が入っているならそれを使って画面を描画している.

こうしておけば,別のページに遷移してからブラウザバックで戻ってきたときに,前回の表示内容を復元することができる.

この内容については,Ajax実装の基礎(ブラウザバック対応)に詳細を記述している.

(3) キャッシュを更新する方法

Ajaxで画面に表示する内容の元データが更新された等の理由で, Ajax通信のGETに対してサーバが返すべきレスポンスの内容が変わった時は, キャッシュを更新して最新の内容をブラウザに送信できるようにしなければならない.

この場合,XMLHttpRequestによるリクエストに以下のヘッダ設定を行えば,キャッシュを更新して最新化できる.

ヘッダへの設定内容その設定が影響を与えるキャッシュ
Cache-Controlヘッダにno-cacheを指定するApacheのキャッシュ
If-Modified-Sinceヘッダにエポック時を指定するIEのローカルキャッシュ

ここには,サーバ側のキャッシュとクライアント側の両方の話が混じっているので, 以下ではそれぞれ別々に説明する.

サーバ側のキャッシュの更新

サーバ側ではApacheのmod_cacheがキャッシュする.

詳細はmod_cacheでWiki(CGI)の高速化に書いているが,有効期限切れではなく強制的に更新したい場合には, Cache-Controlヘッダをつけたリクエストをブラウザ(今回はXMLHttpRequest)から送ればよい.

そうすると,

  1. Apacheは自分のキャッシュを使わずに,サーバ側プログラム(CGI)にリクエストを流す
  2. サーバ側プログラムは必要な処理を実行して,レスポンスを生成してApacheに返す
  3. Apacheは,サーバ側プログラムから受け取ったレスポンスのLast-modifiedヘッダと,Apacheが持っているキャッシュのLast-modifiedヘッダを比較して,もし受け取ったレスポンスの方が新しければ,それをブラウザへのレスポンスとする

という動作が,サーバ上で発生する.

3.のLast-Modifiedヘッダ比較については,既に書いたようにサーバ側プログラムのHTTP応答では

  • Last-Modified = 現在日時

を設定するので,受け取ったレスポンスの方が常に新しいことが保証される.

そのため,ブラウザへのレスポンスは,必ず更新される結果になる.

クライアント側のキャッシュの更新

クライアント側ではhiddenにデータを保持することを書いたが,それとは別に,IEは勝手にローカルにキャッシュしてくれる. このこと自体は別に問題ないのだが,一旦キャッシュするとその内容ばかり使うようになり, サーバを見に行ってくれない.

それで,IEにローカルキャッシュの内容を使うのではなくサーバを見に行かせるために, If-Modified-Sinceヘッダの設定が必要となる.

強制更新したいので,常に成り立つようにエポック時(最も古い日時)を設定する.

 If-Modified-Since: Thu, 01 Jun 1970 00:00:00 GMT

(4) キャッシュを更新するタイミング

では,このキャッシュ更新をいつやれば良いのかだが,これはなかなか難問. 色々な状況に当てはまる答えは見つかりそうにない.

そのため,Ajax応答の元となるコンテンツが更新されたなどの状況を人間が判断して, 人間が更新の指示を出すことにする.

更新の指示を出す方法だが, Ajax通信は,それを呼び出しているページと密接な関係にあると考えられるので,

  • Ajax通信を呼び出しているページのキャッシュが更新されたら,そのAjax通信のキャッシュも更新する

というキャッシュ更新仕様にして,

  • 人間はページのキャッシュを手動更新すれば良い.そうすればAjax通信のキャッシュも自動更新される

という方法を採ることにした.

ページのキャッシュ更新と連動する方法

ということで,キャッシュ更新の仕様は,
  • XMLHttpRequestによるリクエスト送信時に,ページのキャッシュが更新されたかどうかを判断し,
  • もし更新されていたら既に述べたようにCache-Control,If-Modified-Sinceヘッダを設定する

となった.

ここで問題なのが,ページのキャッシュが更新されたかどうかをどうやって判断するかだ.

ページのキャッシュが更新されると,サーバ側プログラムまでリクエストが行くので, カモランドの場合は

  • Last-Modified = 現在日時

というヘッダが付加される.(PyukiWikiにキャッシュ機能をつけるの「mod_cacheと併用する場合について」 に書いてあるとおり,カモランドのWikiはそういう実装にしている)

逆に,キャッシュがそのまま返された場合は,Last-Modifiedは昔の日時となる.

そこでこの性質を利用して,documentのlastModifiedが現在日時かどうかをチェックして, もし現在日時ならページのキャッシュが更新されたとみなすことにした.

カモランドではXMLHttpRequestによる通信に, JKL.ParseXML を使っているので,これを少し改造することで実現している.

jkl-parsexml.js の566行目付近に挿入

	if (Math.abs(new Date().getTime() - Date.parse(window.document.lastModified)) < 30 * 1000) {
		// no-cache
		this.req.setRequestHeader( "Cache-Control","no-cache");
		this.req.setRequestHeader( "If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
	}

Last-Modifiedと現在日時(new Date)の差が30秒以内なら,キャッシュが更新されたと判断している.

30秒以内とかいうのは,

  • Last-Modified : カモランドサーバの時計が基準
  • 現在日時(new Date) : ページ閲覧者のマシンの時計が基準

なので,それなりに誤差が出そうだからです.閲覧者のマシンの時計が狂っていると,ここの制御もおかしくなります.

参考

[Ajax tips] XMLHttpRequest と If-Modified-Since
http://www.semblog.org/msano/archives/000386.html
kamolandをフォローしましょう


© 2021 KMIソフトウェア