LoginSignup
8

More than 5 years have passed since last update.

社内でクイズ番組的なノリを実現したくて、簡易的なWebアプリケーションを Lambda でととのえる。

Last updated at Posted at 2016-12-23

アドベントカレンダーですので、箸休め記事を張り切って書いていきたいと思います! @mochizukikotaro です。

日常の風景

弊社ではときどき社内で、すごい小さなプログラミング勉強会が実施されます。クイズを2、3個出すような短いものなので、『つぶやき勉強会』と呼んでいます。先輩エンジニアにひねくれた問題をだして、間違えを誘いだしヒンシュクを買ったりすることができます。

だいたい下の図のようなイメージです。

IMG_1990.JPG

これは、これで楽しいのですが。毎回、

司会 : 「これは何だとおもいますか?では、ラーメンマンさん!」
:man_with_gua_pi_mao_tone1: : 「んー false」
司会 : 「なるほどー。では、ミスターカーメンさん!」
:construction_worker: : 「ん、ん、.. true」
司会 : 「ははーん。では..(以下同文」

という会話を繰り返すのに、すこしだけ飽きてきてしまいました。

ふと、

クイズ番組みたいにやりたい。

と思いました。テレビでやってるクイズ番組的な...解答者は手元のパネルに記載して、司会者の合図で「それでは一斉に解答オープン!」的なやつ。

今回は、それらしきものを S3 + Lambda + API Gateway で 簡易的でクローズド使用なWebアプリケーションとして作ってみようとしたお話になります。

できあがりイメージ

下の gif は解答者側の画面になります。流れは以下のようなものです。

  • 問題を読む
  • 解答と名前を入力して
  • 送信
  • しばらく待ってから結果ボタンを押すと
  • (この待ち時間中に、admin画面側で出題者が ○ × を付けています)
  • 全員の解答結果が ○ × つきでオープン

qr_demo.gif

つまり、ひとことで言うと、

はじめに夢見たクイズ番組とは、まあ、違います。

クイズ番組のことは忘れてください。

これは、なんとなくクイズに答えて、みんなの解答がまとめて見れる、そんなWebアプリケーションです。

(WebSocket とかを考えたのですが、Lambda を触ってみたいという気持ちが主なモチベーションだったので細かいことは無視しました。)

ここからレシピ紹介

まず、アーキテクチャー図は以下のようなイメージです。

Architecture.png

S3 に html とか js ファイルを置いて、静的ウェブホスティングによりユーザーからアクセス可能にします。そして、ユーザーの解答や名前情報を保有するのは DB をつかわずに、JSON ファイルでまかないました。その json ファイルも S3 に置いておきます。

ユーザーのアクションで js のイベントを発火させ ajax で API Gateway を叩き Lambda function を呼び出して、それが json ファイルを読んだり、アップデートしたりして情報を更新していきます。

ここから大枠の流れとソースコードをステップバイステップで書きたいと思います。ぼくがハマったポイントを添えて。(ただ、ちょっと複雑でうまくお伝えできるか...)

ステップ

  1. まずは S3 に html を配置してアクセスできるようにする。
  2. Lambda に Function を用意する
  3. API Gateway にリソースを作って Lambda と連結する

対象読者

  • Lambda 使ったことない
  • S3 でWebホスティングしたことない
  • API Gateway という名前をはじめて聞いた
  • Node.js を普段書かないけど、JavaScrip は書いている
  • CORS なにそれ?
  • つまり私

Step1: まずは S3 に html を配置してアクセスできるようにする。

  • 適当な名前でバケットを作成(便宜的に examplebucket とします)
  • index.html と error.html を適当に用意してアップロード
  • プロパティ -> 静的ウェブホスティング -> ホスティングを有効にする

hosting.png

バケットポリシー
{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"PublicReadGetObject",
        "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::examplebucket/*"
      ]
    }
  ]
}
  • これでエンドポイントにアクセスできるようになりました。

ここまでは、問題ないと思います。 :grinning:

Step2: Lambda に Function を用意する

作り方はとても簡単。Lambda のページに行き。
「Create a Lambda function」 -> 「Blank Function」を選んで -> 「Next」ボタン。とすれば、設定画面までたどり着きます。

スクリーンショット 2016-12-23 16.23.26.png

ここで、適当にファンクション名をつけたらやることは3つです。

  • IAMユーザーをつくって key を用意する
  • IAMロールつくっておく
  • コード書く

IAMユーザーをつくっておく

まず、Lambda の編集画面はそのままにしておいて、IAM に行きます。「ユーザーを追加」ボタンをクリックして、適当にユーザー名を入れて、アクセスの種類で「プログラムによるアクセス」をチョイスして「次のステップ」に進みます。そして、「既存のポリシーを直接アタッチ」で「AmazonS3FullAccess」をアタッチします。スクリーンショット 2016-12-23 15.28.46.png

あとは、道なりに進めば

  • アクセスキーID
  • シークレットアクセスキー

を取得できます。大切に管理してください。

IAMロールをつくっておく

実行ロール (IAM ロール) を作成する この辺を参考にロールを作成してください。 :innocent:

Lambda 編集ページの続きにもどります

まず、コードはこんな感じです。node.js です。

Lambda_function_S3のjsonファイルを読むサンプル
'use strict';

var AWS = require('aws-sdk');
var AWS_ACCESS_KEY_ID = {取得したアクセスキーID};
var AWS_SECRET_ACCESS_KEY = {取得したシークレットアクセスキー};
var S3_BUCKET_NAME = 'examplebucket';
var FILE_NAME = 'example.json'; // 適当に json 書いて S3 にアップしておいてください

exports.handler = (event, context, callback) => {

  AWS.config.update({
    accessKeyId: AWS_ACCESS_KEY_ID,
    secretAccessKey: AWS_SECRET_ACCESS_KEY,
    region: 'ap-northeast-1'
  });
  var s3 = new AWS.S3();

  var getParams = {
    Bucket: S3_BUCKET_NAME,
    Key: FILE_NAME
  };

  s3.getObject(getParams, function(err, data){
    if (err) {
      console.log(err);
    } else {
      callback(null, data.Body.toString());
    }
  });
}

そして、Lambda 作成ページの下の方にある「Exsinting role」に上で作成したロールを入れる。

スクリーンショット 2016-12-23 16.14.55.png

あとは道なりで Lambda function の作成は完了です。 :grinning:

Step3: API Gateway にリソースを作って Lambda と連結する

API Gateway のページにいき適当な名前で新規 API を作成してください。そうしましたら、アクションボタンから適当にメソッドを追加して、ポチポチと先ほどの Lambda function を選択します。

スクリーンショット 2016-12-23 16.28.10.png

(上キャプチャはすぐにメソッド追加しちゃってますが、アクションボタンからリソースも簡単につくれます。これで任意のパスに GET とか POST とかできるようになります。)

保存してください。

残る手順は2つほどです。

  • 統合リクエストのマッピングテンプレート設定
  • CORSの有効化とAPIデプロイ

安心してください。後者はボタンポチポチです。(CORSとかの概念がわかっていない僕はとてもハマリましたが。)

統合リクエストのマッピングテンプレート設定

※ここの設定は、API Gateway に POST する場合、そのデータにアクセスするための対応です。なので、上の Lambda function の例とは直接絡みません。 下に別の例を記載します。

スクリーンショット 2016-12-23 16.41.08.png

上キャプチャの通り「統合リクエスト」へ進みます。そして、下キャプチャのように、Content-Type や テンプレートを書きます。

スクリーンショット 2016-12-23 16.43.46.png

テンプレート
{
"body" : $input.json('$')
}

上のように書くことで、API Gateway に対してポストしたデータを Lambda function 側で、event.body としてオブジェクトのように扱うことができます。(これを知るまでは、どこを彷徨っているのかも分からない状態でした。 :astonished:

Lambda function の別例

Lambda_function_S3にプットする例
exports.handler = (event, context, callback) => {

  // (略)

  var obj = event.body; // こんな風に POST データにアクセスできる

  var jsonStr = JSON.stringify(obj);
  var putParams = {
    Bucket: S3_BUCKET_NAME,
    Key: FILE_NAME,
    Body: jsonStr,
    ContentType: 'application/json; charset=utf-8',
    ACL: 'public-read'
  };
  s3.putObject(putParams, function(err, data){
    if (err) {
      callback(null, err);
    } else {
      callback(null, jsonStr);
    }
  });
}

CORS の有効化と API デプロイ

スクリーンショット 2016-12-23 17.01.28.png

ポチ、ポチ、ポチで完了です。

S3 エンドポイントから API Gateway エンドポイントへのアクセスが走るので、CORS を設定しないと、XMLHttpRequest cannot load となります。(最初ぼくは、JSONPをどうやったらつくれるんだろうとしばらく彷徨っていました。 :astonished:

あ、デプロイするときに、ステージを聞かれるので、適当に prod などのステージ名をつけてみてください。

これで下キャプチャのダッシュボードからエンドポイントを確認できます。このエンドポイントに向かって後で ajax 叩きます。

スクリーンショット 2016-12-23 18.37.38.png

以上で、設定完了です。 :grinning:

ajax 部分: S3 に置いてある HTML で発火する JS を紹介します

全部はまだ公開できる状態ではないので、部分的ですが、雰囲気をお伝えするために。
「挑戦する!」ボタンを押したら、API Gateway に解答と名前をポストする部分を記載します。

index.js_部分的な紹介
var API_BASE_URL = {上で用意した API Gateway のエンドポイント};
var challengePromise = function(){
  var nickname = $('#nickname').val();
  var answer =   $('#answer').val();
  var defer = $.Deferred();
  var str = JSON.stringify({ "nickname": nickname, "answer": answer });
  $.ajax({
      type: 'POST',
      url: API_BASE_URL + '/challenge', // challenge というリソースを用意してた場合 
      contentType: 'application/json',
      data: str,
      success: defer.resolve,
      error: defer.reject
  });
  return defer.promise();
}

$('#challenge').on('click',function(){
  challengePromise().done(function(data){
    $('#form_area').remove();
    document.cookie = "quiz_room_id=" + JSON.parse(data).quiz_room_id;
    appendWaitingArea();
  });
  challengePromise().fail(function(r,s,e){
    console.log('失敗');
  })
});

おわりに感想

最近、サーバーレスという言葉をよく聞くので、どんなものか知りたくて、Lambda + API Gateway を触ってみました。ネタとしてクイズ番組アプリを作成しはじめたのですが、慣れないので、重複コードは沢山できるし、アクセス権限をコントロールしきれてないし、Lambda のデプロイについてはまだ勉強してないので、いちいちコピペしているし、大変です :astonished:。CORS のこと分からないし、DynamoDB 使ったことないし(未だ使ってないけど)、途中で WebSocket したいと思って、AWS IoT に手を出そうとして調べ始めたら心がバキバキに折れるし、大変です :astonished:でも、サーバーを立ち上げないで、こういったことができて、面白さに少し触れることができた気がしました :slight_smile:

(結局アプリはととのったんだか、ととのっていないんだか... いまのところ1問だけ解答できるという貧弱仕様状態なので、引き続き手を加えて、育てて行きたいと思います。)

引き続き勉強していきたいと思います。

ありがとうございました!

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8