先日、CloudflareとActivityPubプラグインは混ぜるな危険?キャッシュに要注意という記事を書いた。かいつまんで説明すると、ActivityPubで配信していたJSONがCloudflare CDNにキャッシュされてしまい、ブラウザでブログにアクセスすると記事が表示されなくて困った、という内容だ。この時はCDNキャッシュを無効にすることで凌いだが、キャッシュとActivityPubを両立する上手い解決法を見つけた(かもしれない)ので紹介しておく。

  1. 通常のブラウザからのアクセスのみキャッシュさせる
  2. CloudflareのCache Rulesを用いてブラウザ以外のアクセスはキャッシュをバイパスする

この方法だと上手くいく気がする。

まず、1の「通常のブラウザからのアクセスのみキャッシュさせる」について。ActivityPubで配信しているActivity Streams 2.0のJSONはキャッシュされると困るが通常のHTMLはCDNにキャッシュしてほしい。そこでブログへのリクエストがActivityPubによるものか、通常のアクセスなのかを判定して適切なCache-Controlを設定する必要がある。ActivitiyPubによるリクエストはAcceptヘッダーにapplication/ld+json; profile=”https://www.w3.org/ns/activitystreams”やapplication/activity+jsonでリクエストされるらしいからAcceptヘッダーを見ると良さそうだ。簡単な例を挙げるとこんな感じ。

<?php
if (isset($_SERVER['HTTP_ACCEPT'])) {
	$ha = strtolower($_SERVER['HTTP_ACCEPT']);
	if (strpos($ha, 'application/ld+json') !== false || strpos($ha, 'application/activity+json') !== false) {
		#キャッシュさせない
		header('Cache-Control: private, must-revalidate, no-cache');
	} else {
		#キャッシュさせる
		header('Cache-Control: max-age=1800, public');
	}
}

CDNのキャッシュコントロールは自前のプラグインでやっていたので問題なかったが、既存のキャッシュ系のプラグインを使っている人はwp_headersをhookすれば良さそうだ。

そして、2の「CloudflareのCache Rulesを用いてブラウザ以外のアクセスはキャッシュをバイパスする」について。Cache RulesとはCookieやURIなどの値を条件にキャッシュが有効な期間を設定したり、一時的にキャッシュを無効化したりできる機能のようだ。嬉しいことに無料ユーザーでも10件のルールが作成できる。

さて、上記の1で既にHTMLをキャッシュしているはずなので、ActivityPubでのリクエストではこのキャッシュを無視して新しくオリジン(WordPress)にアクセスしてほしい。残念なことにCache RulesではAcceptヘッダーを条件にすることができないので、User Agentで判断することにした。つまり、通常のブラウザからのアクセスと区別するためにUser-Agentに「Mozilla/5.0」を含まないアクセスではキャッシュを一時的に無効にすることにした。ちなみに歴史的経緯により、一般的に使われているブラウザは全て「Mozilla/5.0」から始まっている

cache rulesの設定画面 ブラウザからのアクセス以外はキャッシュをバイパスしている

このように設定し、ブラウザ以外からのリクエストはキャッシュをバイパスすることで安全にHTMLをキャッシュできるはずだ。ちなみに「Mozilla/5.0」を含むUser-AgentでActivityPubでのアクセスがあるとうまく動作しないことになる。サーバーやbotからのアクセスでブラウザに偽装する意味はなさそうなので大丈夫だと思うが、将来が不確実でモヤモヤする。ログを調べたところ、少なくともマストドンのサーバーからのアクセスでは”http.rb/4.4.1 (Mastodon/3.4.1 Fedibird/0.1; +https://fedibird.com/)”のようにUser-Agentが設定されていた。

設定が終わったら実際にアクセスしてテストしてみた。

$ { curl -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0' -I https://blog.srytk.com/aquei/851.html 2>/dev/null; curl -H 'User-Agent: I AM BOT !!!!' -H 'Accept:
application/ld+json; profile="https://www.w3.org/ns/activitystreams"' -I https://blog.srytk.com/aquei/851.html 2>/dev/null; } | grep CF-Cache
CF-Cache-Status: HIT
CF-Cache-Status: DYNAMIC

ブログ記事にブラウザのUser-AgentでアクセスするとCloudflareのキャッシュから配信されているが、”I AM BOT !!!!”というUser-AgentでActivityPubでアクセスするとDYNAMIC、つまりWordpressからコンテンツが再取得された。うん、これはうまくいきそうだ。この記事で実際に投稿してみてうまく動くかテストしてみよう。うまくいくといいな!