SouinでCaddyをキャッシュサーバーにしなち
2024/09/28追記:おそらく最近(v1.7.0)のSouinはCache-Control: max-age=0
やCache-Control: no-cache
があっても正常に動くようになったので、Minimal Configurationにdefault_cache_controlを追加するだけでよさそうです。
{
cache
}
localhost:80 {
cache {
default_cache_control public, max-age=5, must-revalidate
}
reverse_proxy 127.0.0.1:8080
}
以降紹介しているCaddyfileの情報は2023/04/02当時の情報ですので注意してください。
Cache
キャッシュサーバーといえばVarnishを使う人が多いのではないのでしょうか。
他にも簡単に使えるApacheのmod_cacheやNginxのproxy_cacheなんかもあります。
ではこのCaddyにもキャッシュサーバー用Moduleが存在するのでは……と思い、探してみたところありました。
Souin cacheをベースにしたCaddy用の分散型HTTPキャッシュモジュールです。
どうやらSouinというHTTPキャッシュシステムをベースにしているようです。
元々Caddy独自でCache Moduleを作っていたそうですが、RFC互換で、Vary, Request Coalescing, Stale Cache-Control, RFC7234に関連する仕様に対応しているSouinを取り込むことにしたそうです。
Cache-Status HTTPレスポンスヘッダや、VarnishなどのYKeyグループ、go-esiパッケージにより、ESIタグもサポートしています。
Souin本家とCaddy Cache Handler Moduleとの違いはほとんどなく、CaddyのOrganizationで管理しているかどうかの違いぐらいだそうです。
ただ両方ともCaddyのModuleとして使用することができますし、Souin本家の方が積極的にアップデートされるため、私はSouin本家のModuleを使ってみることにしました。
ちなみにSouinは単体でバイナリを配布しており、単体でもリバースプロキシやキャッシュサーバーとして機能しますが、Souin独自の設定ファイルconfiguration.ymlを書く必要があるので、Caddyのリバースプロキシを使いたい人やCaddyfileにまとめたい人はCaddyのModuleを使ったほうが良さそうです。
その他にも様々なGo Webフレームワークと統合したり、Cache StorageとしてBadger, Etcd, NutsDB, Olric, Redisを選択できたりものすごく多機能です。
全部説明しても仕方ないので詳しくはSouinのREADME.mdを読んで下さい。
Souin
早速SouinのCaddy ModuleをCaddyに追加してみます。
$ xcaddy build --with github.com/darkweak/souin/plugins/caddy
README.mdに記載されている上記の通り、Custom Caddy Builderのxcaddyを使用しても追加できますが、公式のダウンローダーでも追加できるので今回はこちらの方法を試してみます。
また、CaddyをWindowsのローカル環境で普段使っているので、Windowsのローカル環境で試してみたいと思います。
ダウンロードページへ行き、darkweak/souin/plugins/caddyを選択します。
「Extra features: 1」になっていることを確認したら、「Download」ボタンを押し、しばらく待つとダウンロードが始まります。
> caddy version
v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=
> caddy list-modules
...
Standard modules: 100
cache
http.handlers.cache
Non-standard modules: 2
Unknown modules: 0
caddy list-modules
コマンドを使い追加されていることを確認できました。
以下のページで使い方を確認してみます。
{
order cache before rewrite
}
localhost:80 {
cache {
ttl 5s
}
reverse_proxy 127.0.0.1:8080
}
Caddyfileを少し修正しましたが、このように書けば動くっぽい?
SouinはデフォルトのTTLが2分なのでテスト用に5秒にしました。
> deno --version
deno 1.32.3 (release, x86_64-pc-windows-msvc)
v8 11.2.214.9
typescript 5.0.3
import { serve } from "https://deno.land/std@0.182.0/http/server.ts";
serve((_req) => new Response(String(Math.floor(Date.now() / 1000))), {
hostname: "127.0.0.1",
port: 8080,
});
DenoでUnixtimeを返すWebサーバーをサクッと作りました。
> deno run --allow-net server.ts
> caddy run
> curl -i http://localhost/
コマンドプロンプトを3枚用意してcurlでテストします。
> curl -i http://localhost/
HTTP/1.1 200 OK
Cache-Control:
Cache-Status: Souin; fwd=uri-miss; stored; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 13:45:25 GMT
Server: Caddy
Vary: Accept-Encoding
1680443125
> curl -i http://localhost/
HTTP/1.1 200 OK
Age: 2
Cache-Control:
Cache-Status: Souin; hit; ttl=3; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 13:45:25 GMT
Server: Caddy
Vary: Accept-Encoding
1680443125
> curl -i http://localhost/
HTTP/1.1 200 OK
Cache-Control:
Cache-Status: Souin; fwd=uri-miss; stored; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 13:45:30 GMT
Server: Caddy
Vary: Accept-Encoding
1680443130
ちゃんと5秒間キャッシュされていることがわかります。
ブラウザで確認してみましょう。
……あれ?
キャッシュされていない???
原因はリクエストヘッダーにいるこいつです。
Cache-Control: max-age=0
curlでもこのように叩けばキャッシュされていないことがわかります。
> curl -i -H "Cache-Control: max-age=0" http://localhost/
詰んだか???
Caddy
CaddyのIssuesを確認したところ全く同じ問題を見つけました。
どうやらDirective Orderを使い、先行してCache-Controlのリクエストヘッダーを書き換えてしまえばよさそうです。
Cache-Controlをすべて消してしまうと、ChromeやEdgeでCtrlキー + F5キーを押したとき(いわゆるスーパーリロード)や、DevToolsのネットワークタブの「キャッシュを無効化」にチェックを入れたとき送信されるリクエストヘッダーのCache-Control: no-cache
も消してしまいキャッシュを更新したくても更新できなくなるので、max-age=0の値をTTLと同じ値へ置換することにしました。
{
order request_header before rewrite
order cache before rewrite
}
localhost:80 {
request_header Cache-Control max-age=0 max-age=5
cache {
ttl 5s
}
reverse_proxy 127.0.0.1:8080
}
これで完璧です。
> curl -i -H "Cache-Control: max-age=0" http://localhost/
HTTP/1.1 200 OK
Cache-Control:
Cache-Status: Souin; fwd=uri-miss; stored; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 14:03:42 GMT
Server: Caddy
Vary: Accept-Encoding
1680444222
> curl -i -H "Cache-Control: max-age=0" http://localhost/
HTTP/1.1 200 OK
Age: 2
Cache-Control:
Cache-Status: Souin; hit; ttl=3; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 14:03:42 GMT
Server: Caddy
Vary: Accept-Encoding
1680444222
> curl -i -H "Cache-Control: max-age=0" http://localhost/
HTTP/1.1 200 OK
Cache-Control:
Cache-Status: Souin; fwd=uri-miss; stored; key=GET-http-localhost-/
Content-Length: 10
Content-Type: text/plain;charset=UTF-8
Date: Sun, 02 Apr 2023 14:03:47 GMT
Server: Caddy
Vary: Accept-Encoding
1680444227
あとはTTLとあわせてCache-ControlやVaryを各自調整して使ってください。
Ref
VarnishやESIなど、RFC以外のCacheに関する情報は全ていわなちゃんさんのブログを参考にしました。
ありがとうございました。
Discussion