slackのincoming webhookからoutgoing webhookに移行するついでにサーバレス化したお話し

slackのincoming webhookからoutgoing webhookに移行するついでにサーバレス化したお話し
# 背景
弊社のときふじが過去に「[社WebをAWS s3 + cloudfrontなサーバーレス環境に移行した話](http://qiita.com/tokifuji/items/edd6be2ee6ebcdd86d94)」こんな知見を残していたことから、そろそろサーバレス化進めていくかと考えていました。


# 参考にしたURL
[SlackからAWS API Gatewayを通してLambdaを起動するまで](http://qiita.com/sgp/items/fc803c285cb560dab796)

Lambdaの使い方をローカルで試すのであればdocker-lambdaが早いです。
[AWS Lambdaの開発環境を構築～docker-lambdaの紹介～](http://www.mediaplex.co.jp/blogs/creative/aws-docker-lambda/)

# まずは概要図から
slackから特定の文字列を入力すると、outgoing webhookでAPI GatewayのURLにform-urlencodedで送信され、AWS Lambdaにjson形式でデータが渡り、関数を経由してjsonが返ってきます。返されたAPI Gatewayはそのままslackに送信してくるという仕様です。
※図の作成に使ったのは[Cacoo](https://cacoo.com/diagrams/#)です。

![lambda-apigateway-slack.png](https://qiita-image-store.s3.amazonaws.com/0/2567/3dc01334-8681-4f9c-79f9-3172b5eb52fd.png)

# はじめにLambda関数を作成する


AWSコンソールから、はじめてAWS Lambdaにアクセスするとこんな画面がでてきます。「今すぐ始める」をクリックしましょう。

## はじめだけ表示される画面
![lambda_get_start.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d1cb0e24-4a47-572f-0070-0ee98831601d.png)

次に設計図の選択画面が出るので、ランタイムの選択やフィルターを駆使して目的の設計図（テンプレート）を探しましょう。最終的に「Blank Function」ばかりつかうことになるでしょう。

## 関数の作成を行うとはじめに表示される画面
![lambda_get_template.png](https://qiita-image-store.s3.amazonaws.com/0/2567/31540d30-740d-01c8-c5f4-455837493d24.png)

フィルターのテキストボックスに「hello」と入力すると、「hello-world」が出てきます。こちらを使って入門編を進めても良いです。

![lambda_set_filter.png](https://qiita-image-store.s3.amazonaws.com/0/2567/e9784627-dc83-2f86-eeff-bb733432d25f.png)

一度でも関数を作ったことがある場合は、AWS Lambdaの画面はここからになります。「Lambda関数の作成」ボタンからいつでも新しいLambda関数を作成することができます。

![lambda_dashboard.png](https://qiita-image-store.s3.amazonaws.com/0/2567/8114c626-2ac7-9b37-60a3-c2d9dc626139.png)

設計図の選択で「次へ」ボタンを押すと、トリガーの設定画面に遷移します。ここで連携するAWSサービスを選びますが、今回はAPI Gateway側からLambda関数を選択するので、スキップしても構いません。

![lambda_set_trigger.png](https://qiita-image-store.s3.amazonaws.com/0/2567/625f90fa-964a-b61e-ec87-3462536717dc.png)

トリガーの設定画面からもAPI Gatewayを選ぶことができますが、今回はこのトリガーを使いません。

![lambda_set_api_gateway.png](https://qiita-image-store.s3.amazonaws.com/0/2567/769be913-d29f-afce-c45f-5ff2e251d4e4.png)


トリガーを設定してもしなくても「次へ」を押して関数をコーディングできる画面に遷移します。

![lambda_set_iam.png](https://qiita-image-store.s3.amazonaws.com/0/2567/af2cc547-9412-d08f-a117-ef7e106342c0.png)

## 関数の設定画面

こちらで名前とランタイムを入力します。
名前は適当に「slackAPI」、ランタイムには「Node.js 6.10」を選択しました。

![lambda_set_function.png](https://qiita-image-store.s3.amazonaws.com/0/2567/b2175277-a964-3dd5-54ee-ca10d2d8481c.png)

## ランダムにテキストを返す関数のコード

そして、コードテキストエリアに、以下のコードを記述します。
res.usernameはslack上のbot名になります。
arrayにランダムに選択される文章をカンマ区切りで記述しています。

```js:index.js
exports.handler = (event, context, callback) => {

    var res = {};
    res.username = "outgoing-webhook-from-lambda";
    var array = [
        'わからないことは<https://api.slack.com/custom-integrations|こちら>を参照してください。',
        'こんにちは、私は'+ res.username +'です。\nAWSのLambda functionで処理されています。\n',
        'API Gatewayを使ってLambda functionを起動する良い練習になりますね。',
        'outgoing-webhookを使用すればLambda functionをAPI Gateway経由でキックすることができます。'
    ];
    res.text = array[Math.floor(Math.random() * array.length)];

    callback(null, res);
};
```

## Lambda 関数ハンドラおよびロールを設定する
関数の設定画面をもう少しスクロールすると、ハンドラとロールを設定する箇所が現れます。
ここでは、ハンドラは「index.handler」のままで、ロールに「テンプレートから新しいロールを作成」を選択、ロール名に任意のロール名を入力して、「次へ」ボタンをクリックします。

![lambda_handler_role.png](https://qiita-image-store.s3.amazonaws.com/0/2567/c847af2a-3631-d7f4-3150-812d465af181.png)

作成した関数の確認画面が表示されるので、「関数の作成」ボタンをクリックします。

![lambda_function_create.png](https://qiita-image-store.s3.amazonaws.com/0/2567/91adbd8e-93e9-12f7-a788-303e4726388d.png)

## 関数のテスト
関数のテスト画面が表示されるので、テストデータを準備して、テストを実行してもよいですが、ここでは省略します。

# API Gatewayを作成する

![api_gateway_menu.png](https://qiita-image-store.s3.amazonaws.com/0/2567/b426ccb5-5757-cd5d-6e78-2450a3bff1a4.png)


AWSコンソールのメニューからアプリケーションサービスにある「API Gateway」をクリックして、API GatewayのGetting Start画面を表示します。中央の「APIの作成」ボタンをクリックすると「新しいAPIの作成」画面に遷移します。

![api_gateway_create.png](https://qiita-image-store.s3.amazonaws.com/0/2567/38f6b774-1342-b41a-3db2-a6d66a7dfb97.png)

API名を入力して、「APIの作成」ボタンをクリックします。
すると、メソッドが空の状態のリソースが表示されます。

![api_gateway_root.png](https://qiita-image-store.s3.amazonaws.com/0/2567/9fc971ad-352c-ec00-bd26-d50907c70564.png)

アクションから「リソースの作成」をクリックして、新しい子リソースの「リソース名」に任意の名前を入力します。※ここでは例として「start」を入力しています。

![api_gateway_resource_create.png](https://qiita-image-store.s3.amazonaws.com/0/2567/7e7776bf-8656-d585-a128-c3eedba7ec37.png)

次にアクションから「メソッドの作成」をクリックして、プルダウンから「POST」を選択してチェックマーク（レ点）をクリックします。

![api_gateway_method_create.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d22b71c6-cebc-dad6-5ac3-aa72f4425268.png)

次にリージョンを選択します。（東京リージョンはap-northeast-1です）

![api_gateway_region.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d721135d-c618-566d-02e8-fadbfd02c1dc.png)

そして、表示されるプルダウンから、作成済みのLambda関数名を入力して「保存」ボタンをクリックします。※Lambda関数名は途中まで入力すると補完されます。

![api_gateway_function_select.png](https://qiita-image-store.s3.amazonaws.com/0/2567/eb094865-7f89-d821-726a-f338732e6eb5.png)

権限を追加するか確認のダイアログが表示されるので、「OK」をクリックします。

![api_gateway_function_kakunin.png](https://qiita-image-store.s3.amazonaws.com/0/2567/2e2d8bab-c61d-3049-60ad-dc68b511b708.png)

すると作成API Gatewayのメソッドの実行画面が表示されるので、「統合リクエスト」のリンクをクリックします。

![api_gateway_function_setting.png](https://qiita-image-store.s3.amazonaws.com/0/2567/ebd9c3fc-0c8b-a38b-87c1-08c9e6c067d1.png)

統合リクエストの設定画面が表示されるので、「本文マッピングテンプレート」をクリックして設定画面を表示させます。

![api_gateway_request_setting.png](https://qiita-image-store.s3.amazonaws.com/0/2567/598da7d2-c039-3615-e988-1a2bd7694b7d.png)

「＋マッピングテンプレートの追加」リンクをクリックして、「application/json」と記入例が書かれたテキストボックスに「application/x-www-form-urlencoded」と入力してから、チェックマーク（レ点）をクリックします。

![api_gateway_maping_template.png](https://qiita-image-store.s3.amazonaws.com/0/2567/c861578e-d241-6ed5-b9fd-b4def98602bf.png)

「パススルー動作の変更」確認画面が表示されるので、「はい」をクリックします。

![api_gateway_maping_template_confirm.png](https://qiita-image-store.s3.amazonaws.com/0/2567/7280d5df-52f6-73ef-f445-7c09108b1f38.png)

コードエディタが表示されるので、以下のコードを記入して「保存」をクリックします。

![api_gateway_maping_codeeditor.png](https://qiita-image-store.s3.amazonaws.com/0/2567/944eb205-fafc-d1fa-d4fc-bfc3ef74de56.png)

## form-urlencoded形式をjson形式に変換するコード

このコードでは、「application/x-www-form-urlencoded」の時は json 型に変換するマッピングを定義しています。

```js:mapping_template.txt
## convert HTML POST data or HTTP GET query string to JSON

## get the raw post data from the AWS built-in variable and give it a nicer name
#if ($context.httpMethod == "POST")
 #set($rawAPIData = $input.path('$'))
#elseif ($context.httpMethod == "GET")
 #set($rawAPIData = $input.params().querystring)
 #set($rawAPIData = $rawAPIData.toString())
 #set($rawAPIDataLength = $rawAPIData.length() - 1)
 #set($rawAPIData = $rawAPIData.substring(1, $rawAPIDataLength))
 #set($rawAPIData = $rawAPIData.replace(", ", "&"))
#else
 #set($rawAPIData = "")
#end

## first we get the number of "&" in the string, this tells us if there is more than one key value pair
#set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())

## if there are no "&" at all then we have only one key value pair.
## we append an ampersand to the string so that we can tokenise it the same way as multiple kv pairs.
## the "empty" kv pair to the right of the ampersand will be ignored anyway.
#if ($countAmpersands == 0)
 #set($rawPostData = $rawAPIData + "&")
#end

## now we tokenise using the ampersand(s)
#set($tokenisedAmpersand = $rawAPIData.split("&"))

## we set up a variable to hold the valid key value pairs
#set($tokenisedEquals = [])

## now we set up a loop to find the valid key value pairs, which must contain only one "="
#foreach( $kvPair in $tokenisedAmpersand )
 #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
 #if ($countEquals == 1)
  #set($kvTokenised = $kvPair.split("="))
  #if ($kvTokenised[0].length() > 0)
   ## we found a valid key value pair. add it to the list.
   #set($devNull = $tokenisedEquals.add($kvPair))
  #end
 #end
#end

## next we set up our loop inside the output structure "{" and "}"
{
#foreach( $kvPair in $tokenisedEquals )
  ## finally we output the JSON for this pair and append a comma if this isn't the last pair
  #set($kvTokenised = $kvPair.split("="))
 "$util.urlDecode($kvTokenised[0])" : #if($kvTokenised[1].length() > 0)"$util.urlDecode($kvTokenised[1])"#{else}""#end#if( $foreach.hasNext ),#end
#end
}
```

## APIをデプロイしてURLを取得

保存したらアクションから「APIのデプロイ」をクリックします。

![api_gateway_deploy.png](https://qiita-image-store.s3.amazonaws.com/0/2567/528eca57-c8f0-205b-e70f-a6dcb328c95a.png)

APIのデプロイを行うステージを入力する画面が表示されます。

![api_gateway_deploy_stage1.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d43cb9e6-dbd3-2ea6-1ff0-08bd78cdedb6.png)

「デプロイされるステージ」で「[新しいステージ]」を選択すると入力欄が増えます。

![api_gateway_deploy_stage2.png](https://qiita-image-store.s3.amazonaws.com/0/2567/e384f687-de5e-cc10-3e4c-bed724381543.png)

ここでは仮に「beta」と入力しました。※説明欄は任意入力です。

![api_gateway_deploy_stage3.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d8c1479e-b01a-0ef3-8fc6-f00ed9623a68.png)

「デプロイ」ボタンをクリックするとAPIのデプロイが実行され、水色背景の箇所にURLが表示されます。これをslackの設定時に使用するのでメモしておきます。

![api_gateway_deploy_fin.png](https://qiita-image-store.s3.amazonaws.com/0/2567/93ca72fe-0221-7f50-04fa-4374b3e02f1d.png)

# slackの設定

slackチームのapp or integrateから遷移するAPP Browse画面の検索ボックスに「outgoing」と入力して、「Outgoing Webhooks」を選択します。

![slack_app_browse.png](https://qiita-image-store.s3.amazonaws.com/0/2567/d23c7c9c-d884-a691-3acc-92bbb0449f24.png)

表示されたOutgonig Webhooksの画面より、緑色の「Add Configuration」ボタンをクリックします。

![slack_app_outgouing.png](https://qiita-image-store.s3.amazonaws.com/0/2567/8e7f21b0-13ac-e7b7-a446-a2a948a516df.png)

次に「Add Outgoing Webhooks integration」ボタンをクリックして、詳細設定画面へ遷移します。

![slack_app_outgouing_integration.png](https://qiita-image-store.s3.amazonaws.com/0/2567/732a3731-dbe9-7513-3523-af58fd8d1f71.png)

「Outgoing Payload and Responses」の右側にある「Close」ボタンをクリックします。

![slack_app_outgouing_setting3.png](https://qiita-image-store.s3.amazonaws.com/0/2567/675a42de-00ec-2ef1-5c70-6d3b586caae7.png)

「Outgoing Payload and Responses」の次の項の「Integration Settings」で、「Trigger Word(s)」にbotに反応してもらいたい文字列と「URL(s)」にAPI Gatewayでデプロイ後にメモしたURLを入力します。

![slack_app_outgouing_integrate_url.png](https://qiita-image-store.s3.amazonaws.com/0/2567/7a47737f-29b1-7ac0-c8cc-deb0308984b1.png)

例として「Trigger Word(s)」に「lambda」、「URL(s)」にメモしたURLを入力しました。入力したら、最下段の「Save Settings」ボタンをクリックします。

![slack_app_outgouing_integrate_url2.png](https://qiita-image-store.s3.amazonaws.com/0/2567/a51ea686-1416-f677-9218-4e1393fe87e2.png)

# 試してみましょう！

![lambda_apigateway_slack.png](https://qiita-image-store.s3.amazonaws.com/0/2567/ce65cfc9-32be-eb6a-ae10-063d92c8d4f3.png)

できましたね！！