CloudFront のカスタムエラーレスポンスを有効にした環境で特定のパスだけオリジンのエラーレスポンスを返す方法を教えて下さい

本記事の方法は、RFC7231 によって定義されている HTTP ステータスコードの意味合いと異なってくるため、推奨する方法ではございません。そのため、本番環境への適用は十分ご考慮ください。

困っていた内容

CloudFront で、カスタムエラーレスポンスを設定しております。
特定の Behavior で、CloudFront で設定したカスタムエラーレスポンスではなく、オリジンが返してきたエラーレスポンスをそのままクライアントへ返したいです。
Lambda@Edge の Origin Response を使って、以下の実現例の様に異なるレスポンスに変更して返したいのですが、実現方法を教えてください。

実現例:
Origin の HTTP ステータスコードが、「404 Not Found」
    - Behaivior 「/class」 の場合は、「CloudFront のカスタムエラーレスポンス」をクライアントへ返す
    - Behaivior 「/method」の場合は、「Origin が返してきたエラーレスポンス」をクライアントへ返す

結論

Lambda@Edge の Origin Response を使用して、特定の Behaivior のステータスコードを上書きすることで、そのまま Origin のエラーレスポンスをクライアントへ返すことは可能です。

どうやって実現するの?

Origin からレスポンスが返す際に、Lambda@Edge の Origin Response を利用することでエラーレスポンスの内容を上書きすることができます。

ただし、Lambda@Edge の Origin Response でステータスコードを別の値に変更せずにそのままレスポンスすると、CloudFront のカスタムエラーレスポンスで設定したステータスコードの条件に当てはまってしまい、Lambda@Edge の処理は無効となり、結局クライアントで表示されるのは、CloudFront のカスタムエラーコードになってしまいます。

注意事項

注意:早速ですが、冒頭でも触れました通り、RFC7231 による定義を無視するため推奨する方法ではないので、十分理解してから自身の環境へ適用ください。

「Lambda@Edge で特定の HTTP ステータスコードに一致する場合は、独自 HTTP ステータスコードに上書きしてレスポンスを返す」 ことで CloudFront のカスタムエラーレスポンスを利用しなくなります。

つまり、無理矢理、レスポンスコードをカスタムエラーレスポンスと被らないものにすれば良いということです。 アンチパターンを嫌う開発者からしたら、セオリーを無視したやり方ですので、あんまりオススメしない方法だということがわかりますよね(ここまで、紹介しておいてすみません)。

基本的には、RFC 標準で空いている HTTP 4xx や 5xx のステータスコードで上書きするかと思いますが、「499」は Nginx だと「クライアントからの接続断」を表すエラーメッセージのため、 使っているミドルウェアやフレームワークが出力するステータスコードをしっかり確認 してから利用するようにしましょう。

499 Client Closed Request — httpstatuses.com

ステータスコードが被ると、運用時の障害調査や開発のデバックが困難になるので注意しましょう。

手順

ここからは、Lambda@Edge で Origin から返ってきたレスポンスをそのままクライアントへ返す方法について紹介します。

構成図

1. Behaivior「/class」の場合

2. Behaivior「/method」の場合

前提条件

以下を設定していることを前提として手順の説明をします。

  • CloudFront のカスタムエラーレスポンスを設定
    カスタムエラーレスポンスの生成 - Amazon CloudFront

  • オリジンサーバの準備
    また、CloudFront のオリジンに設定するサービスは今回は ALB として、
    ALB のターゲットグループに EC2 インスタンスを配置するような構成で話を進めます。

  • オリジンのレスポンスのスクリプト準備
    EC2 上のアプリケーションは各自で設定してください。
    その際に、アプリケーションにリクエストが来るたびに、必ず「404」のステータスコードを返すようなソースコードを作成してください。

Lambda@Edge の設定

Behaivior が「/method」の Lambda@Edge のオリジンレスポンスで、オリジンから返ってきた HTTP ステータスコードが 「404」 の場合に、 自分で決めた独自 HTTP ステータスコードで上書きして、CloudFront へレスポンスを返してあげるように、Lambda のスクリプトを作成します。 以下は、Node.js でスクリプトを作成したときの例です。

'use strict';

exports.handler = (event, context, callback) => {
  const response = event.Records[0].cf.response;

  if (response.status == 404) {
    response.status = 499;
    response.statusDescription = 'カスタムエラーレスポンスを利用しないけど、「404 Not Found」ですよ。';
  }

  callback(null, response);
}

作成したスクリプトは、以下の AWS ドキュメントを参考に作成しました。

上記の Lambda@Edge のソースコードを 下図のように Lambda へデプロイして、 CloudFront の Behaivior「/method」のオリジンレスポンスへ紐付けましょう。

Lambda@Edge が独自レスポンスコードで上書きするか検証してみる

設定が完了したら、検証してみましょう。

1. Behaivior「/class」の場合

以下のコマンドでステータスコードを取得します。 カスタムエラーレスポンスのステータスコードが返ってきます。

$ curl -LI https://xxxxx/class -o /dev/null -w '%{http_code}\n' -s
404

ブラウザでアクセスすると、CloudFront のカスタムエラーページが表示されます。

2. Behaivior「/method」の場合

同様に、ステータスコードを取得すると、Lambda@Edge で上書きしたステータスコードの値が返ってきます。

$ curl -LI https://xxxxx/method -o /dev/null -w '%{http_code}\n' -sd
499

ブラウザでアクセスすると、オリジンで設定したページが表示されるはずです。