Lambdaでジャンケンゲームを作ろう

# 概要

Lambdaでジャンケンゲームを作ります。
http://lambda-janken.s3-website-ap-northeast-1.amazonaws.com [停止中]


![Screen Shot 2014-11-26 at 5.52.41 PM.png](https://qiita-image-store.s3.amazonaws.com/0/19485/58d1bf45-d754-0c76-ce99-0292175b6d52.png)
※データが流れている部分を枠で表示しています
　↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
![Screen Shot 2014-11-26 at 5.52.49 PM.png](https://qiita-image-store.s3.amazonaws.com/0/19485/8b6d4e35-014c-3d04-6bd3-0d4fdb1ec4d1.png)
※グーチョキパーで結果を表示します
※素材：ごっこあそび - http://gokkoasobi.com/omen_jyanken.html

AWSのサービスをフル活用します。
「JavaScriptだけで出来るやん・・」というツッコミは置いておいて、以下のサービスを組み合わせて実装します。
Amazon Cognito
Amazon Kinesis
Amazon Lambda
Amazon SQS
Amazon S3

ブラウザで受けたリクエストをKinesis に流し込み、これを受けて発火したLambdaファンクションで、グー・チョキ・パーを表す乱数を生成し、SQSにメッセージとしてに結果をキューイングします。ブラウザはキューイングされたSQSを取得し、グー・チョキ・パーのイラストを表示して切り替えます。

※現時点でLambdaはプレビューのため、すべて us-east-1 リージョンを利用します。

# Kinesis 設定
AWSマネージメントコンソールから適当な名前でストリームを作成します。

```lang:setting
stream_name = LambdaJanken
ARN = arn:aws:kinesis:us-east-1:xxxxxxxxxx:stream/LambdaJanken
number of shard = 1
```

# SQS 設定
AWSマネージメントコンソールから適当な名前でキューを作成します。

```lang:setting
queue_name = LambdaJanken
URL = https://sqs.us-east-1.amazonaws.com/xxxxxxxxxx/LambdaJanken"
ARN = arn:aws:sqs:us-east-1: xxxxxxxxxx:LambdaJanken
```

# Lambda 設定
index.jsを作成します。

```lang:index.js
var aws = require('aws-sdk');
var kinesis = new aws.Kinesis({region:'us-east-1'});
var sqs = new aws.SQS({region:'us-east-1'});
exports.handler = function(event, context) {
   var rand = Math.floor( Math.random() * 3 );
   var params = {
      MessageBody: String(rand),
      QueueUrl: 'https://sqs.us-east-1.amazonaws.com/xxxxxxxxxx/LambdaJanken',
      DelaySeconds: 0,
   };
   sqs.sendMessage(params, function(err, data) {
     if (err) {
        console.log(err, err.stack);
     }else{
       console.log(data);
       context.done();
     }
   });
}
```

index.jsをzip化して、index.zipを作成します。

AWS CLIからFunctionを作成、アップロードします。

```lang:upload-function
aws lambda upload-function \
--region us-east-1 \
--function-name LambdaJanken \
--function-zip index.zip \
--runtime nodejs \
--role arn:aws:iam::xxxxxxxxxx:role/lambda_exec_role \
--handler index.handler \
--mode event \
--timeout 30
```

AWS CLIからイベントをアタッチします。

```lang:add-event-source
aws lambda add-event-source \
--region us-east-1 \
--function-name LambdaJanken \
--role arn:aws:iam::xxxxxxxxxx:stream:role/lambda_invoke_role \
--event-source arn:aws:kinesis:us-east-1:xxxxxxxxxx:stream/LambdaJanken \
--batch-size 1 \
--parameters InitialPositionInStream=LATEST
```

# Cognito 設定
AWSマネージメントコンソールから適当な名前でプールを作成します。

```lang:setting
identity_pool_name = LambdaJanken
identity_pool_id = us-east-1:cf2xxxx-1xxx-4xxx-9xxx-1856xxxxxxxx
Unauthenticated Identities is checked
```

# IAM 設定

```lang:Cognito_LambdaJankenUnauth_DefaultRole
{
    "Version": "2012-10-17",
    "Statement": [{
        "Action": [
            "mobileanalytics:PutEvents",
            "cognito-sync:*"
        ],
        "Effect": "Allow",
        "Resource": [
            "*"
        ]
    }]
}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "kinesis:*",
      "Resource": "arn:aws:kinesis:us-east-1:xxxxxxxxxx:stream/LambdaJanken"
    }
  ]
}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "sqs:*"
      ],
      "Effect": "Allow",
      "Resource": "arn:aws:sqs:us-east-1:xxxxxxxxxx:LambdaJanken"
    }
  ]
}
```

```lang:Entities-cognito-identity.amazonaws.com
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Federated": "cognito-identity.amazonaws.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "cognito-identity.amazonaws.com:aud": "us-east-1:cxxxxx-1xxx-4xxx-9xxx-1xxxxxxxx"
        },
        "ForAnyValue:StringLike": {
          "cognito-identity.amazonaws.com:amr": "unauthenticated"
        }
      }
    }
  ]
}
```

# HTML 作成
```lang:index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Lambda ジャンケン</title>
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.0.28.min.js"></script>
<style type="text/css"><!--
	.waku{	position: absolute; top: 180px; left: 95px; z-index: 1;	}
	.gu{	position: absolute; top: 20px; left: 180px; z-index: 1; display: none;	}
	.choki{	position: absolute; top: 20px; left: 180px; z-index: 1; display: none;	}
	.pa{	position: absolute; top: 20px; left: 180px; z-index: 1; display: none;	}
	textarea {	margin-left: 50px; width: 850px; height: 300px; line-height: 1.5em;	}
--></style>
</head>
<body>
<img src="LambdaJanken.png" />
<img src="LambdaJanken_waku.png" class="waku" id="waku" />
<img src="gu.png" class="gu" id="gu" />
<img src="choki.png" class="choki" id="choki" />
<img src="pa.png" class="pa" id="pa" />
<textarea cols="40" rows="7" id="tex"></textarea>
<p style="padding-left:50px">素材提供：ごっこあそび - http://gokkoasobi.com/omen_jyanken.html</p>

<script>
var janken;
var tex = document.getElementById('tex');
var waku = document.getElementById('waku');

//Cognito
var params = {
	AccountId: "xxxxxxx",
	RoleArn: "arn:aws:iam::xxxxxxx:role/Cognito_LambdaJankenUnauth_DefaultRole",
	IdentityPoolId: "us-east-1:cxxxxxx-1xxx-4xxx-9xxx-1xxxxxxx"
};
AWS.config.region = 'us-east-1';
AWS.config.credentials = new AWS.CognitoIdentityCredentials(params);
AWS.config.credentials.get(function(err) {
	if (!err) {
		//Log
		tex.value = tex.value + "===== Cognito authentication =====" + "\n"
		tex.value = tex.value + "Cognito Identity Id: " + AWS.config.credentials.identityId + "\n";		
		//CSS
		waku.style.left = "280px";
	}
});

//Kinesis
var kinesis = new AWS.Kinesis();
var params1 = {
		  Data: 'Hello', /* required */
		  PartitionKey: 'partitionkey', /* required */
		  StreamName: 'LambdaJanken', /* required */
};
kinesis.putRecord(params1, function(err, data) {
	if (err){
		console.log(err, err.stack); // an error occurred
	}else{
		//Log
		tex.value = tex.value + "===== Kinesis put object =====" + "\n";
		tex.value = tex.value + "SequenceNumber: " + data.SequenceNumber + "\n";
		tex.value = tex.value + "ShardId: " + data.ShardId + "\n";	
		//Lambda
		tex.value = tex.value + "===== Lambda invoke function =====" + "\n";

		//CSS
		waku.style.left = "465px";

		//Log
		tex.value = tex.value + "===== SQS send message =====" + "\n";		
		
		//SQS
		var sqs = new AWS.SQS();
		var params2 = {
			QueueUrl: 'https://sqs.us-east-1.amazonaws.com/xxxxxxxx/LambdaJanken', /* required */
			MaxNumberOfMessages: 1,
			WaitTimeSeconds: 5
		};
		sqs.receiveMessage(params2, function(err, data) {
			if (err) {
				console.log(err, err.stack); // an error occurred
			}else{				
				janken = data.Messages[0].Body;

				//CSS
				waku.style.left = "650px";
				
				//Log
				tex.value = tex.value + "===== SQS retrieve message =====" + "\n";
				tex.value = tex.value + "ReceiptHandle: " + data.Messages[0].ReceiptHandle + "\n";
				tex.value = tex.value + "Body: " + data.Messages[0].Body + "\n";
								
				var params3 = {
			  				  QueueUrl: 'https://sqs.us-east-1.amazonaws.com/xxxxxxxxxxx/LambdaJanken', /* required */
			  				  ReceiptHandle: data.Messages[0].ReceiptHandle /* required */
			  	};
				sqs.deleteMessage(params3, function(err, data) {
					if (err){
						console.log(err, err.stack); // an error occurred
					}else{
						//Log
						tex.value = tex.value + "===== SQS delete message =====" + "\n";
						tex.value = tex.value + "RequestId: " + data.ResponseMetadata.RequestId + "\n";

						//CSS
						waku.style.display = "none";

						//Display Janken
						switch (Number(janken)){
							case 0:
								var ele = document.getElementById('gu');
								ele.style.display = "block";
								break;
							case 1:
								var ele = document.getElementById('choki');
								ele.style.display = "block";
								break;
							case 2:
								var ele = document.getElementById('pa');
								ele.style.display = "block";
								break;
						}
					}
				});
			}
		});
	}
});
</script>
</body>
</html>
```

```lang:ソースツリー
|-index.html（本体）
|-LambdaJanken.png (背景)
|-LambdaJanken_waku.png (オレンジ枠)
|-gu.png（グー）
|-choki.png（チョキ）
|-pa.png（パー）
```
これらファイルをS3へアップロードして公開します。


免責
こちらは個人の意見で会社とは関係ありません。