[update] CloudFront FunctionsでViewer ResponseのHTTPステータスコードとレスポンスbodyが変更できるようになっていました!

Viewer Responseの際の200/300番台のステータスはFunction内で変更が可能になりました。またオリジンからのレスポンスbody自体についてはアクセスできず、Function内でまるっと置き換えか削除となる点に注意しましょう。
2023.05.01

はじめに

清水です。AWSのCDNサービスであるAmazon CloudFrontでは、そのEdge Locationでコードを実行するCloudFront Functionsという機能があります。このCloudFront FunctionsでViewer ResponseのHTTPステータスコードとレスポンスbodyを変更できるようになるアップデートがありましたのでまとめてみたいと思います。(2023/03/29付でポストされたアップデート情報となります。)

Edge Locationでコードを実行可能なCloudFront Functions、使用する際にはいくつかある制限事項に留意しなければなりません。その一例が今回のアップデート対象で、これまでCloudFront Functionsではレスポンスbodyの変更ができないという制限がありました。またHTTPステータスコードについても、Viewer Request EventでレスポンスをCloudFront Functionsコード内で生成してそのまま(オリジンやキャッシュを経由せずに)返す際のステータスの生成(設定)はできましたが、Viewer Response Eventでオリジンやキャッシュからのレスポンスに対してステータスコードを変更することはできない、という制限がありました。

今回のアップデートでは、Viewer Response Eventの際(つまりオリジンもしくはキャッシュを経由し、Viewerに返されるレスポンスに対して)に、HTTPステータスコードの変更が可能になりました。またレスポンスbodyについても変更が可能となっています。ユースケースの一例としては、オリジンから返されたヘッダーをCloudFront Functionsで評価してリクエストをブロックするという使い方が紹介されています。

使用に関してはいくつか注意点があります。例えばステータスコードについてはオリジンからのレスポンスが400未満のもの(200番台と300番台)である必要があります。またレスポンスbodyについても、CloudFront Functionsコード内でオリジンもしくはキャッシュから渡されたbodyの内容そのものにアクセスできません。CloudFront Functionsコード内では新しいbodyに書き換えるか、bodyの内容を空に設定する、という使い方となります。

CloudFront Developer Guideからアップデートの詳細を確認してみる

まずはAmazon CloudFront Developer Guideから本アップデートの詳細を確認してみましょう。Document historyを確認するとMarch 29, 2023付で「Customize HTTP status and body using CloudFront Functions」という変更があります。

Document history - Amazon CloudFront

リンク先は「CloudFront Functions event structure」のページの「Status code and body」という項目です。

Status code and body - CloudFront Functions event structure - Amazon CloudFront

Internet Archive Wayback Machineで同ページの履歴を確認してみます。以前(本アップデート前の、2023/03/17の段階)はこのセクションはありませんでした。(「Response object」の次のセクションが「Query string, header, and cookie structure」になっています。)今回のアップデートで「Status code and body」のセクションが追加されたかたちですね。

CloudFront Functions event structure - Amazon CloudFront (Internet Archive Wayback Machine)

具体的な内容についてはDeveloper Guideの「Status code and body」の項目を確認いただくとして、ここではポイントのみまとめてみます。

冒頭で示したAWS What's Newのアップデート情報の通り、CloudFront FunctionsでViewer Response EventのHTTPステータスコードの変更、またレスポンスのbodyをすべて新しく置き換えたり、削除することが可能となっています。ただし、オリジンが400以上のHTTPエラーを返した場合はCloudFront Functionが実行されないこと(つまりステータスコードの変更ができないこと)、またCloudFront Functionsはレスポンスのbody自体にはアクセスできないことに注意が必要です。後者については、オリジンもしくはキャッシュからのレスポンスbodyの一部のみを書き換えるといったことはできず、まるっと置き換えるか削除するかのどちらかの処理になる、ということですね。

今回のアップデートのポイントがこのセクションにまとめられていますが、これまでの挙動との比較についてももう少し深堀りして確認しておきましょう。

HTTPステータスコードの書き換えについての詳細

まずはHTTPステータスコードの書き換えについてです。Internet Archive Wayback Machineで本アップデート前(2023/03/23時点)の制限事項を確認してみましょう。「Restrictions on edge functions」のページの「Restriction on all edge functions」という項目の「HTTP status codes」の箇所に制限事項として、以下3点が記載されていました。

  1. オリジンが400以上のHTTPステータスコードを返す場合、edge functionsではviewer response eventを呼び出すことができない
  2. viewer response eventsに設定したedge functionsではレスポンスがオリジンまたはキャッシュのどちらからきたかに関係なく、レスポンスのHTTPステータスコードを変更することはできない
  3. origin response eventsに設定したLambda@Edge関数は、オリジンが400以上のHTTPステータスコードを返す場合も含めて、すべてのオリジンレスポンスに対して呼び出される

Restrictions on edge functions - Amazon CloudFront (Internet Archive Wayback Machine)

本アップデート後の、つまり最新のDeveloper Guideの該当箇所を確認してみましょう。記載として残っているのは1.と3.のみで、2.の記載がなくなっていることがわかります。つまり(CloudFront Functionsでは)Viewer Responseにて、レスポンスのHTTPステータスコードが変更できないという制限が撤廃された(HTTPステータスコードの変更が可能になった)ということになります。

Restrictions on edge functions - Amazon CloudFront

なお、本ページ内「Restrictions on Lambda@Edge」のHTTP status codesの項目を確認するとわかりますが、Lambda@Edgeについては2.の制限がまだ残っている状況となっています。

Restrictions on edge functions - Amazon CloudFront

レスポンスbodyの書き換えについての詳細

レスポンスbodyの書き換えについても確認してみましょう。Internet Archive Wayback Machineで本アップデート前(2023/03/23時点)の「CloudFront Functions event structure」のページを確認してみます。bodyという文字列でページ内を検索してみますが、該当する箇所は1箇所も見つかりません。つまりレスポンスのbodyについてはCloudFront Functionsで触れる術がなかった状況です。

CloudFront Functions event structure - Amazon CloudFront (Internet Archive Wayback Machine)

最新のDeveloper Guideの「CloudFront Functions event structure」のページを確認してみます。「Response object」の項目にbodyフィールドについての記載が追加されています。この内容をもとにすることで、bodyレスポンスについて書き換えができますね!

CloudFront Functions event structure - Amazon CloudFront

先ほどの「Status code and body」の項目もこのページ内にあります。また「Example response object」の項目ではbodyフィールドについての具体例も示されています。

CloudFront Functions event structure - Amazon CloudFront

CloudFront FunctionsでViewer ResponseのHTTPステータスコード変更やレスポンスbodyの書き換えをやってみた

Amazon CloudFront Developer Guideの変更箇所から今回のアップデートでの詳細、これまでの制限事項と比べてどのようなことができるようになったのかを確認してきました。続いては実際にCloudFront FunctionsでViewer Response EventのHTTPステータスコードの書き換え、ならびにbodyレスポンスの書き換えを行ってみたいと思います。

Viewer Response EventでHTTPステータスコードとbodyを書き換えてみる

まずはViewer Response EventでHTTPステータスコードとbodyの書き換えを行ってみます。AWS What's Newのアップデート情報にあったユースケースを参考に、オリジンから特定のヘッダが含まれていた際にはコンテンツを変更する動作をCloudFront Functionsで実現してみました。

まずオリジン側はEC2でApache HTTP ServerとPHPを動かします。以下のresponse_headers.phpというコードを準備しました。リクエスト時の時刻を返すのみですが、viewというクエリ文字列があった場合はx-view-response-headers: trueというヘッダをレスポンスとして返します。

<?php
if(isset($_GET['view'])){
    header("x-view-response-headers: true");
}

date_default_timezone_set('Asia/Tokyo');
echo date("Y/m/d H:i:s");
echo "\n";

?>

クエリ文字列viewがないときとあるときで、それぞれ以下のような動作を行います。

クエリ文字列viewがないとき

% curl -i "http://18.XXX.XXX.172/response_headers.php"
HTTP/1.1 200 OK
Date: Mon, 01 May 2023 13:33:10 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
X-Powered-By: PHP/5.6.35
Content-Length: 21
Content-Type: text/html; charset=UTF-8


2023/05/01 22:33:10

クエリ文字列viewがあるとき

% curl -i "http://18.XXX.XXX.172/response_headers.php?view=true"
HTTP/1.1 200 OK
Date: Mon, 01 May 2023 13:33:12 GMT
Server: Apache/2.4.27 (Amazon) PHP/5.6.35
X-Powered-By: PHP/5.6.35
x-view-response-headers: true
Content-Length: 21
Content-Type: text/html; charset=UTF-8


2023/05/01 22:33:12

続いてCloudFront Distributionの作成です。準備したオリジンとなるEC2のPublic IPv4 DNSをOrigin domainに指定して作成します。動作確認のためキャッシュはしないよう設定しました。Cache key and origin requestsの項目でCache policyはCachingDisalbed、Origin request policyはAllViewerを選択します。

このまま(CloudFront Functionsはまだ関連付けせずに)Distributionを作成します。Distributionの作成完了を待ちながらCloudFront Functionsの作成と設定を進めます。

[Create function]ボタンからFunction作成画面に進みます。適切なNameとDescriptionを設定します。

Function codeとして以下を準備しました。(なお筆者はコーディングに明るくなく、見様見真似で作成したコードとなります。必ずしも適切なコードではなく、あくまで動作検証を最優先としている点にご注意ください。)オリジンもしくはキャッシュから返されるレスポンスについて、x-view-response-headersというレスポンスヘッダがあればbodyの内容をレスポンスヘッダを列挙した文字列で書き換えます。レスポンスヘッダにx-view-response-headersがない場合は、レスポンスのステータスコードを302としてnewurlで指定したURLにリダイレクトさせます。

function handler(event)  {
    var response  = event.response;
    var headers  = response.headers;
    var newurl = `https://dev.classmethod.jp/`

    if (headers['x-view-response-headers']) {
        var keys = Object.keys(headers);
        var bodytext = ``;
        keys.forEach( (key) => {
            bodytext += key + ": " + response.headers[key].value + "\n";
        });
        response.body = {
            "encoding" : "text",
            "data": bodytext
        };
    }
    else {
        response.statusCode = 302;
        response.statusDescription = 'Found';
        headers['location'] = { 'value' : newurl };
    }        
    return response;
}

コードを保存、テストして[Publish function]します。さらに[Add association]ボタンから先ほど作成したCloudFront Distributionに関連付けます。Event typeではViewer responseを選択します。

それでは実際に https://CloudFrontのドメイン/response_headers.php にアクセスして(pathは先ほどオリジン側で準備したPHPコードですね)CloudFront Functionsの実行を確認してみます。まずはクエリ文字列viewがない場合です。オリジンからCloudFrontへのレスポンスヘッダにもx-view-response-headersが含まれないかたちになります。

オリジンからのレスポンスステータスは200だったはずですが302となり、リダイレクト先のlocationも設定されていることがわかります。bodyは書き換えていないのでdate("Y/m/d H:i:s")の値ですね。

クエリ文字列viewがないとき

% curl -i "https://d3e1xxxxxxxxxx.cloudfront.net/response_headers.php"
HTTP/2 302
content-type: text/html; charset=UTF-8
content-length: 21
location: https://dev.classmethod.jp/
date: Mon, 01 May 2023 14:05:18 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
via: 1.1 5a8bxxxxxxxxxxxxxxxxxxxxxxxxfa4e.cloudfront.net (CloudFront)
x-cache: Miss from cloudfront
x-amz-cf-pop: NRT57-P1
x-amz-cf-id: kGOGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxkQ==


2023/05/01 23:05:18

続いてクエリ文字列でview=trueを設定した場合です。bodyでCloudFront Functionsが受け取ったレスポンスヘッダが文字列として列挙されています。bodyの書き換えができていますね!

クエリ文字列viewがあるとき

% curl -i "https://d3e1xxxxxxxxxx.cloudfront.net/response_headers.php?view=true"
HTTP/2 200
content-type: text/html; charset=UTF-8
content-length: 261
date: Mon, 01 May 2023 14:05:23 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
x-view-response-headers: true
via: 1.1 f10bxxxxxxxxxxxxxxxxxxxxxxxx7078.cloudfront.net (CloudFront)
x-cache: Miss from cloudfront
x-amz-cf-pop: NRT57-P1
x-amz-cf-id: hOIvxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxhw==

date: Mon, 01 May 2023 14:05:23 GMT
server: Apache/2.4.27 (Amazon) PHP/5.6.35
x-powered-by: PHP/5.6.35
x-view-response-headers: true
via: 1.1 f10bxxxxxxxxxxxxxxxxxxxxxxxx7078.cloudfront.net (CloudFront)
content-type: text/html; charset=UTF-8
content-length: 21

Viewer Request Eventでもbodyを書き換えてみる

今回のアップデートのうちレスポンスbodyの書き換えについては、Viewer Response EventのみならずViewer Request Eventでも対応しているようです。なおViewer Request EventでのレスポンスのHTTPステータスコードの書き換えは(オリジンにリクエストを投げずにCloudFront Functionsでリクエストを返すかたちですが)、これまでも実現ができていました。例えばCloudFront Functionsでリダイレクトさせるケースや、Basic認証を行うケースですね。

これまでのレスポンスbodyは空の状態でしたが、今回のアップデートでレスポンスオブジェクトが示している通りbodyフィールドを変更すればViewer Request Eventでのbodyの書き換えも可能となっているようです。(ただし、HTTPステータスコードと同様、オリジンにリクエストを投げずにCloudFront Functionsでリクエストを返すかたちでの利用となるかと思います。)

実際にCloudFront Functionsを設定して確認してみましょう。以下サイトのCloudFront FunctionsでのBasic認証のコードをもとに、レスポンスbodyを設定したコードを準備しました。

function handler(event) {
  var request = event.request;
  var headers = request.headers;

  // echo -n user:pass | base64
  var authString = "Basic dXNlcjpwYXNz";

  if (
    typeof headers.authorization === "undefined" ||
    headers.authorization.value !== authString
  ) {
    return {
      statusCode: 401,
      statusDescription: "Unauthorized",
      headers: { "www-authenticate": { value: "Basic" } },
      body: {
        "encoding": "text",
        "data": "<!DOCTYPE html><html><body><p>Please enter the correct username and password.</p></body></html>"
      }
    };
  }

  return request;
}

先ほどViewer Response Eventの動作確認をしたCloudFront Distributionを使いましわして確認してしまいます。先ほどのCloudFront Functionは関連付けを外してしまい、新たに上記コードで作成したFunctionをViewer requestとして関連付けます。

Basic認証の認証情報を指定せずにアクセスしてみます。これまでCloudFront Functionsでレスポンスbodyを設定することはできなかったのですが、しっかりと設定した文字列がレスポンスbodyとなっていますね!

% curl -i "https://d3e1xxxxxxxxxx.cloudfront.net"
HTTP/2 401
server: CloudFront
date: Mon, 01 May 2023 14:47:43 GMT
content-length: 95
www-authenticate: Basic
x-cache: FunctionGeneratedResponse from cloudfront
via: 1.1 05e0xxxxxxxxxxxxxxxxxxxxxxxxb7f2.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT57-P1
x-amz-cf-id: f5jXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxHA==

<!DOCTYPE html><html><body><p>Please enter the correct username and password.</p></body></html>

まとめ

CloudFront FunctionsでViewer Response Eventの際のHTTPステータスコードの変更、ならびにレスポンスbodyの書き換えができるようになったアップデートについてお届けしました。HTTPステータスコードの変更のついてはオリジンからのレスポンスが400未満である必要がある点、またレスポンスbodyの書き換えについてはオリジンからのレスポンスbodyには直接アクセスできない点などに注意しましょう。とはいえ、これまで制限事項としてCloudFront Functionsでは変更ができなかったHTTPステータスコードならびにレスポンスbodyの書き換えができるようになった点は大きいですよね。アップデート情報やDeveloper Guide記載の方法以外にも、いろいろな方法で活用できるのではないかと思いました。