CloudFrontでBrotli圧縮したコンテンツを配信してみた

CloudFrontでBrotli圧縮したコンテンツを配信するには一工夫必要です。CloudFrontのAccept-Encodingの扱いに注意しながら構築しましょう。
2020.05.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

BrotliはGoogleが開発した圧縮フォーマットです。

RFC 7932で仕様公開されており、gzipやZopfliよりもパフォーマンスがよく、Chrome、Safari、Firefox、Edgeといったすべての主要ブラウザが対応しています。Content-Encodingbr です。 より効率的にエンコードすることで、より速く、より安くコンテンツ配信できます。

今回はこのBrotliによるコンテンツ圧縮をCloudFrontと連携される方法を3種類ご紹介します。

  1. (未対応)CloudFrontで動的にBrotli圧縮
  2. オリジンで動的にBrotli圧縮
  3. オリジンで静的にBrotli圧縮

(未対応)1. CloudFrontで動的にBrotli圧縮

CloudFront Behaviorには「Compress Objects Automatically」という設定が存在し、クライアントのAccept-Encodingに応じて適切なエンコーディング形式でコンテンツを返す機能が存在します。

残念ながら、現時点ではgzipエンコーディングにしか対応していないため、この方式ではBrotli配信できません。

CloudFrontの公式フォーラムでロードマップの意見を募集しているので、Brotli対応にプラス投票をすれば、思いが伝わるかもしれません。

AWS Developer Forums: Amazon CloudFront wants to hear your input on the service roadmap

2020年9月に Brotli 対応しました!

2. オリジンで動的にBrotli圧縮

オリジンで動的(オンデマンド)にBrotli圧縮します。

ポイントは、CloudFrontでAccept-Encodingヘッダーをホワイトリスト化することです。

クライアントのAccept-Encodingをそのままオリジンにフォワードしつつ、Accept-Encodingに基づいてコンテンツをキャッシュできるようになります。

ホワイトリスト化しなかった場合、次のようにAccept-Encodingからgzip以外の情報が失われてしまいます。

  • Accept-Encodingにgzipが含まれていればAccept-Encoding: gzipに書き換える。gzip以外は削除
  • Accept-Encodingにgzipが含まれていなければ、ヘッダーフィールドそのものを削除

Nginxで動的にBrotli圧縮

Nginxの場合、Googleがモジュールngx_brotliを提供しています。

同モジュールを有効後、

brotli on;  # enable on-the-fly compression
brotli_comp_level 6; # compression level : 0 - 11
brotli_types text/plain ... application/atom+xml applic # MIME types in addition to text/html

のようなディレクティブを追加すると、オンデマンドでBrotli圧縮します。

3. オリジンで静的にBrotli圧縮

オリジンでファイルを事前にBrotli圧縮しておき、Accept-Encodingヘッダーに応じてBrotliエンコーディングされたファイルを返します。

当然ながら、事前にBrotli圧縮する手間が発生します。

Nginxで静的配信

CloudFrontでAccept-Encodingヘッダーをホワイトリスト化するのは動的配信と同じです。

Nginxの場合、Googleがモジュールngx_brotliを提供しています。

同モジュールを有効後、

brotli_static on;  # enables checking of the existence of pre-compressed files with.br extension

のようなディレクティブを追加します。

Brotliエンコーディングされた.brファイルが存在する場合、同ファイルを返します。

S3で静的配信

オリジンがS3のケースです。

事前にファイルをBrotli圧縮し、

  • Content-Type : コンテンツごとに異なる
  • Content-Encoding : br

を適切に設定したS3オブジェクトをPUTします。

CloudFrontでAccept-Encodingヘッダーフィールドをホワイトリスト化するのは動的配信と同じです。

クライアントがBrotliエンコーディングに対応する場合、Lambda@EdgeのOrigin RequestでURIをBrotliファイル向けに書き換えます。

L@E Request Viewer

def lambda_handler(event, context):
    request = event['Records'][0]['cf']['request']
    headers = request['headers']
    encoding = headers.get('accept-encoding', [{}])[0]
    if 'br' in encoding.get('value', ''): # クライアントが Brotli に対応している
        request['uri'] = request['uri'] + '.br' # URI を Brotli ファイル向けに書き換える
    return request

Brotliに対応していないクライアント向けには、CloudFrontのBehaviorで「Compress Objects Automatically」を有効にしておけば、gzip圧縮されます。 オリジンが Content-Encoding を含んだレスポンスを返す場合、CloudFront がさらにエンコードすることは有りません。

手間がかかるため、特殊な条件が揃わない限りこのような構成は積極的には採用しないと思いますが、このような構成も可能です。

最後に

CloudFrontからBrotliエンコーディングで配信する場合、現時点ではAccept-Encodingヘッダーフィールドをホワイトリスト化し、オリジンでBrotli圧縮する必要があります。

CloudFrontのAccept-Encodingをホワイトリスト化しなかった場合、オリジンへのリクエスト時にgzip 以外の対応エンコーディングを削除します。Brotliに限らず、クライアントのAccept-Encodingに応じてオリジンでエンコードしている場合は、ご注意ください。

参考