Amazon SNS の Application に紐付いているエンドポイントには Enabled
という Attribute
がついています。文字どおり、有効なエンドポイントかどうかを表すものです。Amazon SNS は賢いので APNS
や GCM
からのフィードバックを受けて、プッシュトークンが無効になった場合に自動的に false にしてくれます。しかし、基本的には Enabled
が false
になったエンドポイントは不要です。したがって、これらを自動的に削除するような仕組みがあったほうがよさそうです。そこで、この記事では AWS Lambda を用いて、それを実現する方法について説明します。
コード自体は非常に簡単で以下のような形です。AWS Lambda Function を素早く立ち上げるとしたら本来は python
か node.js
のほうが良いかと思いますが、今回の作りたいものに関してはそれほど即時性を求められるようなものではないと思うので、好きな言語(もちろん Lambda でうごくもの)で問題ないと思います。
import com.amazonaws.auth.{AWSCredentials, BasicAWSCredentials} import com.amazonaws.regions.{Region, Regions} import com.amazonaws.services.lambda.runtime.events.SNSEvent import com.amazonaws.services.lambda.runtime.{Context, RequestHandler} import com.amazonaws.services.sns.AmazonSNSClient import com.amazonaws.services.sns.model.{DeleteEndpointRequest, GetEndpointAttributesRequest} import com.typesafe.config.ConfigFactory import com.typesafe.scalalogging.slf4j.StrictLogging import org.json4s._ import org.json4s.native.JsonMethods._ import scala.collection.JavaConversions._ object DeleteEndpointAction extends RequestHandler[SNSEvent, Unit] with StrictLogging { private type EndpointArn = String private val conf = ConfigFactory.load() private val credential: AWSCredentials = new BasicAWSCredentials(conf.getString("amazon_sns.access_key"), conf.getString("amazon_sns.access_secret")) private val snsClient: AmazonSNSClient = new AmazonSNSClient(credential).withRegion(Region.getRegion(Regions.fromName(conf.getString("amazon_sns.region")))) override def handleRequest(input: SNSEvent, context: Context) = { val records = input.getRecords.toList records.foreach { record => val message = record.getSNS.getMessage if (message != null) { getEndpointArn(message).foreach { endpointArn => logger.info(s"check if enabled: endpoint_arn = $endpointArn") val enabled = getEnabled(endpointArn).getOrElse(true) if (!enabled) { logger.info(s"delete disabled endpoint: endpoint_arn = $endpointArn") deleteEndpoint(endpointArn) } } } else { logger.error(s"receive empty message: message_id = ${record.getSNS.getMessageId}") } } } private def getEndpointArn(message: String): Option[EndpointArn] = for { JObject(child) <- parse(message) JField("EndpointArn", JString(resource)) <- child } yield resource.headOption private def getEnabled(endpointArn: EndpointArn): Option[Boolean] = { val request = new GetEndpointAttributesRequest().withEndpointArn(endpointArn) try { val result = snsClient.getEndpointAttributes(request) result.getAttributes.toMap.get("Enabled").map(_ == "true") } catch { case e: Throwable => logger.error("failed to get endpoint attributes", e) None } } private def deleteEndpoint(endpointArn: EndpointArn) = { val request = new DeleteEndpointRequest().withEndpointArn(endpointArn) try { snsClient.deleteEndpoint(request) } catch { case e: Throwable => logger.error("failed to delete endpoint", e) } } }
json4s
とか typesafe config
とか scala logging
とか使ってます。よい塩梅に jar
に固めて、 Lambda Function を作って、アップロードしてください。これで AWS Lambda 側の準備は完了です。
Amazon SNS 側の作業は次の 2 点になります。
それぞれについて見ていきましょう。
トピックの作成は通常どおり普通にやってあげれば大丈夫です。とりあえず適当に「endpoint-updated-topic」とでも名前をつけておきましょう。続いて作成したトピックを先ほど作成した Lambda Function に購読させましょう。以下のような形で Protocol で AWS Lambda を選択すれば Endpoint のプルダウンリストに先ほど作成した Lambda Function の ARN がでてくるでしょう。
最後に Amazon SNS の Application に紐づくエンドポイントの Attributes
が変化したイベントをトピックに通知させるように設定します。 Application 一覧の画面で設定対象のアイテムを選択し Actions から configure events を選択してください。
そして、次に表示されるダイアログの Endpoint Updated
の項目に、先ほどつくったトピックの ARN を入力すれば、すべての作業は完了です。
設定が終わったら適当に Application へ Endpoint を追加してみて、Edit Endpoint Attributes から Enable を false にしてみてください。きっと自動的にエンドポイントが削除されるはずです。
Application エンドポイントの Attribute 変更以外にも、作成イベント、削除イベントなどをフックすることができるので、やり方次第では様々な連携を AWS Lambda を用いて行うことができると思います。
ウェブ界隈でエンジニアとして労働活動に励んでいる @gomi_ningen 個人のブログです