Lambdaを使ってindex.htmlを自動生成する

#はじめに#
Lambdaでは、Amazon S3にコンテンツがPUTされると、コードを実行することができます。
そこで、S3にファイルがPUTされたら、そのバケットにあるコンテンツの一覧を表示する「index.html」を自動生成してみたいと思います。
動作結果は、下の図のとおり。S3においてあるコンテンツの内容が一覧表示される、簡易なコンテンツ共有サイトを簡単に実現できます。ファイル一式を友人に渡す時に便利ですね。ファイルのLinkは、署名付きURLにして期間限定公開にすることや、AWSのマネージメントコンソールで「make public」されたのと同じ状態で、期間限定なしでダウロード可能な状態にすることもできます。

余談ですが、この「make public」ってメニュー項目の響きが好きです。これで、世の中に公開されるんだな、という感じです。「Work smart, Have fun, Make public」という感じです。

![result001a.png](https://qiita-image-store.s3.amazonaws.com/0/61619/a1952402-c85e-f96e-2044-55a677d54b43.png)


#Lambdaコード#
このlambdaコードは、Oregonリージョンで動かします。

```lang:LambdaFunctionCode
console.log('Loading event');
var AWS = require('aws-sdk');
var s3 = new AWS.S3();
var regionurl = 'https://s3-us-west-2.amazonaws.com/';

exports.handler = function(event, context) {
    
    var htmlheader = '<html><haed><link href="test.css" type="text/css" rel="stylesheet" /></head><body><table class="sample007"><tr><th>No.</th><th>Contents</th><th></th><th>Date</th></tr>';
    // index.html header
    
    var htmlfooter = '</table></body></html>';
    // index.html footer
    
    var indexhtmlbucket = 'yoicht-makepublic';  // bucket of Static web hosting
    var htmlfilename = 'index.html';            // filename of index document
    var maxkeys = 100;                          // max keys

    var bucketname = event.Records[0].s3.bucket.name;
    var bucketkey  = event.Records[0].s3.object.key;
    
    console.log('Bucket = ' + bucketname);
    console.log('key = ' + bucketkey);

    var param = {
        Bucket: bucketname,
        Key: bucketkey,
        ACL: 'public-read'      // If signed-url, write 'private'.
    };
    s3.putObjectAcl(param, function(err, data) {
        if (err)
            console.log(err, err.stack);
        else
            makeTable(bucketname, htmlheader, htmlfooter, indexhtmlbucket, htmlfilename, maxkeys, param.ACL );
    });   
};

function makeTable(bucketname, htmlheader, htmlfooter, indexhtmlbucket, htmlfilename, maxkeys, acl ) {
    var params = {Bucket: bucketname, EncodingType: 'url', MaxKeys: maxkeys};
    var url ='';
    
    s3.listObjects(params, function(err, data) {
        if (err) {
            console.log(err, err.stack);
        } else {
            var indexhtml = htmlheader;
            
            for(var index in data.Contents) {
                if (acl == 'public-read') {
                    url = regionurl + bucketname + '/' + data.Contents[index].Key;   // for make public
                } else {
                    params = {Bucket: bucketname, Key: data.Contents[index].Key, Expires: 36000};
                    url = s3.getSignedUrl('getObject', params);
                }

                indexhtml += '<tr><td align="center">'+ index + '</td>' + 
                '<td><a href="' + url + '">' + data.Contents[index].Key + '</a></td>' +
                '<td><img src="' + url + '" height="50">' + '</td>' +  
                '<td>' + data.Contents[index].LastModified + '</td>' + 
                '</tr>';
            }
            indexhtml += htmlfooter;
            
            params = {
                Bucket: indexhtmlbucket,
                Key: htmlfilename,
                ACL: 'public-read',
                Body: indexhtml,
                ContentType: 'text/html'
            };
            s3.putObject(params, function(err, data) {
                if (err)
                    console.log(err, err.stack);
                else
                    console.log(data);
            });
        }
    });
    console.log(indexhtml);
    context.done(null, "done");
}
```
>    var param = {
>    　　Bucket: backetname,
>    　　Key:    backetkey,
>    　　ACL:    'public-read'
>    };

で、ACL : 'private'と書けば、アクセス期間を限定可能なsigned-urlになります。

**注意点が２つあります。**
ひとつめは、「index.html」を生成するバケット（「indexhtmlbucket」）は、**コンテンツをPUTするバケットとは別にしておく**必要があります。このコードを流用される場合は、ご自分のBucketに変更する必要があります。また、Bucketは、Static Web Hostingの設定をしておく必要があります。
ふたつめは、Lambdaの「Change function configuration and role」の設定で、**S3の権限設定を変える必要があります**。デフォルトのRoleで設定されているのは、S3の読み込みと書き込み権限だけです。

```lang:Role
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:*"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:*"
      ],
      "Resource": [
        "arn:aws:s3:::*"
      ]
    }
  ]
}
```

#動作の概要#
1. S3 PUTをトリガーに、Lambaのコードが実行開始。
2. S3 PUTされたコンテンツのバケット名とkeyを取得。
3. S3 PUTされたコンテンツのアクセス権を設定（public-read or private）。
4. バケット内のコンテンツの一覧を取得。
5. tableタグでコンテンツ一覧を整形。
6. index.htmlを作成し、static web hostingの設定がしてある**別のバケット**にPUT。
7. おしまい。

#さいごに#
このコードは、改良の余地が多くあります。まず、削除が反映されません。これは、Lambdaで削除イベントのきっかけをつかめないためです。あと、処理をサボっているので、日本語のコンテンツに対応できていないなどがあります。
そのほか、S3 PUTをきっかけに、コンテンツのサムネイルを自動生成したり、署名付きURLのダウンロードできる期間もあわせて表示したり、styleシートを読み込み見た目リッチなhtmlにする、など思いつきました。

Lambdaを使うのは初めてでしたが、簡単ですね。Lambdaを使うと、S3のstatic web hostingも、「static」が取れてしまいます。便利！。

この記事は、私の所属している組織とは何の関係もありません。