Help us understand the problem. What is going on with this article?

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

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

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

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

追記: bodyさわれるようになりましたね。以下はもうoutdated


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

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした