Go言語でシンプルなHTTPキャッシュヘッダーの作り方

ブラウザからWebページにアクセスすると、画像やCSSファイルなどをダウンロードしてページとして表示しますよね。しかし特にCSSファイルは、1つのWEBサイトの全てのページで同じものが使われることが多いですよね。

同じCSSファイルなのにアクセスのたびにダウンロードしていては、ブラウザにもサーバーにも負荷がかかります。でも、HTTPキャッシュヘッダー(ブラウザキャッシュ)をサーバー側で設定しておくと負荷を減らせます。

HTTPキャッシュヘッダーとは簡単に解説すると「このファイルは何日の何時何分までは有効だから、いちいちサーバーにからダウンロードしなくても前にダウンロードしたのを使っておいてね!」ってことです(笑)

有効期限を過ぎたら、新鮮なデータに置き換える必要があるのでサーバーから再度ダウンロードし直すわけです。

Go言語では、このような仕組みを簡単に用意することが出来るので、そのコードをご紹介していきます。

コンテンツの転載は固くお断りいたします。

HTTPキャッシュヘッダーを設定するコード

コードは非常にシンプルです。なお、このコードは『Go言語でシンプルで簡単なHTTPサーバーの作り方』や『Go言語で複数ドメインにも対応可能なHTTPSサーバーの作り方』の記事で紹介したサーバープログラムの延長線上にある機能です。

上記の記事で書いている内容は省いて説明していきますので、分からないことがあれば上記の記事を見れば解決するかもしれません。


import (
	"net/http"
	"time"
)

func handler(w http.ResponseWriter, r *http.Request) {
	now := time.Now().AddDate(0, 0, 10)

	w.Header().Set("Cache-Control", "max-age=864000, public")
	w.Header().Set("Last-Modified", now.Format(http.TimeFormat))
}

上記のコードはクライアントから接続があった際に呼び出されるハンドラーで、サーバープログラムに組み込むと、以下のようなヘッダーを作成します。

上の写真は当ブログのトップページをWEBサイト解析サイトの『GTmetrix』で確認した時のもので、注目して頂きたいのは『Cache-Control』と『Last-Modified』の部分です。

上記のコードは当ブログでも似たものを利用しているので、無事にHTTPキャッシュヘッダーが設定されているのを確認できますね。

コードの解説

では、コードを解説していきます。

必要なパッケージを指定


import (
	"net/http"
	"time"
)

『net/http』はHTTP関連のパッケージで、『time』は時間関連のパッケージです。

現在時刻から10日先の日時を計算する


now := time.Now().AddDate(0, 0, 10)

まず『time』パッケージの『Now』関数を使って現在時刻を取得し、そこに『AddDate』関数を使って年月日を加算します。

『AddDate』関数は3つの引数を指定することが可能で、1つ目は加算する年数、2つ目は加算する月数、3つ目は日数です。今回はキャッシュ有効期間を10日都するので、第三引数に10をセットしています。

Cache-Control ヘッダをセットする


w.Header().Set("Cache-Control", "max-age=864000, public")

HTTPヘッダーに設定をする際は『http.ResponseWriter』の『Header』関数を使って、そこに『Set』関数を使います。

第一引数にヘッダーの種類、第二引数に文字列です。今回は第一引数に『Cache-Control』を設定し、第二引数に『max-age=864000, public』を指定しました。

『public』はほぼ必須で、『max-age』の後には有効期間の秒数を指定します。今回は10日間なので『60秒×60分×24時間×10日』で864000秒です(GTmetrix画像では7日分を指定しています)

Last-Modified ヘッダをセットする


w.Header().Set("Last-Modified", now.Format(http.TimeFormat))

次に『Last-Modified』ヘッダを指定し、有効期限の部分は先ほど計算した時刻『now』に『Format』関数を使って時刻をフォーマットします。

フォーマット形式は『http.TimeFormat』です。これを指定することで『Fri, 13 Oct 2017 02:59:13 GMT』のような文字列を出力できます。

Cache-Control と Last-Modified の違い

どちらもHTTPキャッシュヘッダーなのですが、Cache-Controlは有効期間内はサーバーに一切問い合わせをしないのに対し、Last-Modifiedはファイルが更新されているかどうかだけサーバーに確認します。

その際に、Last-Modified ヘッダで渡された日時をサーバーに『If-Modified-Since』ヘッダとして送信します。

通常はこの2つのヘッダを指定しておくと問題ないでしょう。

If-Modified-Since ヘッダの解析


t, err := time.Parse(http.TimeFormat, r.Header.Get("If-Modified-Since"))

If-Modified-Since ヘッダはクライアントから送信するヘッダなので、サーバー側で処理する必要があります。

『time』パッケージの『Parse』関数を使い、第一引数に『http.TimeFormat』、第二引数に『http.Request』の『Header』の『Get』関数を使い『If-Modified-Since』ヘッダを取得します。

これで『time』パッケージの『Time』形式で日時を取得できるので、それを元に対象ファイルの最終更新日時と比較します。

『If-Modified-Since』の日時より最終更新日時の方が新しければ、新しいデータをクライアントに送信し、更新する必要が無ければ『301レスポンス(Not Modified)』を送信します。

静的コンテンツなら ServeContent 関数が良さそう

『net/http』パッケージに『ServeContent』関数というものがあり、これは自動的に『If-Modified-Since』ヘッダなどを取得して更新日時と比較し、コンテンツを送信する必要があるか判断して処理してくれるようです。

詳しくは『ServeContent関数の Go Doc』をご覧ください。

終わりに

当記事のコードや仕組みを実装するだけで、サーバーの負荷は減りますので是非試してみてくださいね。