[Node.js]Amazon SNSでHTTPを使って通知を受け取る[aws-sdk-js]

2013.02.22

Amazon SNSで通知を受け取ろう

以前の記事で、Amazon SNSについて少し解説しました。
Amazon SNSとは、安価でクラウドからいろいろなプロトコルを用いて通知を行うことができるサービスです。
使用できる形式はHTTPやEmailがあり、プッシュを用いて通知を行うことができる優れ物です。
今回はnode.js + aws-sdk-jsで、Amazon SNSをHTTP形式で通知させる方法を紹介します。

環境構築方法

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.7.4
  • Node.js : v0.8.15
  • npm : 1.1.66

適当なディレクトリを作成し、そこでnpmを使用してaws-sdk-js、url、asyncモジュールをインストールします。

% mkdir aws-sns url async
% cd aws-sns
% npm install aws-sdk

HTTPで通知を受け取るための準備

1.Topicを登録

まずはTopicを登録します。名前はなんでもいいので、AWSコンソールからTopicを登録してARNを覚えておきましょう。

2.外部からアクセスできるサーバを用意

Amazon SNSの通知をHTTPで受けたい場合、通知先URLが必要になります。
subscribe時にSNSに指定したURLへ通知データがPOSTされるので、そのPOSTを受け取るためのHTTPサーバ用のサーバを用意しておきましょう。
今回はEC2でnodeが動作する適当なサーバを用意しました。
public DNSを仮に「ec2-aaa-bbb-ccc-ddd.ap-northeast-1.compute.amazonaws.com」とします。

3.AWS設定ファイル作成

configディレクトリを作成し、その中にaws.jsonファイルを作成します。
jsonファイル内には下記内容でAWSアカウント情報を記述しましょう。

{
    "accessKeyId":"アクセスキー",
    "secretAccessKey":"シークレットアクセスキー",
    "region":"ap-northeast-1"
}

4.node.js用プログラムを作成

先ほどのサーバ上で起動し、SNSの通知を受け取るnode.jsプログラムを作成しましょう。
まずは必要なモジュールをすべてrequireし、設定ファイルに記述したaws情報を使ってSNS用オブジェクトを作成します。
その後、httpサーバを作成しています。

var http = require('http');
var url = require("url");
var async = require('async');
var AWS = require('aws-sdk');

AWS.config.loadFromPath('./config/aws.json');
var sns = new AWS.SNS();

var httpServer = http.createServer(handler);

そしてHTTPサーバの処理内容です。「http://ec2-aaa-bbb-ccc-ddd.ap-northeast-1.compute.amazonaws.com/httpsns」
というリクエストが来た場合、SNSからの通知として処理するようにしましょう。

function handler(req, res) {
    var path = url.parse(req.url).pathname;

    if (path === "/httpsns") {
        var body = '';
        req.on('data', function (data) {
            body += data;
        });
        req.on('end', function () {
            res.writeHead(200, {
                'Content-Type':'text/html'
            });
	    //SNSからPOSTされたデータをパース
            var obj = JSON.parse(body);
            
            if (obj.Type === "SubscriptionConfirmation") {
                sns.client.confirmSubscription({ TopicArn:obj.TopicArn, Token:obj.Token}, function (err, data) {
                    console.log("confirmSubscription");
                });
            } else if (obj.Type === "Notification" && obj.Message !== undefined) {
                console.log(obj.Subject + ":" + obj.Message);
            }
            res.end('OK');
        });
    } else {
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end('server connected');
    }
}

SNSからPOSTされるデータは2種類あり、Typeフィールドによって見分けています。
1つは「SubscriptionConfirmation」タイプの通知で、これはsubscribeをリクエストしたときに、確認のために通知されるデータです。
これをうけとったらconfirmSubscription関数を使用し、ARN情報とトークン情報を渡してAmazon SNSに確認OKを伝えています。
もう1つのデータタイプは「Notification」で、これはSNSに対してpublishされた通知です。

処理の順番としては、
1.nodeサーバがTopicに対してsubscribeをリクエスト
2.Amazon SNSがnodeサーバ(/httpsns)に対してsubscribe確認
3.nodeサーバがsubscribe確認OKを伝える
4.nodeサーバへの配信開始
となります。

次は、subscribe依頼する関数を作成します。TopicARNはTopic登録時に発行されたARN、
ENDPOINTはSNS通知を受け取る自身のアドレス+ポート番号+パスです。環境によって変更してください。

function initSubscriber(callback) {
    var args = {
        TopicArn:"arn:aws:sns:ap-northeast-1:xxxxxxxxxxxxx:test",
        Protocol:'http',
        Endpoint:"http://ec2-aaa-bbb-ccc-ddd.ap-northeast-1.compute.amazonaws.com:3000/httpsns"
    };

    sns.client.subscribe(args, function (err, data) {
        console.log("subscribe start.");
        callback(null, 3);
    });
}

最後に、HTTPサーバの起動やsubscribeを適切な順序でasyncを用いて実行しましょう。

async.series([
    //HTTPサーバ起動
    function (callback) {
        httpServer.listen(3000);
        callback(null, 1);
    },
    //subscrive開始
    function (callback) {
        initSubscriber(callback);
        callback(null, 2);
    }
], function (err, results) {
    if (err) {
        throw err;
    }
});

5.プログラム全体

このプログラムの全体を載せておきます。

//app.js
var http = require('http');
var url = require("url");
var async = require('async');
var AWS = require('aws-sdk');

AWS.config.loadFromPath('./config/aws.json');
var sns = new AWS.SNS();

var httpServer = http.createServer(handler);

//asyncを使って順番に処理を実行
async.series([
    //HTTPサーバ起動
    function (callback) {
        httpServer.listen(3000);
        callback(null, 1);
    },
    //subscrive開始
    function (callback) {
        initSubscriber(callback);
        callback(null, 2);
    }
], function (err, results) {
    if (err) {
        throw err;
    }
});

function handler(req, res) {
    var path = url.parse(req.url).pathname;

    if (path === "/httpsns") {
        var body = '';
        req.on('data', function (data) {
            body += data;
        });
        req.on('end', function () {
            res.writeHead(200, {
                'Content-Type':'text/html'
            });

            var obj = JSON.parse(body);
            if (obj.Type === "SubscriptionConfirmation") {
                sns.client.confirmSubscription({ TopicArn:obj.TopicArn, Token:obj.Token}, function (err, data) {
                    console.log("confirmSubscription");
                });
            } else if (obj.Type === "Notification" && obj.Message !== undefined) {
                console.log(obj.Subject + ":" + obj.Message);
            }
            res.end('OK');
        });
    } else {
        res.writeHead(200, {'Content-Type':'text/html'});
        res.end('server connected');
    }
}

function initSubscriber(callback) {
    var args = {
        TopicArn:"arn:aws:sns:ap-northeast-1:aaaaaaaaaaaaaaa:test",
        Protocol:'http',
        Endpoint:"http://ec2-aaa-bbb-ccc-ddd.ap-northeast-1.compute.amazonaws.com:3000/httpsns"
    };

    sns.client.subscribe(args, function (err, data) {
        console.log("subscribe start.");
        callback(null, 3);
    });
}

6.アプリケーションをアップロードしたり起動する

用意したnodeサーバに先ほど作成したプログラムをアップロードし、起動します。
EC2ならscpでアップロードし、sshでログインして起動することになるかと思います。

% cd aws-sns
% node app.js

publishされた配信データを受け取ってみる

nodeサーバで先ほどアップロードしたアプリが起動したのを確認したら、AWSコンソールでテスト用に作成したTopicに対して、
新しいsubscriberが登録されているのを確認できるはずです。
そこでPublish to Topicボタンを押して、テスト用Topicに対して適当なデータをpublishしてみてください。
publishすると、nodeサーバが通知を受け取り、コンソールではそのデータが表示されるはずです。

まとめ

今回はAmazon SNSでHTTPプロトコルを使って配信を受け取る方法について紹介しました。
HTTPを使えばスケール可能なpub/subシステムを簡単に構築できますね。

参考サイトなど