動的な html をキャッシュさせる
基本的にブラウザは画像などのリソースについてはキャッシュするのですが、 HTML はキャッシュの対象外です。
キャッシュさせるためには、サーバー側からブラウザにキャッシュ可能であることを通知する必要があります。
今回はその方法を紹介します。
なぜ Etag ではなく Last-Modified なのか?
キャッシュさせる方法として Etag という方法もあります。
こちらはハッシュ値等をやり取りし、前回のハッシュと差分があった場合にキャッシュを利用する、という利用目的で使うことが多いです。
動的な HTML では基本的にハッシュを取ることが難しいため、
時間を扱う Last-Modified で管理したほうが実装がしやすいのです。
手順
- 最初のリクエスト時、HTMLレスポンスヘッダーに Last-Modified を設定して送信する
- 次回以降のリクエストヘッダに If-Modified-Since がついてくるので、読み取る
- 現在時刻と比較し、一定時間経過したか計測する
- ↓
- 一定時間経過していなければ、ステータスコード304でレスポンスする
- 一定時間経過していれば、HTMLを生成して返す
サンプル
下記は、最初にページをロードした後10秒間だけHTMLをキャッシュさせる例です。
時刻のパースやフォーマットは癖がありますが、すべてのユーザーが10秒間キャッシュの恩恵を受けられます。
package main
import (
"flag"
"fmt"
"io"
"net/http"
"os"
"time"
)
var port = flag.String("http", ":8888", "HTTP port number.")
func init() {
flag.Parse()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
sinceText := r.Header.Get("If-Modified-Since")
if sinceText != "" {
// JSTのまま time.Time にパースする
loc, _ := time.LoadLocation("Asia/Tokyo")
since, err := time.ParseInLocation("Mon, 02 Jan 2006 15:04:05 JST", sinceText, loc)
// 10秒後の時間
nowless10 := time.Now().Add(-10 * time.Second)
// いろいろ表示
fmt.Println()
fmt.Println(since, err)
fmt.Println(nowless10)
fmt.Println(since.Before(nowless10))
// 前回の記録から10秒以内であればキャッシュを読ませる
if since.After(nowless10) {
w.WriteHeader(http.StatusNotModified)
return
}
}
// HTMLの生成に3秒かかる想定
time.Sleep(time.Second * 3)
// Last-Modified を設定
now := time.Now().Format("Mon, 02 Jan 2006 15:04:05 MST")
w.Header().Set("Last-Modified", now)
f, _ := os.Open("index.html")
io.Copy(w, f)
})
}
func main() {
fmt.Printf("Start HTTP Server http://localhost%s/", *port)
fmt.Println(http.ListenAndServe(*port, nil))
}
※ // いろいろ表示 というコメントの箇所は削除してもOKです。
※ 動作確認の際は適当に作った index.html を用意してください。
以上です。