アプリケーション キャッシュを初めて使う

HTML5 Rocks

はじめに

ウェブベースのアプリケーションは、オフラインでもアクセスできるようにすることがますます重要になっています。あらゆるブラウザがキャッシュ メカニズムを備えてはいますが、信頼性に欠け、ユーザーの期待どおりに機能しないことがあります。HTML5 では、ApplicationCache インターフェースにより、オフライン作業で発生する問題のいくつかに対処しています。

アプリケーションでキャッシュ インターフェースを使用することには、次の 3 つの利点があります。

  1. オフライン ブラウジング: ユーザーがオフラインのときでも、すべてのサイトにアクセスできます。
  2. 高速: キャッシュされたリソースはローカルなので、高速に読み込まれます。
  3. サーバー負荷の軽減: ブラウザは、変更があったリソースのみをサーバーからダウンロードします。

デベロッパーはアプリケーション キャッシュ(AppCache)を使用することにより、オフライン ユーザーが利用できるようにブラウザがキャッシュするファイルを指定できます。ユーザーがオフラインのときに更新ボタンを押した場合でも、アプリケーションは正しく読み込まれ、機能します。

キャッシュ マニフェスト ファイル

キャッシュ マニフェスト ファイルは、オフライン アクセスに備えてブラウザがキャッシュする必要があるリソースを列挙した、単純なテキスト ファイルです。

マニフェスト ファイルの参照

アプリケーションでアプリケーション キャッシュを有効にするには、ドキュメントの html タグに manifest 属性を含めます。

<html manifest="example.appcache">
  ...
</html>

manifest 属性は、キャッシュ対象にするすべてのウェブ アプリケーションのページに含める必要があります。manifest 属性が含まれていないと、ブラウザはそのページを(マニフェスト ファイルに明示的に列挙されていない限り)キャッシュしません。つまり、manifest を含むページにユーザーがアクセスすると、そのページは暗黙的にアプリケーション キャッシュに追加されます。そのため、マニフェストにすべてのページを列挙する必要はありません。

manifest 属性では絶対 URL と相対パスのいずれかを指定できますが、絶対 URL はウェブ アプリケーションの URL またはその下の場所である必要があります。マニフェスト ファイルは、ファイル拡張子の制限はありませんが、正しい MIME タイプで提供する必要があります(後述を参照)。

<html manifest="http://www.example.com/example.mf">
  ...
</html>

マニフェスト ファイルは、MIME タイプ text/cache-manifest で提供する必要があります。カスタムのファイル形式を使用する場合は、ウェブ サーバーまたは .htaccess 設定に追加する必要があります。

たとえば、この MIME タイプを Apache で提供するには、次の行を設定ファイルに追加します。

AddType text/cache-manifest .appcache

または、Google App Engine の app.yaml ファイルに次の行を追加します。

- url: /mystaticdir/(.*\.appcache)
  static_files: mystaticdir/\1
  mime_type: text/cache-manifest
  upload: mystaticdir/(.*\.appcache)

マニフェスト ファイルの構造

シンプルなマニフェストは、次のような構造です。

CACHE MANIFEST
index.html
stylesheet.css
images/logo.png
scripts/main.js

この例では、このマニフェスト ファイルを示すページ上の 4 つのファイルをキャッシュします。

注意の必要な点がいくつかあります。

  • 1 行目には文字列 CACHE MANIFEST を記述する必要があります。
  • サイトのキャッシュ データは 5MB 相当に制限されます。ただし、Chrome ウェブストア向けのアプリケーションを作成する場合は、unlimitedStorage を使用するとこの制限はなくなります。
  • マニフェスト ファイルまたはマニフェスト ファイルに指定されているリソースのダウンロードに失敗すると、キャッシュ更新プロセス全体が失敗します。エラーが発生すると、ブラウザは以前のアプリケーション キャッシュを使用し続けます。

次に、もう少し複雑な例について見てみましょう。

CACHE MANIFEST
# 2010-06-18:v2

# Explicitly cached 'master entries'.
CACHE:
/favicon.ico
index.html
stylesheet.css
images/logo.png
scripts/main.js

# Resources that require the user to be online.
NETWORK:
login.php
/myapi
http://api.twitter.com

# static.html will be served if main.py is inaccessible
# offline.jpg will be served in place of all images in images/large/
# offline.html will be served in place of all other .html files
FALLBACK:
/main.py /static.html
images/large/ images/offline.jpg
*.html /offline.html

「#」で始まる行はコメント行ですが、別の目的にも使用できます。アプリケーションのキャッシュが更新されるのは、マニフェスト ファイルが変更されたときだけです。そのため、たとえば画像リソースを編集したり、JavaScript 関数を変更した場合の変更は再キャッシュされません。キャッシュ ファイルを更新するようにブラウザに通知するには、マニフェスト ファイルを変更する必要があります。生成したバージョン番号、ファイルのハッシュ、タイムスタンプを記載したコメント行を追加することは、ユーザーのソフトウェアが最新バージョンであることを確認するひとつの方法として有効です。また、キャッシュの更新で説明するように、新しいバージョンの準備ができたときにキャッシュをプログラムで更新する方法もあります。

マニフェストには、CACHENETWORKFALLBACK という 3 つのセクションを指定できます。

CACHE:
これはエントリのデフォルト セクションです。このヘッダーの下(または CACHE MANIFEST のすぐ後)に列挙したファイルは、初めてダウンロードされた後、明示的にキャッシュされます。
NETWORK:
このセクションに列挙したファイルは、サーバーへの接続を必要とするホワイトリストに登録されるリソースです。ユーザーがオフラインでも、これらのリソースに対するすべての要求はキャッシュをバイパスします。ワイルドカードを使用できます。
FALLBACK:
リソースにアクセスできない場合のフォールバック ページを指定する、オプションのセクションです。最初の URI はリソースで、2 つ目のリソースはフォールバックです。いずれの URI にも、マニフェスト ファイルと同じ場所からの相対パスを指定する必要があります。ワイルドカードを使用できます。

: これらのセクションを指定する順序は自由です。また、1 つのマニフェストで各セクションを複数回指定することもできます。

次のマニフェストは、ユーザーがオフラインのときにサイトのルートにアクセスしようとしたときに表示される「キャッチオール」ページ(offline.html)を定義しています。また、その他すべてのリソース(リモート サイト上のリソースなど)がインターネット接続を必要とすることも宣言します。

CACHE MANIFEST
# 2010-06-18:v3

# Explicitly cached entries
index.html
css/style.css

# offline.html will be displayed if the user is offline
FALLBACK:
/ /offline.html

# All other resources (e.g. sites) require the user to be online. 
NETWORK:
*

# Additional resources to cache
CACHE:
images/logo1.png
images/logo2.png
images/logo3.png

: マニフェスト ファイルを参照する HTML ファイルは自動的にキャッシュされます。このような HTML ファイルをマニフェストに含める必要はありませんが、含めることを推奨します。

: HTTP キャッシュ ヘッダーと、SSL で提供されるページに課せられるキャッシュ制限は、キャッシュ マニフェストによって上書きされます。そのため、https 上で提供されるページもオフラインで使用できます。

キャッシュの更新

アプリケーションがオフラインになると、次のいずれかが発生するまで、キャッシュされた状態が保たれます。

  1. ユーザーがブラウザでサイトのデータ ストレージをクリアする。
  2. マニフェスト ファイルが変更される。注: マニフェストに列挙されているファイルを更新しても、ブラウザはそのリソースを再キャッシュしません。マニフェスト ファイル自体を変更する必要があります。
  3. アプリケーション キャッシュをプログラムで更新する。

キャッシュ ステータス

window.applicationCache オブジェクトを使用すると、ブラウザのアプリケーション キャッシュにプログラムからアクセスできます。status プロパティは、キャッシュの現在の状態を確認するために役立ちます。

var appCache = window.applicationCache;

switch (appCache.status) {
  case appCache.UNCACHED: // UNCACHED == 0
    return 'UNCACHED';
    break;
  case appCache.IDLE: // IDLE == 1
    return 'IDLE';
    break;
  case appCache.CHECKING: // CHECKING == 2
    return 'CHECKING';
    break;
  case appCache.DOWNLOADING: // DOWNLOADING == 3
    return 'DOWNLOADING';
    break;
  case appCache.UPDATEREADY:  // UPDATEREADY == 4
    return 'UPDATEREADY';
    break;
  case appCache.OBSOLETE: // OBSOLETE == 5
    return 'OBSOLETE';
    break;
  default:
    return 'UKNOWN CACHE STATUS';
    break;
};

プログラムでキャッシュを更新するには、まず applicationCache.update() を呼び出します。これにより、ユーザーのキャッシュの更新が試行されます(この処理のために、マニフェスト ファイルを変更しておく必要があります)。最後に、applicationCache.statusUPDATEREADY 状態の場合、applicationCache.swapCache() を呼び出すと、古いキャッシュは新しいキャッシュに置き換えられます。

var appCache = window.applicationCache;

appCache.update(); // Attempt to update the user's cache.

...

if (appCache.status == window.applicationCache.UPDATEREADY) {
  appCache.swapCache();  // The fetch was successful, swap in the new cache.
}

: このように update()swapCache() を使用しても、ユーザーに更新されたリソースは提供されません。このフローは、ブラウザが新しいマニフェストを確認し、マニフェストで指定されている更新されたコンテンツをダウンロードし、アプリケーション キャッシュの内容を再設定する処理を簡単に説明したものです。そのため、新しいコンテンツをユーザーに提供するには、2 回のページ再読み込みが必要です。1 回は新しいアプリケーション キャッシュをダウンロードするため、もう 1 回はページ コンテンツを更新するためです。

ただし、この 2 回の再読み込みという面倒な処理は回避することができます。最新バージョンのサイトに更新するには、ページの読み込み時に updateready イベントを監視するリスナーを設定します。

// Check if a new cache is available on page load.
window.addEventListener('load', function(e) {

  window.applicationCache.addEventListener('updateready', function(e) {
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
      // Browser downloaded a new app cache.
      // Swap it in and reload the page to get the new hotness.
      window.applicationCache.swapCache();
      if (confirm('A new version of this site is available. Load it?')) {
        window.location.reload();
      }
    } else {
      // Manifest didn't changed. Nothing new to server.
    }
  }, false);

}, false);

AppCache イベント

ご覧のように、キャッシュの状態を監視するためのイベントを追加しました。ブラウザは、ダウンロードの処理状況、アプリケーション キャッシュの更新、エラー条件などの状況に応じてイベントを発生させます。次のスニペットでは、各種のキャッシュ イベントに対応するイベント リスナーを設定しています。

function handleCacheEvent(e) {
  //...
}

function handleCacheError(e) {
  alert('Error: Cache failed to update!');
};

// Fired after the first cache of the manifest.
appCache.addEventListener('cached', handleCacheEvent, false);

// Checking for an update. Always the first event fired in the sequence.
appCache.addEventListener('checking', handleCacheEvent, false);

// An update was found. The browser is fetching resources.
appCache.addEventListener('downloading', handleCacheEvent, false);

// The manifest returns 404 or 410, the download failed,
// or the manifest changed while the download was in progress.
appCache.addEventListener('error', handleCacheError, false);

// Fired after the first download of the manifest.
appCache.addEventListener('noupdate', handleCacheEvent, false);

// Fired if the manifest file returns a 404 or 410.
// This results in the application cache being deleted.
appCache.addEventListener('obsolete', handleCacheEvent, false);

// Fired for each resource listed in the manifest as it is being fetched.
appCache.addEventListener('progress', handleCacheEvent, false);

// Fired when the manifest resources have been newly redownloaded.
appCache.addEventListener('updateready', handleCacheEvent, false);

マニフェスト ファイルまたはマニフェスト ファイルに指定されているリソースのダウンロードに失敗すると、更新全体が失敗します。このようなエラーが発生すると、ブラウザは以前のアプリケーション キャッシュを使用し続けます。

参考資料

Comments

0