[AWS]LambdaVPCでScalaからAWS SNSを使ってiOSにJSON形式で通知してみた。

# [AWS]LambdaVPCでScalaからAWS SNSを使ってiOSにJSON形式で通知してみた。
## 前提
SNSのTopicが設定済み
VPCのセキュリティグループが設定済み
**NATGatewayを設定したsubnetが2つある！** ←重要
## ソースコードの作成
この辺参考に作成してください。
[Amazon LambdaでScala](http://qiita.com/yoshiyoshifujii/items/126b1f7928f285ed4c37)
### build.sbt
今回イベント駆動にはしていないので、依存関係から
aws-lambda-java-events
は除きました。また、SNSを使うので
<pre>"com.amazonaws" % "aws-java-sdk-sns" % "1.10.54",</pre>
を追加しました。
また、SNS通知メッセージをJSON形式にしたいので
<pre>"com.typesafe.play" %% "play-json" % "2.3.10",</pre>
を追加しています。
### sample
コードはこんな感じです。（ただしサンプル用に作ったので動作未確認）
実行する関数のinputパラメータはjavaのMap型にする必要がある。
CredentialsProviderはEnvironmentVariableCredentialsProviderを指定すればいい感じに権限を取得してくれる。
<pre>
package qr.test

import com.amazonaws.auth._
import com.amazonaws.regions.{ Region, Regions }
import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.sns._
import com.amazonaws.services.sns.model._

import play.api.libs.json._

case class PublishMsg(
  default: String,
  APNS: String,
  APNS_SANDBOX: String)
case class APNSMsgBody(
  aps: APS,
  title2: String,
  options: String)
case class APS(
  `content-available`: Int,
  sound: String,
  alert: Alert)
case class Alert(
  title: String,
  body: String)

object Notify {
  def notify(input: java.util.Map[String, Object], context: Context): String = {
    val credential: AWSCredentialsProvider = getCredentials    
    val topicArn = "arn:aws:sns:ap-northeast-1:xxxxxxxxxxx:test"
    val m = APNSMsgBody(APS(1, "default", Alert("title", "body")), "title2", "options")
    val APNSStr = Json.toJson(m).toString
    val msg = Json.toJson(PublishMsg("title", APNSStr, APNSStr)).toString
    println("msg:" + msg)
    val publishRequest = new PublishRequest(topicArn, msg)
    publishRequest.setMessageStructure("json")
    getSNSClient.publish(publishRequest).getMessageId
  }
  implicit def convertMsgBody: Writes[APNSMsgBody] = Json.writes[APNSMsgBody]
  implicit def convertAlert: Writes[Alert] = Json.writes[Alert]
  implicit def convertAPS: Writes[APS] = Json.writes[APS]
  implicit def convertPublishMsg: Writes[PublishMsg] = Json.writes[PublishMsg]

  def getSNSClient = {
    val cli = new AmazonSNSClient(getCredentials)
    cli.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
    cli
  }
  def getCredentials: AWSCredentialsProvider = {
    new EnvironmentVariableCredentialsProvider
  }
}
</pre>
messageの内容はこんな感じになります。
<pre>
{
	"default": "title",
	"APNS": "{\"aps\":{\"content-available\":1,\"sound\":\"default\",\"alert\":{\"title\":\"title\",\"body\":\"body\"}},\"title2\":\"title2\",\"options\":\"options\"}",
	"APNS_SANDBOX": "{\"aps\":{\"content-available\":1,\"sound\":\"default\",\"alert\":{\"title\":\"title\",\"body\":\"body\"}},\"title2\":\"title2\",\"options\":\"options\"}"
}
</pre>
### jar作成
activator assemblyでjarファイル作成
## Lambdaの設定
「Select blueprint」「Configure triggers」は無視
### Configure Function
Nameは適宜
RuntimeにJava8を選択
Code entry typeはzip or jar fileを選択
Function packageからファイルを選択し、upload
Handlerは上記の場合であれば qr.test.Notify::notify
roleはVPCのデフォルトにSNS Publishのみ許可したものを作成して選択。
メモリは大きめにしておいた方がコンパイルが早い。1024以上はあまり恩恵がなさそうに感じた。
コンパイルに10～30秒ほどかかるのでTimeoutはそのくらいで設定
VPCを選び、NATGateway設定済みのサブネットを二つ選択する。
**NATGatewayが設定されていないと、SNSのAPIを叩けないのでtimeoutエラーとなるので注意！**
http://stackoverflow.com/questions/36167836/java-myhandler-s3-put-client-timeout-in-lambda
セキュリティグループは443か80が全オープンであれば問題ないはず…。（未確認）
### 確認
確認して問題なさそうなら作成。

テストを実行したら通知される。

## SNS Java SDK利用時のエラー

<pre>
TopicArn (Service: AmazonSNS; Status Code: 400; Error Code: InvalidParameter; Request ID: ｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘｘ) (AmazonHttpClient.java:1369)
</pre>
snsClientに対してはRegionの設定が必要なようです。
<pre>
def getSNSClient = {
    val cli = new AmazonSNSClient(getCredentials)
    cli.setRegion(Region.getRegion(Regions.AP_NORTHEAST_1))
    cli
}
</pre>