まくろぐ

AWS SNS トピックから通知されるイベントデータの例

更新:
作成:

AWS SNS トピックからメッセージが発行されたときに、サブスクライバー(Lambda 関数など)にどのようなイベントデータが配信されるかの例です。

基本的なメッセージの構造

Lambda 関数で受け取る場合

SNS トピックに対して Lambda 関数をサブスクライブしておくと、Lambda 関数の第一引数 (event) で、次のようなオブジェクトを受信できます。 重要な情報は、Records[0].Sns の下に格納されています。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:mytopic:0884-5d81c0db-4e13-829f-596f7ea9f8ad",
      "Sns": {
        "Type": "Notification",
        ...
        "Subject": "Message Title",
        "Message": "Message Body",
        ...
      }
    }
  ]
}

メールで受け取る場合

SNS トピックに「JSON 形式のメール」をサブスクライブしておくと、SNS のメッセージが発行されたときに、次のような内容のメールが届きます。

{
  "Type": "Notification",
  ...
  "Subject": "Message Title",
  "Message": "Message Body",
  ...
}

Lambda 関数の第一引数で渡される event オブジェクトで表現すると、event.Records[0].Sns に相当する部分の情報がメールで送られてきます。

具体的な SNS メッセージの例

下記の例では、Records[0].Sns 以下の情報のみを示しています(JSON 形式のメールで送られる内容です)。

SNS のマネージメントコンソールからメッセージ発行したとき

件名に Message Title、本文に Message Body を設定してメッセージ発行したときに送られるイベントデータの例です。

{
  "Type" : "Notification",
  "MessageId" : "789b67e7-9bd8-5a6e-99d0-3f10e520edf7",
  "TopicArn" : "arn:aws:sns:ap-northeast-1:123456789012:mytopic",
  "Subject" : "Message Title",
  "Message" : "Message Body",
  "Timestamp" : "2021-04-19T12:51:04.019Z",
  "SignatureVersion" : "1",
  "Signature" : "bKXAFppLnPMeajvylkQvjiH/9RNeAlNfxbBC7HJbiOt7SKrw9oVk+CI1ZYsADcNt9aAtAOysdHrFD97J/N4n5o+tV+Dz5hsjxqKskYOCrYDRTTqLyhwa5OBtjAhU74IZy9ByfBSQWfOD1I5AFp0FLUWR8ieop/ZTV5buf4FodNm4scwW18nUJ1D5iTPNy3NinWq0wVP2FT7Ykt9HCdNleaFamMZ+war4OHsRhOgvDqOV4auZ2yvayMf70eJPHLbuQn09E0IlQYsvUArTOSknbFK3lsbeLfJBHmIa2qnuZm+VTknNgJxkCJNyer+7EKTrOABYqXsz55ENYJsn5xvjCA==",
  "SigningCertURL" : "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-94bdb98bd93083010a507c1833636cda.pem",
  "UnsubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:mytopic:97989c34-955f-49c8-856c-1b8e3e926a6f"
}

S3 の PutObject イベントを SNS から通知したとき

S3 バケットに対して sample.txt をアップロードしたときの SNS 通知の例です。 Message プロパティの中に JSON 形式の情報が格納されていて、この中身を見ると、アップロードされたファイルの名前(S3 オブジェクトのキー)などが分かります。

{
  "Type" : "Notification",
  "MessageId" : "da5edc2e-1ccf-51d9-8601-2ada4b82a998",
  "TopicArn" : "arn:aws:sns:ap-northeast-1:123456789012:mytopic",
  "Subject" : "Amazon S3 Notification",
  "Message" : "{\"Records\":[{\"eventVersion\":\"2.1\",\"eventSource\":\"aws:s3\",\"awsRegion\":\"ap-northeast-1\",\"eventTime\":\"2021-04-19T13:16:16.834Z\",\"eventName\":\"ObjectCreated:Put\",\"userIdentity\":{\"principalId\":\"AWS:AIDAQXINMCQAMKLZ6V3HG\"},\"requestParameters\":{\"sourceIPAddress\":\"110.76.125.97\"},\"responseElements\":{\"x-amz-request-id\":\"JWTTJDZ40Y077BRS\",\"x-amz-id-2\":\"TNX1leb/NXvnS3fN/slo373HsAi6Wy0OUXK0LLlxLTfWHr+HhyxMckvUvkN91A7BDx2/UEFM0EFiCRJw0mGxlZBAy68j43vt\"},\"s3\":{\"s3SchemaVersion\":\"1.0\",\"configurationId\":\"497e6001-fbc4-40df-939e-c09289477696\",\"bucket\":{\"name\":\"myapp-sample-bucket-123456789012\",\"ownerIdentity\":{\"principalId\":\"FJF0VI7A2H51MM\"},\"arn\":\"arn:aws:s3:::myapp-sample-bucket-123456789012\"},\"object\":{\"key\":\"sample.txt\",\"size\":5,\"eTag\":\"cda10b5681c9e9ff6fe9e95990dcd04a\",\"sequencer\":\"48084D47F00607D82A\"}}}]}",
  "Timestamp" : "2021-04-19T13:16:21.315Z",
  "SignatureVersion" : "1",
  "Signature" : "EDu9KfAsGc4ajlurH+R+fALKFBGLbQEvm+DPMfI6GWyWk0e1tfg3VxpsSwRaE7+QTKeqjCwCXXLhsALo3eIZbFh7xL4mTX+4tDyPtw3zV4OpSlEfWJr0F3g9lP1ZbO0DbSqKqrLnthw3CPsG3yNDGgyEVTPR8BGIL0tTsmpNDDcHfOsEIwoxUCpR4HZfImLWKua4lZd8U1Yib/8r9p033T9kp5VoQlaK0lMRJI0WuI3Rbn7YC2Qvh2G3Lp8mszoqkUjmftmk7wCfgguB88dCLn5FU7ylHNanOao0do1BbV12rTcBMqZH3vbcezaqa9KVRcetDEHB5BJHQRGeG6t0og==",
  "SigningCertURL" : "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-94bdb98bd93083a010a507c1833636cd.pem",
  "UnsubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:mytopic:97989c34-955f-49c8-856c-1b8e3e926a6f"
}

関連記事

Lambda 実装例: S3 へのアップロードを SNS で通知して Lambda から読み込む

更新:
作成:

何をするか?

/p/f2fq2cn/img-s3-sns-lambda.svg

ここでは Lambda 関数の実装例として、SNS トピックから S3 バケットの PutObject イベント通知を受けて、アップロードされたファイルを読み込む例を示します。 S3 バケット、および SNS トピックの作成と、S3 → SNS の通知設定は完了していると想定します。

Lambda 関数の実装

AWS SDK のインストール

ここでは、Node.js 用の AWS SDK ver.2 を使っているので、先にインストールしておく必要があります。 AWS 側の Lambda 実行環境には標準でインストールされているので、--save-dev(開発用)でインストールしておけば OK です。 ついでに TypeScript 用の型定義もインストールしておくと、Lambda ハンドラのパラメータを any 型ではなく、SNSEvent 型などで参照できて便利です。

$ npm install aws-sdk --save-dev
$ npm install @types/aws-lambda --save-dev

Lambda ハンドラの実装

先に、S3 バケット内のオブジェクトの内容を取得するユーティリティ関数を用意しておきます(AWS SDK ver.2 を使用しています)。 エクスポートされた getS3Object 関数を使う想定ですが、このコードを直接実行したときには末尾のテスト関数を実行するようにしてあります。

src/s3util.ts
import { S3 } from 'aws-sdk';
const s3 = new S3();

/** S3 バケット内のオブジェクト(テキスト)を読み込んでその内容を返します。 */
export async function getS3Object(
    bucketName: string,
    objectKey: string
): Promise<string | undefined> {
    const output: S3.GetObjectOutput = await s3.getObject({
        Bucket: bucketName,
        Key: objectKey,
    }).promise();
    return output.Body?.toString();
}

// テスト用 main 関数
if (require.main === module) (async () => {
    const bucket = 'bucket-123456789012-sample';
    const key = 'sample.txt';
    const body = await getS3Object(bucket, key);
    console.log(body);
})();

次の Lambda 関数実装では、第一引数で受け取った SNSEvent オブジェクトから S3 バケット名とオブジェクト名を取り出して、そのオブジェクトの内容を S3 API(上記で定義したユーティリティ関数)を使って取得しています。

src/index.ts
import { SNSEvent, Context, Callback } from 'aws-lambda';
import * as s3util from './s3util';

/** SNS トピックの通知から抽出する S3 メタ情報 */
type BucketMeta = {
    /** S3 のイベント名 (例: `ObjectCreated:Put`) */
    eventName: string,
    /** S3 バケット名 */
    bucketName: string,
    /** S3 バケット内のオブジェクトキー */
    objectKey: string,
};

/** Lambda 関数に渡されるイベントデータから S3 のメタ情報を取得します。 */
function parseSnsEvent(snsEvent: SNSEvent): BucketMeta {
    const msg = JSON.parse(snsEvent.Records[0].Sns.Message) as any;
    return {
        eventName: msg.Records[0].eventName,
        bucketName: msg.Records[0].s3.bucket.name,
        objectKey: msg.Records[0].s3.object.key,
    } as BucketMeta;
}

/** Lambda 関数のエントリポイントです。 */
export async function handler(event: SNSEvent, ctx: Context, callback: Callback) {
    try {
        const meta: BucketMeta = parseSnsEvent(event);
        const body = await s3util.getS3Object(meta.bucketName, meta.objectKey)
        console.log(meta);
        console.log(body);
        callback(null, body);
    } catch (e) {
        console.error(e);
        callback(e);
    }
}

解説

Lambda 関数の第一引数 (event) で渡されるオブジェクト (このケースでは SNSEvent 型) の Records[0].Sns.Message プロパティを参照すると、次のような JSON テキストを取得できます(オブジェクトではなく JSON 形式のテキストです)。

{
  "Records": [
    {
      "eventVersion": "2.1",
      "eventSource": "aws:s3"
      "awsRegion": "ap-northeast-1",
      "eventTime": "2021-04-19T15:30:59.084Z",
      "eventName": "ObjectCreated:Put",
      "userIdentity": {
        "principalId": "AWS:AIDMAQAKLZ6V3HXINMCQG"
      },
      "requestParameters": {
        "sourceIPAddress": "110.76.125.97"
      },
      "responseElements": {
        "x-amz-request-id": "PR9N0NY46J4B4YM9",
        "x-amz-id-2": "IyRPxwo5CHEv4eB8Vd2JjL6kkxdJ0fm0S3qfkB1oKh3gBudpIomjSBvN77749yBgqxKtws7l+FbUhjgMATBf/1VnblothZ6v7DXmjCj5s8Y="
      },
      "s3": {
        "s3SchemaVersion": "1.0",
        "configurationId": "497e6001-fbc4-40df-939e-c09289477696",
        "bucket": {
          "name": "bucket-123456789012-sample",
          "ownerIdentity": {
            "principalId": "A2H51MMFJF0VI7"
          },
          "arn": "arn:aws:s3:::bucket-123456789012-sample"
        },
        "object": {
          "key": "sample.txt",
          "size": 5,
          "eTag": "dcd0cda10b5681c4a9e9ff6fe9e95990",
          "sequencer": "5500C45300607DA237"
        }
      }
    }
  ]
}

この JSON テキストをオブジェクト化すれば、次のような情報を参照できます。

  • Records[0].eventName → S3 のイベント名 (ObjectCreated:Put)
  • Records[0].s3.bucket.name → S3 バケット名 (bucket-123456789012-sample)
  • Records[0].s3.object.key → S3 バケットに格納されたオブジェクト名 (sample.txt)

あとは、この情報を使って、S3 バケットに格納されたオブジェクト(ファイル)の内容を取得すれば OK です。 必要な情報は Lambda 関数の引数で受け取れるので、環境変数などで S3 バケットの情報を渡さずに済みます。

デプロイ用の情報

CloudFormation テンプレート

ここでは CloudFormation (SAM) テンプレートを使って、Lambda 関数のリソースを生成します。 Lambda 関数リソースを生成するときに、S3 バケットへのアクセス権限と、SNS トピックへのサブスクリプション設定を行う必要があるので、これらの情報を入力パラメーター (Parameters) として定義しています。

  • S3 バケット名: bucket-123456789012-sample
  • SNS トピックの ARN: arn:aws:sns:ap-northeast-1:123456789012:mytopic
template.yaml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Parameters:
  TopicArn:
    Type: String
    Default: arn:aws:sns:ap-northeast-1:123456789012:mytopic
  BucketName:
    Type: String
    Default: bucket-123456789012-sample

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: nodejs14.x
      Handler: build/index.handler
      CodeUri: function.zip

      # S3 にアクセスするための権限(ポリシー)をロールに追加
      Policies:
        - S3CrudPolicy:
            BucketName: !Ref BucketName

      # この Lambda 関数を SNS トピックにサブスクライブする
      Events:
        MySnsEvent:
          Type: SNS
          Properties:
            Topic: !Ref TopicArn

このテンプレートを使って実際に CloudFormation スタックを生成する方法をここで説明すると長くなってしまうので、そのあたりは下記の記事を参照してください。

package.json

参考までに package.json の一例を載せておきます。

{
  "name": "function",
  "version": "0.0.1",
  "scripts": {
    "start": "node build/index.js",
    "build": "tsc",
    "build:watch": "tsc --watch",
    "zip": "npm-pack-zip",
    "cf:package": "aws cloudformation package --template-file template.yml --output-template-file template.packaged.yml --s3-bucket bucket-123456789012-functions",
    "cf:deploy": "aws cloudformation deploy --stack-name mystack --template-file template.packaged.yml --capabilities CAPABILITY_IAM"
  },
  "files": [
    "build"
  ],
  "bundledDependencies": [],
  "devDependencies": {
    "@types/aws-lambda": "^8.10.75",
    "@types/node": "^14.14.35",
    "npm-pack-zip": "^1.2.9",
    "typescript": "^4.2.3"
  },
  "dependencies": {
    "aws-sdk": "^2.884.0"
  }
}
使用例
$ npm run install     # 依存パッケージのインストール
$ npm run build       # ビルド(トランスパイル)
$ npm run zip         # ビルド結果を ZIP 化
$ npm run cf:package  # ZIP をアップロード
$ npm run cf:deploy   # デプロイ(CloudFormation スタックを生成)

TypeScript で実装した Lambda 関数を ZIP 化する方法に関しては、以下の記事を参考にしてください。

関連記事

AWS CloudFormation の設定例: SNS トピックを Lambda 関数からサブスクライブする

更新:
作成:

何をするか?

ここでは、CloudFormation (SAM) のテンプレートを使って、SNS トピックをサブスクライブする Lambda 関数を定義してみます。

/p/5q4epyb/img-sns-to-lambda.svg

サブスクライブ対象とする SNS トピック自体は、あらかじめ何らかの方法で作成済みであり、次のような ARN を取得できているものとします。

SNS トピックの ARN
arn:aws:sns:ap-northeast-1:123456789012:mytopic

上記のような CloudFormation スタックが完成すると、マネージメントコンソールや CLI で SNS トピックのメッセージを発行して、Lambda 関数にイベントが届くことを確認できます。

テンプレートの記述例

次の SAM テンプレートでは、Lambda 関数を定義しつつ、そのイベントソースとして SNS トピックを設定しています。 イベントソースの指定は、実際には、SNS トピックに Lambda 関数をサブスクライブすることを意味しています。 SNS トピックの ARN は、入力パラメータ TopicArn のデフォルト値として指定しています。

template.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31

Parameters:
  TopicArn:
    Type: String
    Default: arn:aws:sns:ap-northeast-1:123456789012:mytopic

Resources:
  MyFunction:
    Type: AWS::Serverless::Function
    Properties:
      Runtime: python3.7
      Handler: index.handler
      InlineCode: |
        import json
        def handler(event, context):
          s = json.dumps(event, indent=2)
          print('Message received from SNS:' + s)
          return {'body': s, 'statusCode': 200}        

      # Lambda 関数を SNS トピックにサブスクライブする
      Events:
        MySnsEvent:
          Type: SNS
          Properties:
            Topic: !Ref TopicArn

Lambda 関数の実装は InlineCode としてテンプレートに埋め込んでいます。 ここでは、単純に第一引数 (event) の内容を出力しています。

デプロイ

AWS CLI で次のように実行して、CloudFormation スタックを生成します。 もちろん、CloudFormation のマネージドコンソール(Web サイト)から実行しても構いません。

$ aws cloudformation deploy --stack-name mystack \
    --template-file template.yml \
    --capabilities CAPABILITY_IAM

しばらく待つと、SNS トピックにサブスクライブされた Lambda 関数リソースが生成されます。

テスト実行

SNS トピックのマネージメントコンソールから「メッセージを発行」を実行すると、Lambda 関数は次のようなメッセージを受信します。

Message received from SNS:
{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:123456789012:mytopic:0884c0db-5d81-4e13-829f-596f7ea9f8ad",
      "Sns": {
        "Type" : "Notification",
        "MessageId" : "789b67e7-9bd8-5a6e-99d0-3f10e520edf7",
        "TopicArn" : "arn:aws:sns:ap-northeast-1:123456789012:mytopic",
        "Subject" : "Message Title",
        "Message" : "Message Body",
        "Timestamp" : "2021-04-19T12:51:04.019Z",
        "SignatureVersion" : "1",
        "Signature" : "bKXAFppLnPMeajvylkQvjiH/9RNeAlNfxbBC7HJbiOt7SKrw9oVk+CI1ZYsADcNt9aAtAOysdHrFD97J/N4n5o+tV+Dz5hsjxqKskYOCrYDRTTqLyhwa5OBtjAhU74IZy9ByfBSQWfOD1I5AFp0FLUWR8ieop/ZTV5buf4FodNm4scwW18nUJ1D5iTPNy3NinWq0wVP2FT7Ykt9HCdNleaFamMZ+war4OHsRhOgvDqOV4auZ2yvayMf70eJPHLbuQn09E0IlQYsvUArTOSknbFK3lsbeLfJBHmIa2qnuZm+VTknNgJxkCJNyer+7EKTrOABYqXsz55ENYJsn5xvjCA==",
        "SigningCertURL" : "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-94bdb98bd93083010a507c1833636cda.pem",
        "UnsubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:123456789012:mytopic:97989c34-955f-49c8-856c-1b8e3e926a6f"
      }
    }
  ]
}

ちなみに、AWS CLI を使って SNS トピックにメッセージ発行 (publish) することもできます。

$ aws sns publish \
    --subject "Message Title" \
    --message "Message Body" \
    --topic-arn arn:aws:sns:ap-northeast-1:123456789012:mytopic

関連記事

メニュー

まくろぐ
サイトマップまくへのメッセージ