S3
twilio
lambda

TwilioのIPメッセージングをAWS lambdaとs3で実装

More than 1 year has passed since last update.

昨年5月のSIGNALイベントで発表されたTwilioのIPメッセージングサービスが、オープンβ状態となっているので、公開されているデモプログラムをAWS Lambda(API Gateway)とS3を使ってAWS用に移植してみました。
IPメッセージングとは、チャットを実現するサービスで、すでにJavaScript、iOS、AndroidのSDKもリリースされています(Androidだけはドキュメントがまだ整備されていません)。
IPメッセージング
サンプルプログラムも準備されていますが、今回はそのサンプルプログラムをAWS LambdaとS3に移植することで、サーバーレスアーキテクチャでIPメッセージングを実現したいと思います。

システム構成図

今回のシステム構成は次のようになります。
構成図 (2).png

① S3から静的WebコンテンツとJavaScriptをダウンロード。
② JavaScriptからIPメッセージングのアクセストークンを取得。
③ 別のクライアントとメッセージの交換。
この図からもわかるように、Lambdaを利用するのはIPメッセージングのアクセストークンを取得するときのみで、実際のメッセージのやり取りはクライアントとTwilioサーバー間で行われます。

準備

IPメッセージングを利用するには、以下の情報が必要です。それぞれ手順に従って事前に取得しておいてください。

twilioアカウント

Twilioのアカウントを持っていない方は、まずはこちらの記事を参考にアカウントを取得してください。
Twilioアカウントの新規作成

IPメッセージング Service SID

Twilioのアカウントが取得できたら、以下の手順に従いIPメッセージングのサービスを生成してください。
1. 以下のURLにアクセスします(要ログイン)。
  https://jp.twilio.com/user/account/ip-messaging/services
2. 「IPメッセージングサービスを作成する」のボタンを押します。
3. フレンドリーネームに「ipmlambda」と入力して「作成」ボタンを押します。
4. IPメッセージングサービスが生成されますので、「Service SID」に表示されている文字列を記録しておきます。

Twilio API Key

以下の手順でAPIキーを生成します。
1. 以下のURLにアクセスします(要ログイン)。
  https://jp.twilio.com/user/account/ip-messaging/dev-tools/api-keys
2. 「APIキーを作成する」のボタンを押します。
3. フレンドリーネームに「IP Messaging」と入力して「Create API Key」ボタンを押します。
4. API KeyのSidとSecretが表示されるので、どちらも記録しておきます。特に、API Secretについては、後から表示させることができないので、忘れずに必ず記録しておいてください。

AWS Command Line Interfaceのインストール

この記事では、AWS CLIを使って作業をしていきますので、以下の記事を参考に予めインストールしておきます。
AWS Command Line Interfaceのセットアップ
AWS Command Line Interfaceの設定

コードの取得

一連のコードをGitHubにあげてありますので、こちらをダウンロードしてください。

sh
$ git clone https://github.com/mobilebiz/ipmlambda.git

次に、必要なライブラリをnpmコマンドを使ってダウンロードします。

sh
$ cd ipmlambda
$ npm install

※もしaws-sdkのインストールに管理者権限が必要というエラーが出た場合は、sudoを付けて実行してください。

AWS Lambdaのロールの作成

Lambda functionのロールをCLIを使って作成しておきます。
必要なポリシーはダウンロードしたフォルダ内のrole-policy.jsonとして準備してあります。

role-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    }
  ]
}

このファイルを使ってロールを作成するには、ダウンロードしたファルダ内で以下のコマンドを実行します。

sh
$ aws iam create-role --role-name ipmlambda_role --assume-role-policy-document file://role-policy.json

続けて、作成したロールにCloudWatchLogsへの書き込み権限をアタッチします。

sh
$ aws iam attach-role-policy --role-name ipmlambda_role --policy-arn "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"

作成されたロールを確認します。

sh
$ aws iam get-role --role-name ipmlambda_role

jsonファイルが表示されます。
後ほど使うので、作成されたロールの中にあるArn情報(arn:aws:iam::xxxxxxxxxx:role/ipmlambda_rolr)を記録しておいてください。

コーディング

IPメッセージングでは、チャットを実行する前にアクセストークンを取得する必要があります。アクセストークンの取得はサーバー経由で行うことが推奨されているので、今回はこの部分をAWS LambdaとAPI Gatewayを使って実装します。
今回、Lambda上のfunctionとして登録するのは、srcフォルダ内のindex.jsとrandos.js、さらにこれらの内部で利用するライブラリ群です。この内、メインとなるのは「index.js」です。

src/index.js

準備のところで取得しておいた内容をindex.jsに反映させます。
srcフォルダ内にあるindex.jsをエディタで開き、11行目からの変数に、先ほど取得した内容にそれぞれ書き直してください。

node.js
var twilio = require('twilio')
  , _accountSid = 'Your Twilio Account SID' ← TwilioのアカウントSID
  , _apiKey = 'Twilio API Key' ← TwilioのAPIのSID
  , _apiSecret = 'Twilio API Secret' ← TwilioのAPIのSecret
  , _serviceSid = 'IP Messaging Service SID' ← IPメッセージングのService SID
  , AccessToken = twilio.AccessToken
  , IpMessagingGrant = AccessToken.IpMessagingGrant
;

ローカルで単体テスト

今回は、ローカルでテストができるように、localtest.jsを作成してあります。
以下のコマンドを実行してみます。

sh
$ node localtest.js
結果
Process start.
Received event:
 {
  "param": {
    "device": "browser"
  }
}
Process completed.
Test completed with succeed. DaftWendyZimmerman, eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiIsImN0eSI6InR3aWxpby1mcGE7dj0xIn0.eyJqdGkiOiJTSzY1MzQ2YTQ1MzUyOWU3Y2I5ZTkxYTZhNDJlOTJkNzZhLTE0NTMwMTcxMDkiLCJncmFudHMiOnsiaWRlbnRpdHkiOiJEYWZ0V2VuZHlaaW1tZXJtYW4iLCJpcF9tZXNzYWdpbmciOnsic2VydmljZV9zaWQiOiJJUzk0ZGE5ZjYwNGQ3ZjQ2YTZhOGZiOTY4Yjk1MzUyNzhmIiwiZW5kcG9pbnRfaWQiOiJUd2lsaW9DaGF0RGVtbzpEYWZ0V2VuZHlaaW1tZXJtYW46YnJvd3NlciJ9fSwiaWF0IjoxNDUzMDE3MTA5LCJleHAiOjE0NTMwMjA3MDksImlzcyI6IlNLNjUzNDZhNDUzNTI5ZTdjYjllOTFhNmE0MmU5MmQ3NmEiLCJzdWIiOiJBQ2RhMGIxZmNkMDQyMGQzNGRiOGJmMjJkMTA4YWExYjU1In0.rEXZEGgd45oDxYYmM-5-qWOQjvJlfdl_MW4UZBBScyU

Test completed with succeed.が表示されていればテストはOKです。もしエラーが出るようであれば、先ほど修正したコードの値が間違っている可能性があります。

Lambda functionの新規登録

Lambdaに登録するためには、ソースファイルの他に、ライブラリなどをまとめてzipファイルにする必要があります。今回はgulpを使って、一連のビルドを自動化するようにしてあります。

sh
$ gulp build
結果
[16:57:57] Using gulpfile ~/Documents/workspace/test/ipmlambda/gulpfile.js
[16:57:57] Starting 'build'...
[16:57:57] Starting 'clean'...
[16:57:57] Finished 'clean' after 15 ms
[16:57:57] Starting 'copySrc'...
[16:57:57] Starting 'copyModule'...
[16:57:58] Finished 'copySrc' after 112 ms
[16:57:58] Finished 'copyModule' after 761 ms
[16:57:58] Starting 'zip'...
[16:57:59] Finished 'zip' after 643 ms
[16:57:59] Finished 'build' after 1.43 s

エラーが発生せずに終了したことを確認しておきます。このコマンドで、distフォルダ内にipmlambda.zipというファイルが生成されるので、そちらも併せて確認しておきます。

次に、create.shをエディタで開きます。

create.sh
#!/bin/bash
aws lambda create-function \
--function-name ipmlambda \
--zip-file fileb://./dist/ipmlambda.zip \
--role YOUR_ROLE \
--handler index.handler \
--runtime nodejs \
--region ap-northeast-1

5行目のYOUR_ROLEの部分を、先ほど作成したロールのArn情報に書き換えます。

最後に、以下のシェルスクリプトを使ってLambdaに登録します。

sh
$ ./create.sh
結果
{
    "CodeSha256": "VCrAnUm6QUxYG0ROsIjsuifQjgQcrVvDPzArKQxEdDs=",
    "FunctionName": "ipmlambda",
    "CodeSize": 1544967,
    "MemorySize": 128,
    "FunctionArn": "arn:aws:lambda:ap-northeast-1:XXXXXXXXXX:function:ipmlambda",
    "Version": "$LATEST",
    "Role": "arn:aws:iam::XXXXXXXXXX:role/ipmlambda_role",
    "Timeout": 3,
    "LastModified": "2016-01-17T08:03:47.962+0000",
    "Handler": "index.handler",
    "Runtime": "nodejs",
    "Description": ""
}

正常に作成が完了すると、jsonが戻ります。
この中のFunctioArnは後ほど利用するので、メモしておきます。

なお、今回は利用しませんが、もしLambda functionを更新したい場合は、update.shというシェルスクリプトも用意してありますので、そちらを利用してください。

API Gatewayの設定

Lambda functionの登録が終了したら、次のそのLambda functionを呼び出すAPI Gatewayを準備します。
ここからはAWSのマネージメントコンソールを使います。
マネージメントコンソールにログインしたら、API Gatewayの画面に移動します。
スクリーンショット 2016-01-17 18.44.59.png
「Create API」のボタンを押します。

スクリーンショット 2016-01-17 18.47.27.png
API name欄に「IPMLambda」と入力し、「Create API」ボタンを押します。

スクリーンショット 2016-01-17 18.50.34.png
Resources画面が表示されたら、「Create Resource」のボタンを押します。

スクリーンショット 2016-01-17 18.52.14.png
Resource Name欄に「token」と入力し(Resource Path欄は自動的に入ります)、「Create Resource」ボタンを押します。

スクリーンショット 2016-01-17 18.55.27.png
次に、いま作成した/tokenというリソースが選ばれている状態で、「Create Method」ボタンを押します。

スクリーンショット 2016-01-17 18.56.18.png
/tokenの下にリストが表示されるので、中から「GET」を選択し、その右側に表示されているチェックマークのアイコンを押して確定します。

スクリーンショット 2016-01-17 18.58.28.png
GETメソッドが選択されている状態で、Integration typeから「Lambda Function」を選択し、Lambda Resionは「ap-northeast-1」、Lambda Function欄に「ipmlambda」を選択したら、「Save]ボタンを押します。

スクリーンショット 2016-01-17 19.00.51.png
Lambda functionにAPI Gatewayからのアクセス権限を設定してよいかと聞かれるので、「OK」ボタンを押します。

スクリーンショット 2016-01-17 19.04.57.png

Integration Requestの設定

画面上の「Integration Request」をクリックし、さらに「Mapping Templates」の矢印を展開します。
スクリーンショット 2016-01-17 23.18.19.png
画面下にある「Add mapping template」をクリックします。

スクリーンショット 2016-01-17 23.20.01.png
Content-Typeの欄に「application/json」と入力して、右側のチェックアイコンをクリックします。

スクリーンショット 2016-01-17 23.22.21.png
画面右側のInput passthroughの右側にある鉛筆アイコンをクリックします。

スクリーンショット 2016-01-17 23.26.25.png
リストから「Mapping template」を選択、さらにTemplate欄に以下のコードを記述して、最後に「Mapping template」の右側のチェックアイコンをクリックします。

Template
{
  "param": {
#foreach( $key in $input.params().querystring.keySet() )
    "$key": "$input.params().querystring.get($key)"#if( $foreach.hasNext ),#end
#end
  }
}

これでIntegration Requestの設定は終了です。

CORSを有効にする

今回、S3に配置した静的コンテンツからAPI Gatewayを呼び出すため、API GatewayにCORS(Cross-Origin Resource Sharing)の設定をしておく必要があります。
画面左のResourcesから、/tokenを選択します。

スクリーンショット 2016-01-17 23.35.50.png
「Enable CORS」ボタンを押します。

スクリーンショット 2016-01-17 23.36.49.png
OPTIONメソッドを追加する画面が表示されるので、「Enable CORS and replace CORS headers」ボタンを押します。

スクリーンショット 2016-01-17 23.38.41.png
確認のダイアログで「Yes, replace existing values」ボタンを押します。

スクリーンショット 2016-01-17 23.40.03.png

以上でCORSの設定は終了です。

API Gatewayのデプロイ

「Deploy API」ボタンを押します。

スクリーンショット 2016-01-17 23.41.57.png
Deploy APIのダイアログが表示されるので、Deployment stage欄から「New Stage」を選択し、Stage name欄に「prod」と入力、最後に「Deploy」ボタンを押します。

スクリーンショット 2016-01-17 23.47.04.png

デプロイが終了したら、Invoke URL欄に表示されているURLをメモしておきます。

API Gatewayのテスト

ブラウザを開き、作成したAPI Gatewayのurlに/tokenを付加して表示してみます。
スクリーンショット 2016-01-17 23.52.24.png
画面のように、identityとtokenがjsonで戻ればAPI Gatewayの設定はすべて完了です。
※/tokenを忘れずに。

静的コンテンツの設定

ローカルフォルダのpublic/index.jsをエディタで開きます。
3行目のurlを、先ほどテストしたurl(/tokenを付けたもの)に書き換えます。

public/index.js
$(function() {
    // AWS API Gateway url
    var url = "https://xxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/token"; ← ココ

    // Get handle to the chat div
    var $chatWindow = $('#messages');

S3の設定

最後に、IPメッセージングのコンテンツをS3にアップロードしていきます。

バケットの作成

まずは、ユニークになるようなバケット名を決めて、作成をします。

sh
$ aws s3 mb s3://バケット名 --region ap-northeast-1
結果
make_bucket: s3://バケット名

コンテンツのアップロード

public配下のコンテンツをアップロードします。

sh
$ cd public
$ aws s3 sync ./ s3://バケット名/ --region ap-northeast-1
結果
upload: ./index.html to s3://バケット名/index.html
upload: ./index.css to s3://バケット名/index.css
upload: ./index.js to s3://バケット名/index.js

静的ウェブサイトホスティングの設定

sh
$ aws s3 website s3://バケット名 --index-document index.html --region ap-northeast-1

ポリシーの設定

最後にアップロードしたコンテンツのポリシーを設定します。
ダウンロードしたフォルダ内のpolicy.jsonをエディタで開き、Resourceを自身のバケット名に修正します。

policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
        "Effect": "Allow",
        "Principal": "*",
        "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::バケット名/*" ← ココ
    }
    ]
}

ポリシーを適用します。

sh
$ cd ..
$ aws s3api put-bucket-policy --bucket バケット名 --policy file://policy.json --region ap-northeast-1

テスト

以上ですべての設定が終了です。
最後にブラウザからS3のコンテンツにアクセスし、チャットが始まることを確認します。

sh
$ open http://バケット名.s3-website-ap-northeast-1.amazonaws.com

スクリーンショット 2016-01-18 01.24.09.png

まとめ

TwilioのIPメッセージングを使うと、従来のようにSoketサーバーを構築しなくてもチャットシステムを構築することができます。また、LambdaやS3を活用することで、ランニングコストも抑えることが可能になります。