S3 + Cognito + DynamoDBでサーバレスな簡易コメント投稿システム

こんにちは。独りAdventCalendar風ブログ更新の12月5日分の記事です。4日分の投稿が遅かったので間が開かずの更新です。

今回参考にさせていただいたのはQiitaに投稿されているこちらの記事。qiita.com

はてブもたくさんついていて、良い記事だとういことがうかがえます。それにびんj(ryというわけではなく、最近はサーバレスなシステムをどうやって作っていったらいいかと考えていたりとか、触っていないAWSのサービスが多いのでなんとかしていろいろ触ってみたいなと思っていたりすることが重なってこのネタに至ったわけです。

S3バケットの準備

コメントシステムのHTMLを配置するためにStatic Website Hostingを利用します。Bucket名は「matetsu.example.com」とし、PropertiesのStatic Website Hositing(静的ウェブサイトホスティング)メニューから「Enable website hosting」を選択し、必要な項目を埋めて「Save」でひとまず完了。

Static Website Hostingの設定方法については、こちらが参考になるかと思います。

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Amazon Web Services実践入門 (WEB+DB PRESS plus)

Coginitoの設定

Cognitoはモバイルデバイスの認証やデータ同期などに利用されるAWSのサービスの一つです。Cognitoを使ってSTSの一時的セキュリティ認証機能*1を使ってHTMLやJavascriptに固定のIAMの認証情報を埋め込まずとも時限的な認証情報を得ることができるようになります。

Identity Poolを作成します。認証なしで時限的な認証をするために、「Enable access to unauthenticated identities」にはチェックを入れるようにしましょう。

f:id:matetsu:20151205215152p:plain

Cognito用のauthenticated RoleとUnauthenticated Roleが作成されるので、とりあえずはそのまま「Allow」で進む。両者のPolicyはそれぞれ以下の様な感じ。

Authenticated

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Unauthenticated

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "mobileanalytics:PutEvents",
        "cognito-sync:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

Identity Poolの作成が完了するとSample Codeの画面になるので、各言語のSample Codeを眺めてみるのもよし。ここで必要なのはIdentity Pool IDの部分。これを控えておきます。

IAM RoleのPolicyを追加

先ほどCognitoのIdentity Poolを作成したところで作ったIAM RoleのPolicyではCognito関連の操作しかできません。S3に配置したHTML+Javascriptかたの操作でDynamoDBに出たを入れられるようにしたいので、DynamoDBの操作権限も追加したいと思います。今回使うのはUnauthenticatedなほうだけなので、そちらにだけ。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "dynamodb:BatchGetItem",
                "dynamodb:BatchWriteItem",
                "dynamodb:DeleteItem",
                "dynamodb:GetItem",
                "dynamodb:PutItem",
                "dynamodb:Query",
                "dynamodb:Scan",
                "dynamodb:UpdateItem"
            ],
            "Resource": [
                "arn:aws:dynamodb:ap-northeast-1:ACCOUNT_NO:table/TABLE_NAME"
            ]
        }
    ]
}

HTML+JSを配置してみる

とりあえず今日はformに投稿された内容をDynamoDBにPutして、Scanで取ってきて表示するというもの。JSはあまり得意ではないので、いろいろなところから拝借した感じになってしまいました。特に表示データを加工もしていないし見た目もあれなので、どうにかしたい。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>コメント投稿</title>
    <script src="https://sdk.amazonaws.com/js/aws-sdk-2.2.21.min.js"></script>
    <script>
        var $id = function(id) { return document.getElementById(id); };
        AWS.config.region = "ap-northeast-1";
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({IdentityPoolId: "POOL_ID"});
        AWS.config.credentials.get(function(err) {
            if (!err) {
                console.log("Cognito Identify Id: " + AWS.config.credentials.identityId);
            }
        });

        var table = "blog-neta2";
        var dynamodb = new AWS.DynamoDB({params: {TableName: table}});

        function postComment() {
            var now = Math.floor(new Date().getTime() / 1000);
            var itemParams = {
              Item: {
                article: {S: location.pathname},
                timestamp: {N: "" + now},
                data: {S: $id("comment").value}
              }
            };

            dynamodb.putItem(itemParams, function(err){
                if(err){
                    alert("Error: " + err);
                } else {
                    load();
                }
            });
        }

        function load() {
            $id("comment").value = "";
            dynamodb.scan({
                ScanFilter: {
                    article: {
                        ComparisonOperator: 'EQ',
                        AttributeValueList: [
                            { S: location.pathname }
                        ]
                    }
                }
            }, function(err, data) {
                if (err) {
                    alert(err);
                } else {
                    var c = document.getElementById('comment_list');
                    c.innerHTML = '';

                    data.Items.map( function(item) {
                        var p = document.createElement('p');
                        p.textContent = new Date(item.timestamp.N * 1000) + ": " + item.data.S;
                        return p;
                    }).forEach( function(p) {
                        c.appendChild(p);
                    });
                }
            });
        }
    </script>
</head>
<body onload="load();">
    <div class="wrapper">
        <div id="postform">
            <form>
                <table>
                    <tr>
                        <th>Comment</th>
                        <td>
                            <textarea id="comment" cols="40" rows="5" name="comment"></textarea>
                        </td>
                    </tr>
                </table>
                <div id="button_area">
                    <input onClick="postComment();" type="button" value="投稿" id="post_button" />
                </div>
            </form>
        </div>
    </div>
    <div id="comment_list">
    <div>
</body>
</html>

これでテキストエリアに入力して「投稿」ボタンを押していくと、こんな感じになる。

f:id:matetsu:20151206015529p:plain

とりあえず動いてますといった感じ。

おわりに

今日のところはとりあえず簡単なコメントシステムっぽいものができた。これをうまいことちゃんと作れば、WPやMTをStatic Website HostingにHTMLを吐き出してつかった場合にもコメントが出来るようになる(ということをイメージしてみたんだがどうだろうか)。もちろんSEO云々は全く考慮していませんが、WPとかMTを使ってる人の中にはサーバ管理がそんなに得意ではない人もいるのでそのあたりのコストを考えたらコメントの部分くらいは気にしなくてもいいのかなと(といいつつ、そのへんの分野も明るくないのでよくわからないというのが本音)。

とりあえず試してみた感が強いので、もう少し面白みのあることに繋げられるといいなと思う。とくにDynamoDB StreamをつかってさらにLambda Functionを呼び出して云々とかできたらいいなと。

DynamoDBをまともに使ったことがなく、突貫で使ったので正しく使えているのかが不安なので、あとでちゃんとドキュメントを読みます(読んでないのかと)。まずい所があったらコメンとなりSNSなりで教えていただければと思いますm(_ _)m

おわりにのおわりに

5日目もやっぱり日付またいでの更新。。。当日中に更新できる日は来るのだろうか。