Darn – that didn’t work. Feel free to give it another go. 解決方法 API Gateway+Lambda

この記事はSlackbotが自分だけに何か言ってくるというSlackのボタンを押すと自分だけにSlackbotが囁いてくると言う鬼のように邪魔で悪魔のような現象を解決する方法です。
しかもスマホのSlackアプリだと無限増殖するんですよね…
Darn – that didn’t work. Feel free to give it another go. っていう通知が

この現象が発生していた原因はSlackのボタンを押した後(1)にAPI GatewayはAWS側の処理(2,3)全て終わってからHTTPステータス200 OKをSlackへ返していた(4)ことでTimeoutになっていたためです。

ちなみにSlackはボタンを押してから3000ms以内にHTTPの200OKを返さないとTimeoutになります。

そこで以下のように変えました。

  1. Slackのボタンを押す
  2. API GatewayはSlackにHTTPの200OKを返しつつeventを送りながらLambda関数を実行
  3. Lambda関数がボタンを押した結果を何らかの形でSlackへ送信(chat.postMessageなど)

用意するもの

以下の4項目を準備する必要がありますが、この記事を読んでいる人にSlack AppとLambda関数の説明が必要な人はいないと思うので割愛します。

  • Slack App
  • IAMロール2個
  • API Gateway
  • Lambda関数

IAMでロールを2個作ります

  • Lambda用

以下のポリシーがアタッチされたものを作ります。
他のポリシーは必要に応じてアタッチしてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "*"
        }
    ]
}          

信頼関係の設定は次の通りにしてください。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "lambda.amazonaws.com",
          "apigateway.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}  
  • API Gateway用

API Gateway用のポリシーは以下のものをアタッチしてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "lambda:InvokeFunction",
            "Resource": "*"
        }
    ]
} 

API Gateway

途中まで普段通り作成していきます。

POSTメソッドを作成する時以下の赤枠のところを入力していきます。

パス上書きには  /2015-03-31/functions/arn:aws:lambda:region:account-id:function:Lambda関数名/invocations を入力して、(arn以下は実行したいLambda関数のarnが入り、末尾に/invocationsがつきます)

実行ロールには先ほど作成したAPI Gateway用ロールのarnを入力します。

HTTPヘッダーに X-Amz-Invocation-Type と ‘Event’ を入力します。
マッピングテンプレートには次の画像のように設定します。 (Content-Type:application/x-www-form-urlencoded)

#set($rawAPIData = $input.path('$'))
## escape any quotes
#set($rawAPIData = $rawAPIData.replace('"', '\"'))

## 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

{
    "headers" : {
#foreach( $key in $input.params().header.keySet() )
       "$key" : "$input.params().header.get($key)"#if( $foreach.hasNext ),#end
#end
    },
    "querystring" : {
#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("="))
  #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
    #set($kvValue = $kvTokenised[1])
  #else
    #set($kvValue = "")
  #end
  #if( $foreach.hasNext )
    #set($itemDelimiter = ",")
  #else
    #set($itemDelimiter = "")
  #end
 "$kvTokenised[0]" : "$kvValue"$itemDelimiter
#end
    },
    "stage" : "$context.stage",
    "sourceIp" : "$context.identity.sourceIp",
    "userAgent" : "$context.identity.userAgent"
}

おわりに

これでSlackbotの悪夢を見なくてすみます!

p.s. この処理は非同期処理と言うそうです。