Pocket

こんにちは t.yamagishiです。

今回はWeb Storageを仕様的、技術的に掘り下げてみようと思います。

Web Storageとは

Web Storageを簡単に説明すると、ウェブ・クライアントに key-value ペアの永続的なデータを蓄積するための API です。

Web Storageを使えば、こんなことができるようになります。

  • オフラインでもWEBメールを保存できる(そしてオンラインになった時に自動的にサーバに送信)
  • 数MB程度のデータを扱うだけであれば、サーバがなくてもブラウザだけでアプリケーションが作れる

そしてその仕様はW3Cが作成していますが、まだ正式に承認されていません。

http://www.w3.org/TR/webstorage/

正式ではないものの、一般的なブラウザは勧告案に基づいてWeb Storageを実装しています。

各ブラウザの対応状況を見てみましょう。

デスクトップブラウザ

アプリ名 バージョン
IE 8以降
Firefox 3.6以降
Chrome 8以降
Safari 5以降
Opera 11以降

モバイルブラウザ

アプリ名 バージョン
iOS 5以降
Android 3以降 ※1
Windows Phone 9以降
Opera Mini ×
Opera Mobile 11以降

※1 WebViewコンポーネントではAndroid 2.0(API Level5)から有効にする為のAPIが追加

普段使われているブラウザは大体対応していることになりますね。

HTML5との関係

HTML5とセットで紹介されることが多いWeb Storageですが、2009年4月23日の草案「 HTML 5 differences from HTML 4」の中で、HTML5と切り離すと定義されています。
http://www.w3.org/TR/2009/WD-html5-diff-20090423/

HTML5に対応しているのにWeb Storageが使えなかったり、逆にHTML5に対応していないけどWeb Storageが使えても不思議ではないのです。
Android 3から標準ブラウザがHTML5に対応されていますが、アプリ内でブラウザとして使われるWebViewコンポーネントでは Android 2.0(API Level5)からWeb Storageに関するAPIが追加されているのは、こういう理由からなのかもしれません。

http://developer.android.com/reference/android/webkit/WebStorage.html
実際使えるかどうかは

var ss = window.sessionStorage;
var ls = window.localStorage;
alert('sessionStorage:' + ss + ":localStorage:" + ls);

としてみて、Objectが戻ってきたら対応していると判断していいでしょう。

掘り下げてみよう

Web Storage APIを使うと、何かしらのデータをローカルのストレージに保存することができます。
「Web」とついているので、ちょっと混乱しますが、サーバではなく個々の端末に保存されます。
ローカルに保存する機能としてCookieもありますが、わざわざ新しい機能として追加されたことには意味があります。
Cookieはセッション管理などウェブサーバがブラウザに対してその状態を管理するために作られました。そのため、ブラウザはリクエストに対して、毎回Cookieの情報をウェブサーバに送ります。
また、Cookie自体単一のトランザクションを想定した機能であり、複数ウィンドや近年標準化しつつあるタブなどによる、並列処理を行うことで当初のブラウザの状態管理が難しくなってきました。
これを解決するためにWeb Storageという機能が生まれたのです。

ではWeb Storageについて詳しく説明していきましょう。
Web Storageには「sessionStorage」と「localStorage」のが存在します。

W3Cでは、Storageインターフェイスを実装するものと定義され、さらに各Storageによって、メソッドの振る舞いに違いがあります。

http://www.w3.org/TR/webstorage/#the-storage-interface

interface Storage {
 readonly attribute unsigned long length;
 DOMString? key(unsigned long index);
 getter DOMString getItem(DOMString key);
 setter creator void setItem(DOMString key, DOMString value);
 deleter void removeItem(DOMString key);
 void clear();
};

まず「sessionStorage」と聞いて、CookieとセッションIDによるウェブサーバ側のセッション管理が頭に浮かんだ人もいるでしょう。

セッション 【 session 】
http://e-words.jp/w/E382BBE38383E382B7E383A7E383B3.html

先にも書きましたが、Web Storageはローカルにデータを保存する機能である為、このセッションとは意味が違います。
このセッションが接続/ログインから切断/ログオフだとすると、sessionStorageのセッションはブラウザの起動/タブを開くからブラウザの終了/タブを閉じるまでになります。
またブラウザのウインドウ/タブ毎に別セッションとして管理されます。注意してほしいところは、従来のセッションとは別ということです。例えば、複数起動して操作した場合、セッションIDは同じでもsessionStorageから取得できる値は別ということです。

次に「localStorage」ですが、こちらはブラウザを終了してもデータが消えることはありません。次回起動時や別ウインドウからでも同じデータを参照することができます。
ただし、無条件に参照できてしまうということはなく、「プロトコル://ドメイン名:ポート番号」(W3Cではoriginと表記されています)が同じサイト内でしか参照することはできません。このorigin毎のアクセス制限はsessionStorageも同様です。

今までのことをまとめると

参照範囲 保存容量 有効期間
Cookie domain,path単位 4KB 期間指定
sessionStorage ウインドウ,origin単位 5MB/origin※2,3 ブラウザの終了/タブを閉じるまで
localStorage origin単位 5MB/origin※2,3 永続的

※2 W3Cの推奨であり制限ではない
※3 制限を超過した場合、「QuotaExceededError」例外が発生する

Web Storageを使ってみよう

では実際にWeb Storageを使ってみましょう。
先ほど紹介したStorageインターフェイスを実装したオブジェクトを使うことになります。

  • 実行にはChromeを利用しています
  • サーバとしてTomcatをローカルにインストールしてデフォルトで作成される「examples」にHTMLを配置しています
  • 投稿時Chromeはhttp://ではなくfile://で実行してもWeb Storageを使えますが、IEでは使えませんでした
// sessionStorageに対して値を設定する
sessionStorage.setItem('name', 't-yamagishi');
// sessionStorageから値を取得する
var value = sessionStorage.getItem('name');
alert('name = ' + value);

※”sessionStorage”は予約されている為、わざわざ「window.sessionStorage」を参照する必要はありません

上記を実行すると、こんな感じのダイアログが表示されます

これだけでは、本当に設定されているのか、わからないので実際に設定されているか確認してみましょう。
Google Chromeの設定 > ツール > デベロッパーツールを選択して、Resourcesタブを選択してください。


こんな画面が表示されるので、赤線の「http://localhost:8080/」をクリックしてください。
※ここからもorigin毎にデータが保存されているのがわかりますね


Key列に「name」Value列に「t-yamagishi」が設定されていることがわかります。
ちなみに、Local Storageの項目には何も設定されていません。

とても簡単にデータの出し入れができました。
次にlocalStorageも追加して、sessionStorageとlocalStorageの挙動の違いを見てみましょう。

<html>
<script type="text/javascript">

var COMMENT = 'comment';
var SESSION = 'session';
var LOCAL = 'local';
var NAME = 'name';

var comment;
var sessionComment;
var localComment;

function initialize() {
  // 初期処理
  comment = document.getElementById(COMMENT);
  sessionComment = document.getElementById(SESSION);
  localComment = document.getElementById(LOCAL);

  // 初期表示時にストレージに設定されている値をセットします
  // sessionStorage,localStorage というキーワードは予約されています
  setComment(sessionStorage, sessionComment);
  setComment(localStorage, localComment);

  //初期データ投入
  sessionStorage.setItem(NAME, SESSION);
  localStorage.setItem(NAME, LOCAL);

  // コメントが更新されるたびにストレージを更新する
  comment.addEventListener('keyup',function(event){
      localStorage.setItem(COMMENT, comment.value);
      sessionStorage.setItem(COMMENT, comment.value);

      //同時にテキストにストレージの値をセットします
      setComment(sessionStorage, sessionComment);
      setComment(localStorage, localComment);
  },false);
}

window.onload = function() {
     initialize();
}
// Storageイベントが発生したらハンドリングする
window.onstorage = function(event){
    if (event.storageArea.getItem(NAME) == LOCAL) {
      localComment.value = event.newValue;
    } else if (event.storageArea.getItem(NAME) == SESSION) {
      sessionComment.value = event.newValue;
    }
}

// ストレージからコメントを取得してエレメントに設定
// インターフェイスが同じなので、呼出元のストレージ次第で処理は同じ
function setComment(storage, element) {
    var text = storage.getItem(COMMENT);
    element.value = text;
}

</script>

<body>
  Comment<input id="comment" type="text">
  <table>
    <tr>
      <th>SessionStorage</th>
      <td>
        <input id="session" type="text" size="50" readonly>
      </td>
    </tr>
    <tr>
      <th>LocalStorage</th>
      <td>
        <input id="local" type="text" size="50" readonly>
      </td>
    </tr>
  </table>
</body>
</html>

まず、タブを二つ新規に開きます。

初回はStorageに何も設定されていないので、「SessionStorage」「LocalStorage」テキストには何も表示されません。

次に左側のタブのコメント欄に文字を入力します。
入力された文字はStorageを経由して「SessionStorage」「LocalStorage」テキストにコピーされます。

そして、右側のタブを見てみましょう。

何もして操作していないのに「LocalStorage」テキストに「左側」と表示されました。

でも、「SessionStorage」には空白のままです。
これは、43行目

window.onstorage

Storageイベントが発生した時に、各テキストを変更しています。
Storageイベントとはストレージ領域が変化した時に発生するイベントで、下記インターフェイスを実装しています。

http://www.w3.org/TR/webstorage/#the-storage-event

[Constructor(DOMString type, optional StorageEventInit eventInitDict)]
interface StorageEvent : Event {
  readonly attribute DOMString key;
  readonly attribute DOMString? oldValue;
  readonly attribute DOMString? newValue;
  readonly attribute DOMString url;
  readonly attribute Storage? storageArea;
};

dictionary StorageEventInit : EventInit {
  DOMString key;
  DOMString? oldValue;
  DOMString? newValue;
  DOMString url;
  Storage? storageArea;
};

W3Cではストレージ領域「SessionStorage」「LocalStorage」に変化が起こった場合にイベントが発生するとありますが、Chromeでは「LocalStorage」のみ実装されているようです。
「SessionStorage」が変更されたときに、他のタブと同期を取るなんてことは現時点ではできないようです。
そもそも「SessionStorage」はセッション別にデータを管理するための仕様ですからね。同期を取りたいのであれば「LocalStorage」を使いましょう。

次に、右側のタブのコメント欄を入力してみましょう。

「SessionStorage」「LocalStorage」ともに「右側」となりました。

そして、左側のタブを見てみましょう。

「LocalStorage」だけ「右側」となりました。

最後に一旦タブを終了し、再度開いてみましょう。

「LocalStorage」に最後に入力した「右側」の文字が表示されました。

 Web Storageの使いどころ

実際に動かしてみてその違いが分かったと思います。

SessionStorageは、テンポラリ的な使い方に向いています。例えば、ショッピングカードやページ送りがある一覧画面など。

一方LocalStorageは、意図的に削除しなければ消えないことから、個の追跡やオンラインから切り離してオフラインで操作する場合の保存先などに便利です。

Web Storageは単にローカルにデータを保存するという意味合いのほかに、今までは入力したデータをページ間で持ち回るには、一旦サーバへ送信しなければならないところを、Web Storageを使うことによってそういったしがらみから解放され、サーバが本当に必要なときだけリクエストに含めればよくなったという意味もあります。必要なときに必要な情報だけサーバへ送信することは、ネットワークトラフィックの軽減、オフライン時の動作保障など、ネットワークにつながっていることが前提のWebアプリの概念を変える仕様でもあります。

 最後に

いかがでしたでしょうか。今回はWeb Storageというローカルにデータを保存する技術を紹介しましたが、「Offline Web Applications」という、Webアプリ自体をキャッシュする仕様もW3Cは作っています。

http://www.w3.org/TR/offline-webapps/

これらを組み合わせることで、「Webアプリだけど通信をほとんど行わない」なんてこともできます。

それはまたの機会に紹介したいと思いますので、お楽しみに。