AmazonSNSとは、Amazonが提供するPUSH型通知サービスです。
多数のユーザーに対してメールやPUSH通知を送る以外に、
他のAWSサービスからの通知を受け取り、特定のエンドポイントに対してHTTPリクエストを送ったりできます。
https://aws.amazon.com/jp/sns/
今回は、他のAWSサービスからの通知をチャット上(slack等)に流すために、
HubotでAmazonSNSの通知を受け取ってみます。
なお、ここではメッセージに含まれる署名の確認を行っていません。
なりすまし攻撃を防ぐために署名の検証を行う場合は以下のヘルプをご参照ください。
http://docs.aws.amazon.com/ja_jp/sns/latest/dg/SendMessageToHttp.verify.signature.html
また、AWSへの知識が乏しいために間違った情報が含まれている可能性が大いにございます。
生暖かい目でご覧いただければと思います
完成品
さっそくですが、完成したスクリプトはこちらです。
次の項から解説していきます。
util = require 'util'
bodyParser = require 'body-parser'
request = require 'request'
module.exports = (robot) ->
robot.router.post '/webhook/:room', bodyParser.text(), (req, res) ->
room = req.params.room
type = if req.headers['x-amz-sns-message-type'] then req.headers['x-amz-sns-message-type'] else 'x-amz-sns-message-type is not found'
body = if req.body then JSON.parse req.body else {}
console.log type
if type == 'SubscriptionConfirmation'
requestSubscribe body.SubscribeURL
else if type == 'Notification'
sendMessage room, body
res.send 'OK'
requestSubscribe = (subscribeUrl) ->
options =
url: subscribeUrl
method: 'GET'
request options, subscribeCallback
subscribeCallback = (error, response, body) ->
console.log util.inspect body
sendMessage = (room, body) ->
message = body.Subject
#message += "\r\n" + body.Message
robot.messageRoom room, message
HubotでHTTPリクエストを受信
Hubotにはexpressが内蔵されており、robot.router.get
、またはrobot.router.post
を使うことで、HTTPリクエストを受け取ることができます。
AmazonSNSのリクエストはPOSTで来るので、今回はrobot.router.post
を使います。
パスの部分は任意ですが、ここでは:room
という形で、投稿するルームIDを指定できるようにしてみました。
(エンドポイントURLを/webhook/%23general
にすると、#general
に投稿されるようにします)
module.exports = (robot) ->
robot.router.post '/webhook/:room', bodyParser.text(), (req, res) ->
第二引数にはbodyParser.text()
を渡します。
AmazonSNSのリクエストは本文がJSON形式の文字列になっているのですが、Content-Typeがplain/text
で送られてきます。
Hubotのexpressはデフォルトでplain/text
で来たリクエストボディをパースできないので、ここで指定してあげる必要があります。
body-parser
モジュールを使うので、コードの上の方でrequireしておきます。
bodyParser = require 'body-parser'
リクエストタイプを判定
次の行からは、パラメータからroomIDの取得、bodyのパースの他、x-amz-sns-message-type
というヘッダーの情報を取得しています。
room = req.params.room
type = if req.headers['x-amz-sns-message-type'] then req.headers['x-amz-sns-message-type'] else 'x-amz-sns-message-type is not found'
body = if req.body then JSON.parse req.body else {}
AmazonSNSからエンドポイントに対して通知を送るには、一度エンドポイントを承認する必要があります。
承認用のリクエストも、通常の通知と同じURLに送られてきます。
リクエストヘッダーに含まれるx-amz-sns-message-type
を取得することで、
承認用のリクエストか通常の通知かを判別できます。
承認用リクエストであればSubscriptionConfirmation、通常の通知であればNotificationが入ってます。
承認用リクエストを処理
if type == 'SubscriptionConfirmation'
requestSubscribe body.SubscribeURL
requestSubscribe = (subscribeUrl) ->
options =
url: subscribeUrl
method: 'GET'
request options, subscribeCallback
subscribeCallback = (error, response, body) ->
console.log util.inspect body
承認用のリクエストだった場合、リクエストボディに含まれる承認用URLにリクエストを送信する必要があります。
承認用URLはbody.subscribeUrl
に入っています。
ここでは、request
モジュールを使ってリクエストを送信した後、受け取った内容をコンソールに出力してみました。(出力しなくても特に問題はありません)
request
モジュールもrequireしておきます。
request = require 'request'
通常の通知を処理
else if type == 'Notification'
sendMessage room, body
sendMessage = (room, body) ->
message = body.Subject
#message += "\r\n" + body.Message
robot.messageRoom room, message
通常の通知の場合は、リクエストボディに件名と本文の情報が含まれていますので、それに沿って適切に処理を行います。
body.Subject
が件名で、body.Message
が本文です。
今回は、指定したルームに対して件名の情報だけ投稿するようにしてみました。
エンドポイントの登録
これでHubot側の準備ができましたので、AmazonSNSにエンドポイントを登録してみます。
(ここはあまりうまく説明できる自信が無いのでさらっと流します。詳細はAWSのヘルプページをご覧ください……)
まず、AmazonSNSのコンソール画面へ移動し、トピックを作成します。
http://docs.aws.amazon.com/ja_jp/sns/latest/dg/CreateTopic.html
続いて、作成したトピックに対して、エンドポイントをサブスクライブします。
http://docs.aws.amazon.com/ja_jp/sns/latest/dg/SubscribeTopic.html
以下のように設定してください。
Protocol:HTTP
Endpoint:http://<your_hubot_host>:<your_hubot_port>/webhook/<roomID>
Create subscriptionをクリックすると、登録したエンドポイントURLに対して、先ほど説明したSubscriptionConfirmationのリクエストが実行されます。正しくスクリプトが動作すれば、エンドポイントが承認されるはずです。
(トピックの画面で、登録したエンドポイントにSubscription IDが付与されていれば承認成功です)
テスト送信
AmazonSNSのコンソールにおいてトピックを表示した状態で、Publish to topicをクリックすると、
メッセージの作成画面が開きます。
適当な件名と本文を入力し、Publish Messageをクリックしてみてください。
チャット上に、件名に入力した文字列が投稿されれば成功です。
他のAWSサービスと連携
あとは、他のAWSサービスの設定において、通知先に先ほど作成したトピックを指定すれば、
AmazonSNSを通じてHubotにリクエストが送信され、チャット上にメッセージが投稿されるようになります。
必要に応じて件名や本文を解析して見やすい形に整形すると良いと思います。
以上、AmazonSNSのHTTPリクエストをHubotで受け取る手順でした。