AWS
Lambda@Edge

Amazon CloudFront & Lambda@EdgeでレスポンスBodyを(少々強引に)改変する

Amazon CloudFrontのLambda@Edgeは、HTTPリクエスト/レスポンスに対して任意のタイミングでLambdaをカマす機能です。

さて、今回はレスポンスのHTMLをちょっと加工したいなと思って調べました。

実装例のドキュメントはこちら。

これらによると、以下の制限があるとのこと。

HTTP レスポンスを使用する場合、Lambda@Edge は、オリジンサーバーから返された HTML 本文を origin-response トリガーに公開しません。

bodyに手を出せないのかよ! オリジンからのレスポンスに対する通常の利用範囲内では、bodyはまるごと上書きのみ可能、とのこと。
ちょっと内容変更して配信したい、というケースに対応するには。。?

そこでヒントにしたのがこちら。

わりとそのまんまの解決策だった。これをすこし方針変更してテキスト処理にすればよさそう。

方針

  • 改変したいのはHTMLだけ
  • オリジンはAmazon S3

How to

  • origin-responseの時点でLambdaコール
    • ここで書き換えればキャッシュにのるので
  • STATUS=200かつ.htmlのリクエストを判定
    • ヘッダは使い回す
    • bodyには、S3からオブジェクトを直接取得してから改変したコンテンツを突っ込む
  • IAM Roleに割り当てるPolicyではgetObjectを許可

コード

で、origin-responseイベントのところで動作するLambdaスクリプトをこのようにしました。
content-lengthの再計算いるかなと思ったけど、このイベントの時点ではcontent-lengthヘッダはついてなかったので、レスポンス最終段階で付与されるんだろうと。

サンプルではBody中のWordPressという単語をgetShifterに置換してます。

origin_response.js
'use strict';
const util = require('util');
const AWS = require('aws-sdk');
const S3 = new AWS.S3({
  signatureVersion: 'v4',
});

exports.handler = (event, context, callback) => {
  console.log(util.inspect(event.Records[0].cf));
  let response = event.Records[0].cf.response;

  if (response.status == 200) {
    let request = event.Records[0].cf.request;
    let bucket = request.origin.s3.domainName.split('.')[0];
    let path = request.uri;

    if (path.endsWith('\.html')) {
      let key = path.substring(1);
      S3.getObject({ Bucket: bucket, Key: key }).promise()
        // perform the replace operation
        .then(data => data.Body.toString()
          .replace(/WordPress/g, 'getShifter')
        )
        .then(buffer => {
          // console.log(buffer);
          response.body = buffer;
          callback(null, response);
          return;
        })
        .catch( err => {
          console.log("Exception while reading source :%j",err);
        });
    } else {
      callback(null, response);
    }
  } else {
    callback(null, response);
  }
};

一応Edgeの厳し目なタイムアウト制限(5s)の範囲には収まっているっぽく、置換されたコンテンツを取得できました。

パススルー用のcallbackをelseの外に置いてたらうまくいかなかったのはなんでやろうなあ。。

使用前/使用後

before.png

↓↓↓変わった。

after.png

初回ちょっと遅いけど、キャッシュには改変後が乗るのでよいかな。