[Firebase] Cloud Functionsの実行結果をCDNにキャッシュする

Firebaseで動的に情報を出力する場合にCloudFunctionsを利用するわけですが、頻繁に情報が変化しない場合など実行結果を一定時間キャッシュしたくなりますよね。Firebaseでは非常に手軽に実装できます。

結論から言うと以下のようにCache-Controlヘッダを出力するだけ。以下で30秒間Firebaseが用意するCDNに実行結果がキャッシュされます。publicのつけ忘れにご注意を。

res
  .set("Cache-Control", "public, max-age=30")
  .send("キャッシュされるよ");

最初これを知らずにCDNを別途用意して、Firebase側をオリジンに設定しようとしていましたw 危ない危ないw

サンプル

実行例

以下から実際に動作を確認できます。 https://miku3.net/cdnCacheTestmiku3.net

現在時間を出力するプログラムですが、CDNに30秒間キャッシュされるため再読み込みしてもその間は表示が変化しません。

コード

実行例のコードです。

exports.CDNCacheTest = functions.https.onRequest( (req, res) => {
  const now = new Date();
  const mm = ("00" + now.getMinutes()).slice(-2);
  const ss = ("00" + now.getSeconds()).slice(-2);

  res
    .set("Cache-Control", "public, max-age=30")
    .send(
        `<h1>${mm}:${ss}</h1>`
      + "<p><small>30秒間CDNにキャッシュします</small></p>"
    );
});

Cache-Controlヘッダ

これはFirebaseというよりHTTPの仕様ですが、今回のブログの内容に置き換えてメモしておきます。例えば「CDN」とある箇所は本来はプロキシなどその他の中間サーバも指します。

public

デフォルトでprivateが指定されているのですがprivateのままだとCDNにキャッシュされません。そのため必ずpublicを指定する必要があります。

private
ブラウザにのみキャッシュ期間を伝え、CDNではキャッシュしません。
public
ブラウザとCDNの両方にキャッシュしても良いことを伝えます。

max-ageとs-maxage

Cache-Controlヘッダでは以下のようにmax-ageとs-maxageの2つの値を指定できます。

res.set("Cache-Control", "public, max-age=300, s-maxage=600")

それぞれ以下のような意味を持ちます。

max-ageのみ指定
ブラウザとCDNの両方にキャッシュ期間を伝えます
s-maxageのみ指定
CDNにのみキャッシュ期間を伝えます
max-ageとs-maxageの両方を指定
ブラウザにはmax-age、CDNにはs-maxageで指定されたキャッシュ期間を伝えます

注意点

GET/HEAD以外はキャッシュされない

例えばPOSTやPUTなどはキャッシュされません。毎回Functionが実行されます。

ローカルサーバでの挙動

ローカルで確認用のサーバをfirebase serveで起動してFunctionを実行した場合、ローカルのサーバではキャッシュされませんが、cache-controlヘッダは出力されますのでmax-ageのみ指定している場合はブラウザのキャッシュが効いている可能性が高いです。

$ telnet localhost 5001
HEAD /test-f76bc/us-central1/CDNCacheTest HTTP/1.1
host: localhost

HTTP/1.1 200 OK
x-powered-by: Express
cache-control: public, max-age=30
content-type: text/html; charset=utf-8
content-length: 77
etag: W/"4d-O015JftnB9bMeC9SgNkQhV3dzSs"
date: Sat, 18 Apr 2020 10:07:01 GMT
connection: close

「あれ?ローカルなのに更新されてない?」と思ったら、シフトキーを押しながら再読み込みするかキャッシュを削除してからもう一度リクエストしてみてください。

参考ページ