Varnishでレスポンスをカスタマイズする方法

表題の方法を実現したくなって、調べたところ実現する方法が結構面白かったのでメモ。

例えば以下のような「キャッシュ破棄リクエストを受け付けて200を返す」というVCLがあったとする。

sub vcl_recv {
    if (req.method == "BAN") {
        ban("req.http.host == " + req.http.host + " && req.url == " + req.url);

        return(synth(200, "Ban added"));
    }
}

この状態で curl -XBAN localhost:6081 を実行してみると、レスポンスは以下のように Ban added だけではなく他のHTMLタグや諸々が含まれて返ってくる。

<!DOCTYPE html>
<html>
  <head>
    <title>200 Ban added</title>
  </head>
  <body>
    <h1>Error 200 Ban added</h1>
    <p>Ban added</p>
    <h3>Guru Meditation:</h3>
    <p>XID: 196749</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

そしてこのレスポンスをもし Ban added だけにしたい場合にどのようにするとよいかという話。

結論としては、この状態から以下のように vcl_synth を変更することで実現できる。

sub vcl_synth {
    if (req.method == "BAN") {

        synthetic(resp.reason);

        # これも上と同等の処理
        # set resp.body = resp.reason;

        return (deliver);
    }
}

結構不思議な感じだけど、これで何故動くのかを説明していく。

まず synthetic 関数について、公式ドキュメントには以下のように説明されており、レスポンスをセットする関数になっている。

synthetic(STRING) Prepare a synthetic response body containing the STRING. Available in vcl_synth and vcl_backend_error. Identical to set resp.body / set beresp.body.

https://varnish-cache.org/docs/6.0/reference/vcl.html#synthetic-string

そして resp.reason について。

return synthした際の2つの引数は resp.status , resp.reason にそれぞれセットされる。*1

今回はそのうちの resp.reason を使って参照し、レスポンスを Ban added だけに書き換えることができたという理由になる。

ちなみに注意事項として、synthetic を呼ぶ際は vcl_recv でreturnしたときと同じ条件で囲わないとBANメソッド以外の場合にもレスポンスが書き換わってしまうというのがあるため、もし元々のHTMLも返したい場合には気をつける必要がある。

これを応用してこのような固定のレスポンスを返すだけではなく、例えばリクエストの User-Agent をレスポンスとして返すみたいなことも以下のようにして実現できる。

return(synth(200, req.http.User-Agent));

もちろんVarnishはキャッシュサーバなので複雑なレスポンスを組み立てて返すというのは向いていないけれど、例えばキャッシュ破棄のようなロジックを入れる際にレスポンスを少しカスタマイズする事はあると思う。

例えばvmod_xkey*2を使ってタグベースのキャッシュを行った際に、キャッシュ破棄できた数を返すことで「複数のVarnishサーバで合計でどのくらいキャッシュ破棄できたのか」の集計がしやすくなるなどがありそう。