Amazon CloudFrontからクライアントに対して行うレスポンスについて

この記事は[ディップ Advent Calendar 2024](https://qiita.com/advent-calendar/2024/dip-dev)の16日目の投稿です。

## 背景

Amazon CloudFrontからクライアントに対して行うレスポンスについて調査するきっけは動作確認の際の挙動などが不明だったためになります。
コンテンツに対してアクセスした際、CloudFrontが導入されておりますと、レスポンスヘッダーに対してCloudFrontの挙動を確認するための`x-cache`ヘッダーが付与されますので、そちらに対して学んだことを記述出来ればと思います。

## Amazon CloudFrontの概要

Amazon CloudFrontは、AWSが提供するグローバルなCDNサービスです。

CDNを利用すると、コンテンツ配信の高速化とオリジンの負荷を軽減できる利点があります！

例えばユーザからアクセスされたときは、近いCDNにキャッシュしているのでレスポンスが早くなり、CDNのキャッシュを返す場合はオリジンサーバへアクセスしないためオリジンサーバのCPUなどのリソースを温存出来るためです！
実際にどれぐらい早くなるかは、AWS公式のハンズオンから試してみることが可能なので是非試してみてください！

https://pages.awscloud.com/JAPAN-event-OE-Hands-on-for-Beginners-CF_WAF-2022-reg-event.html

また、CloudFrontについて詳しくは、以下の公式ドキュメントを参照してください！

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Introduction.html


## 既存コンテンツのキャッシュ更新

大別して2通りあります。

- ファイル名にバージョン識別⼦を使⽤して更新する
- 同じ名前を使⽤してファイルを更新する

### ファイル名にバージョン識別⼦を使⽤して更新する

以下の表のように更新前後でコンテンツのファイル名が違う場合、CloudFrontのキャッシュの挙動はどうなるのでしょうか？

| 更新前後 | コンテンツ |
| -------- | ---------- |
| 更新前   | img_v1.jpg |
| 更新後   | img_v2.jpg |

なんてことないのですが、更新の前後でコンテンツのファイル名が違う場合は、別々のキャッシュとして保存されます。
この方法は、明示的にキャッシュが更新を行えるという利点がありますが、呼び出し元の変更必要なのでアプリ改修などの工数がかかってしまう欠点もあります。

### 同じ名前を使⽤してファイルを更新する

以下の表のように更新前後でコンテンツのファイル名は同じだがファイルの内容が違うものの場合、CloudFrontのキャッシュの挙動はどうなるのでしょうか？

| 更新前後 | コンテンツ |
| -------- | ---------- |
| 更新前   | img_v1.jpg |
| 更新後   | img_v1.jpg |

コンテンツのメタ情報を使って別ファイルということを認識し、キャッシュの更新を行います。
コンテンツには多くのメタ情報 (Cookies, クエリ文字列, ヘッダー)が紐づいているため、コンテンツのファイル名が同じでもメタ情報を使って別ファイルだということを認識しています。
CloudFrontでファイル識別に使用されるメタ情報は、以下になります。

| メタ情報 | 内容 |
| -------- | ---------- |
| etag ヘッダー   | コンテンツの特定のバージョンを示すもの |
| last-modified ヘッダー   | 最終更新日時を示すもの |

コンテンツのキャッシュの有効期限が切れると再度クライアントからCloudFrontにアクセスがあった際に、オリジンサーバに対して`Etag`/`Last Modifified`に変更があるかHTTPヘッダーの値( `IF-Modified-Since` / `If-None-Match` )を用いて確認します。

このときもし、キャッシュしたコンテンツに変更がない場合は、`IF-Modified-Since` / `If-None-Match`のレスポンスと`Etag`/`Last Modifified`の値に変更がなければ、クライアントに`304(Not Modified)`と`x-cache : RefleshHit from cloudfront`を返してキャッシュ期間を更新します。
以降は、またコンテンツのキャッシュが切れるまでCloudFront内のキャッシュを返し続けます。

また、キャッシュしたコンテンツに変更がある場合は、`IF-Modified-Since` / `If-None-Match`のレスポンスと`Etag`/`Last Modifified`の値に変更があれば、クライアントに`200(OK)`と`x-cache : Miss from cloudfront`を返してコンテンツ内容とキャッシュ期間を更新をします。
以降は、またコンテンツのキャッシュが切れるまでCloudFront内のキャッシュを返し続けます。


| キャッシュしたコンテンツに対する変更 | CloudFrontからクライアントへのHTTPレスポンス | CloudFrontからクライアントへの`x-cache`レスポンスヘッダー |
| -------- | ---------- | ---------- |
| 変更がない   | `304(Not Modified)` | `x-cache : RefleshHit from cloudfront` |
| 変更がある   | `200(OK)` | `x-cache : Miss from cloudfront` |

CloudFrontからクライアントへのブラウザでキャッシュの挙動を`x-cache`レスポンスヘッダーで確認することができます。

| x-cache レスポンスヘッダー | 説明                                                         |
| -------------------------- | ------------------------------------------------------------ |
| Hit from cloudfront        | CloudFront にキャッシュがあったので、それを返した            |
| RefreshHit from cloudfront | CloudFront にキャッシュはあったが、有効期限が切れており、オリジンサーバに最新か確認した後に返した |
| Miss From cloudfront       | 現在 CloudFront にキャッシュが無い状態なので、オリジンサーバにコンテンツを取りに⾏って返した |

また合わせてCloudFrontのログからキャッシュの挙動を確認するための⽅法についても記載しておきます。

| x-edge-result-type / x-edge-response-result-typeの値 | 説明                                                         |
| ---------------------------------------------------- | ------------------------------------------------------------ |
| HIT                                                  | CloudFront がキャッシュからクライアントにコンテンツを渡したことを⽰す |
| RefreshHit                                           | CloudFront のキャッシュにてコンテンツを検出したが、キャッシュの有効期限が切れていたため、オリジンサーバに問い合わせて、最新バージョンのコンテンツがあるかどうかを確認したことを⽰す |
| Miss                                                 | CloudFront のキャッシュにあるコンテンツでリクエストに対応できなかったため、リクエストをオリジンサーバに転送して結果をクライアントに返したことを⽰す |

注意点としましては、有効期限を決める必要があり、有効期限を長くするとオリジンサーバへのリクエストが減らせますが、頻繁にコンテンツを更新する場合は有効期限を短くする必要があります。
また、指定したタイミングで一斉にキャッシュを更新したい場合は、キャッシュの無効化対応をする必要がありますのでご注意ください。

## オリジンサーバが障害でダウンしてしまったとき

オリジンサーバから返される以下の HTTP 4xx および 5xx ステータスコードを常にキャッシュします。

ステータスコード | 意味
-- | --
404 | Not Found
414 | Request-URI Too Large
500 | Internal Server Error
501 | Not Implemented
502 | Bad Gateway
503 | Service Unavailable
504 | Gateway Time-out

他にオリジンが `Cache-Control max-age` または `Cache-Control s-maxage` ヘッダーを返す場合、CloudFront は以下の HTTP 4xx ステータスコードのみをキャッシュします。

ステータスコード | 意味
-- | --
400 | Bad Request
403 | Forbidden
405 | Method Not Allowed
412 | Precondition Failed
415 | Unsupported Media Type

キャッシュされたエラーはデフォルトで10秒間キャッシュします。これはカスタムエラーレスポンスの定義で変更が可能です。

また以下の二つで挙動が異なるなります。

- CloudFrontのキャッシュが有効期限切れの場合
- そもそもCloudFrontにキャッシュがない場合

### CloudFrontのキャッシュが有効期限切れの場合

5xxエラーの場合、クライアントからCloudFrontに対してアクセスがあるとコンテンツキャッシュの有効期限が切れていることを確認したのち、オリジンサーバに対して`Etag`/`Last Modifified`に変更があるかHTTPヘッダーの値( `IF-Modified-Since` / `If-None-Match` )を用いて確認します。
しかしながらオリジンサーバはダウンしているためCloudFrontからオリジンサーバへのキャッシュ更新確認のレスポンスが`502 Bad Gateway`になります。
`502 Bad Gateway`を受け取ったCloudFrontはクライアントに対して`x-cache : RefleshHit from cloudfront` で有効期限切れのキャッシュを返します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618454/b84243e2-21dc-a8fd-491b-99af2325873d.png)

また4xxエラーの場合はオリジンサーバから受け取ったステータスコードをそのままクライアントへ返します。

### そもそもCloudFronttにキャッシュがない場合

5xxエラーの場合、クライアントからCloudFrontに対してアクセスがあるとコンテンツをオリジンサーバに対して問い合わせます。
しかしながらオリジンサーバはダウンしているためCloudFrontからオリジンサーバへのキャッシュ更新確認のレスポンスが`502 Bad Gateway`になります。
`502 Bad Gateway`を受け取ったCloudFrontに502エラーをキャッシュし、クライアントに対してそのまま`502 Bad Gateway`を返します。
キャッシュした502エラーはデフォルト10秒間キャッシュを保持しますが、カスタムエラーレスポンスでキャッシュ期間を変更することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618454/d23f6023-3a9c-7835-cc24-a0fefafa1a22.png)

## カスタムエラーレスポンスでエラーページを設定

もし、オリジンサーバが障害でダウンしている場合にはエラーページを表示させておきたい場合、カスタムエラーページを設定しておくと表示が可能になります。
オリジンサーバとは異なる場所にエラーページを配置しておき、カスタムエラーページの設定をするとオリジンサーバからのレスポンスが4xx,5xxだった時にエラーページの置いてあるオリジン(画像だとS3)に対してエラーページを要求しキャッシュをユーザへ表示するようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3618454/57725c5f-d005-8beb-11ee-420f7f60c096.png)

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/HTTPStatusCodes.html#HTTPStatusCodes-custom-error-pages

## 終わりに

今回CloudFrontのレスポンスの挙動を調べてみてレスポンスヘッダーの意味合いなどに対して理解を深められました。
また調査の過程でTTLについても知見が多かったので後日まとめてみようと思いました。

## 参考

https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/Introduction.html

https://pages.awscloud.com/rs/112-TZM-766/images/AWS-Black-Belt_2023_AmazonCloudFront-CacheControl_0430_v1.pdf