CloudFront と Lambda@Edge で出来ること

はじめまして。カヤック技術部の杉山です。 主にクライアントワークでサービスを開発しています。

今回は、自分の好きなCloudFrontの「Lambda@Edge」について書きます。

Lambda@Edgeとは

基本的にはここに使い方が書いてありますが、 開発者ガイド > AWS Lambda@Edge

CloudFrontへのアクセス、または応答時に、機能制限されたLambda functionを実行することが出来る機能です。 アクセス時や、応答時に副作用を起こすこと、レスポンス自体を変えることが出来ます。

実際はまだプレビュー版なので、まず申請する必要がありますが、 数日待ち許可されると、1つだけCloudFront にLambda functionを割り当てることが可能になります。

たとえばこんな物が作れます

副作用を起こすLambda functionは想像しやすいと思うので、 今回はレスポンス自体を置き換える例を作ってみました。

以下に示す二つの例はどちらも、リクエスト時に呼ばれるトリガーにLambda functionを割り当てる必要があります。

大まかにいうと、callback に、リクエストとして届いたデータをそのまま渡せば、通常通りの処理となり、 ステータスコードとともにbodyやレスポンスヘッダを返せば、好きなレスポンスに置き換えられます。

時間を返すAPIとか

exports.handler = (event, context, callback) => {
    
    const time = () => {
        const date = new Date() ;
        return Math.floor(date.getTime() / 1000);
    };

    const request = event.Records[0].cf.request;

    const now = time();
    const response = {
        status: '200',
        statusDescription: 'HTTP OK',
        httpVersion: request.httpVersion,
        headers: {
          "Content-Type": ["application/json; charset=utf-8"],
        },
        body: `{"times":${now}}`,
    };

    callback(null, response);
};

負荷テストをしたところ、100req/sec 以上の速度が出ていましたが、同時アクセスが増えると、503 エラーが表示されました。 CloudFrontにCloudFrontを入れるような日が来るかもしれません。

BASIC認証とか

exports.handler = (event, context, callback) => {
    
    const Allows = {
        "users": "users",
    };

    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const authorization = headers.authorization || headers.Authorization;

    if (authorization) {
        
        const enc = authorization[0].split(" ")[1];
        const userPassword = new Buffer(enc, 'base64').toString();

        for (var key in Allows) {
            var val = Allows[key];
            
            if (`${key}:${val}` === userPassword) {
                callback(null, request);
                return;
            }
        }
    }

    const response = {
        status: '401',
        statusDescription: 'Authorization Required',
        httpVersion: request.httpVersion,
        headers: {
          "WWW-Authenticate": ['Basic realm="Enter username and password."'],
          "Content-Type": ["text/plain; charset=utf-8"],
        },
        body: "401 Authorization Required",
    };

    callback(null, response);
};

リクエストヘッダーに含まれるキーが、大文字と小文字どちらの場合もありました。おそらく今後、統一されると思います。

Viewer リクエスト時のヘッダー例

{
  "uri":"/hoge",
  "method":"GET",
  "httpVersion":"1.1",
  "clientIp":"**.**.**.**",
  "headers":{
    "Host":["hoge.kayac.com"],
    "Connection":["keep-alive"],
    "Cache-Control":["max-age=0"],
    "Upgrade-Insecure-Requests":["1"],
    "User-Agent":["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"],
    "Accept":["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"],
    "Accept-Encoding":["gzip, deflate, sdch"],
    "Accept-Language":["ja,en;q=0.8,en-US;q=0.6"]
  }
}

困ったところ

Lambda function の関数を書き換えたときに、反映までしばらく待つ必要があることと、 CloudFront経由のアクセスは、console.log が表示されないのが、開発でなかなかつらいポイントです。

さいごに

面白いことが他にもできそうで、正式リリースが待ち遠しいです。

新しい物がでたらすぐに試して導入できるのも、クライアントワークの楽しいところです。

こんな仕事が面白そうだと思った人はこちらから! 杉山のブログを読んだと書いてもらえると、筆が進みます。

関連記事

techblog.kayac.com

techblog.kayac.com