Webのキャッシュの仕組み

HTTPの世界ではヘッダを使ってキャッシュを表現する方法がいくつかあります。

よくあるキャッシュの1つの仕組みとして、有効期限を設定してその範囲まではCacheを使い、越えたら再びリソースを取りにいく、というものがあります。このタイプのキャシュはExpireヘッダ、またはCache-Controlヘッダで主に表現します。

別のやり方として、条件付きGETと呼ばれる方法があり、これは自分が持っているCacheの状態を含めたGETリクエストを飛ばし、変更が無ければリソースの中身を省略して 304 Not Modified を返すものです。Cacheの状態を表現する方法には、リソースの更新日時を条件にしたIf-Modified-Sinceと、リソースのバージョンやHash値が一致するかどうかで判定するIf-None-Matchがあります。

このIf-None-MatchでCache状態の表現で使う値をETagと言い、これをPlayframeworkのControllerで扱う方法を紹介します。静的ファイルは自動でやってくれた筈なので対象にしません。

ETagの仕組み

まずはETagにどのような値を使うか考えます。リソースが変更されたら値が変わるようなものを使わないといけません。
リソース全体のHashを取るというのはシンプルな方法ですが、リソース全体をRDBなりから一度取得しないといけなくて無駄なのであまり良い方法とは言えません。idを絡めた方法などが考えられるでしょう。

Playframeworkで実装

ETagに使う値を決めたら、ETagとIf-None-Matchの値を比較し、一致しなければ新しいリソースを返すControllerを書きます。簡単に作れますがこんな感じになります。

def list = Action { implicit req =>
  val eTag = ...
  withETag(eTag) {
    createResource()
  }
}

def withETag(eTag: String)(content: => Result)(implicit req: Request[Any]): Result = {
  if (req.headers.get(IF_NONE_MATCH).contains(eTag)) {
    NotModified
  } else {
    content.withHeaders(ETAG -> s""""${eTag}"""")
  }
}