🧊

Cloudflare Workersとレスポンスの圧縮

経緯としては、

  • CFWで作ったとあるAPIのレスポンスヘッダを眺めてた
  • `content-encoding: br`になってたのを見つけた
  • コードの中では特に何もしてない
  • WorkerのDocsにも特にそれらしい記載はなかった
  • そういえばこれってどこで圧縮してんの?

って思ったのがきっかけ。

Cloudflareが自動で圧縮する

https://support.cloudflare.com/hc/en-us/articles/200168396-What-will-Cloudflare-compress

Workerから返すレスポンスの`content-type`ヘッダが、このページにリストされてるものだった場合、それは自動的に`gzip`と`brotli`して返してくれるとのこと。

そしてこれはどうやらWorkerより外側のレイヤーで行われるらしく、Workerのコードとしては適切な`content-type`を設定するほかやることはない。
ちなみに、あまりに小さいレスポンスは無圧縮になってた。

もちろん、リクエストする側として`accept-encoding`に`gzip`か`br`があることが前提。`deflate`だけとか未指定とかだとそのまま落ちてくる。(モダンなブラウザならそんな心配はないけど)

というわけで、レスポンスの圧縮はWorkerではなくその外側でやってくれる。なので、Workerからあえて`content-encoding`を指定して返す理由はない、と。

そもそも、Workerのコードで`headers.get("accept-encoding")`しても、`gzip`しか返ってこない・・。

Cloudflare Workers lies about request Accept-Encoding - Workers - Cloudflare Community

ただし、20xの場合のみ

どうやら40xとか50xをWorkerから返したときは、`content-type`の設定だけでは圧縮してくれない風だった。

Cloudflare 503 Status Maintenance Page worker compressed HTML response? - Workers - Cloudflare Community

このフォーラムによると、`content-type`に加えて`cotnent-encoding`を自分で指定すれば、20xじゃないレスポンスでも圧縮してくれるらしい。

ただ手元でやってみたところ、

  • Workerのレスポンスで`content-encoding: gzip`を設定すると
    • `accept-encoding: gzip`なリクエストに対しては、`gzip`で圧縮したものが返る
    • `accept-encoding: gzip, br`なリクエストに対しては、圧縮してないものが返る
  • Workerのレスポンスで`content-encoding: br`を設定しても
    • どうリクエストしても圧縮してないものが返る

というよくわからない挙動だった。つまりブラウザ向けには、20xじゃないレスポンスの場合、自動で圧縮したレスポンスは返せないということ・・?指定しなくていいんじゃなかったの・・?

自分で圧縮してた場合はどうする

自動で圧縮してくれるがゆえに、たとえばKVなんかから自分たちで既に圧縮してあるものを返したい場合、二重で圧縮されてしまうという問題があるらしい。

さっきの`content-type`のリストのページによると、

If you do not want a particular response from your origin to be encoded, you can disable this by setting cache-control: no-transform at your origin web server.

とのことで、`cache-control: no-transform`をWorkerから返すことで、自動で圧縮されないようにできた。ただこのままだと`content-encoding`がレスポンスヘッダーに入らない。

じゃあどうすれば・・?ってのをひとしきり調べてみたけど、これだ!っていう答えを見つけられなかった。

中の人が回答してる最もそれらしいやつによると、`encodeBody`っていうCFW特有のプロパティを使う、と。

https://developers.cloudflare.com/workers/runtime-apis/response/#properties

デフォルトの`auto`から`manual`にしつつ、`content-encoding`も指定するといいらしい。

const resp = new Response(/* ... */, {
  headers: {
    "content-encoding": "gzip"
  },
  encodeBody: "manual", // コレ
});

ただこれができるのは、独自`gzip`の場合のみで、独自`brotli`はできないとのこと。

Storing Compressed Data for Cloudflare Workers Sites - Workers - Cloudflare Community
How to serve directly my Brotli and Gzip pre-compressed css and js instead of the Cloudflare compressed ones? - Workers - Cloudflare Community

わからない

Cloudflare Workersだからといって、ブラウザと直でやり取りしてるわけではないってことはわかった。

ただそのレイヤー境界で起きてることはよくわからなかった。誰か正解を教えてください・・。