Skip to the content.
この記事にはこんなことが書かれています。
- webhookとは
- webhookを受ける口をつくってみよう(AWS活用)
- API Gateway
- Lambda
‐ Amazon SNS

■モチベーション

客先システム導入時に担当者から「ある処理の完了通知をwebhookで投げることができる」
と連絡をうけた。webhookとは何かから勉強することになったが
AWSでその受け皿をつくることができたのでその軌跡を書く

<webhook概要>
あるアプリケーションから別のアプリに対して、リアルタイムな情報提供を実現するための仕組み。
例えばインターネットであるホームページを見るときには、自身のPCは「この情報くれよ」とあるサーバーに対して問合せをして、サーバー側から「お前は誰?」とか「誰か確認できたからこの情報あげるね」とかをやり取りしている。
それに対してWebhookは、何かイベント発生をしたら、決められた情報を通知するだけの仕組みで、送信側・受信側ともに効率のいい方法です。



■webhookをもう少し詳しく

以下資料がわかりやすかったです↓

WebhookのWeb APIとの違い 〜イベントと通信に着目してみた〜
データ連携(Webhook)

まとめると、webhookは“広義のWeb API”(=以下、HTTP通信を主体としたインターフェース)
 ・http(s) GET 
 ・http(s) POST 
の一種であり、特にWebhookは、イベント駆動で情報を通知(=POST)するものということがわかりました。
つまり、情報をGETで得たりせず、イベント発生時に、相手システムにPOSTして情報を伝達するものという理解です。

ボディと呼ばれる、HTTPの中身に情報をいれて送受信しますが
JSONと呼ばれる形式で書くのが一般的なようです。
これはWebhookに依らずよく使われる形式で
非常に見やすく簡単な構造で以下のような感じです

{
  "id":"{id}",
  "value":"{value}",
  "unit":"{unit}"
}

“id”部分をキーと呼び、“{ほにゃらら}”をバリューといいます
キーは何か概念を表すもので、バリューはその値です。非常にシンプルですね。
例えば、ある温度センサーから定期的に温度の値の値知する仕組みを作って、JSONでやり取りしましょうとなったらこんな感じでしょうか。

{
  "id":"{1}",
  "area":"{nagoya}",
  "temperature":"{28.5}"
}

■webhookを実装する

AWSでwebhookを受信し自分にメール通知する仕組みを作ってみる。
まず結論から、こんな構成で作成しました。


Webhookの受信部はAPI Gatewayで作成し、エンドポイントを提供します。
Lambdaは、関数部で、API Gatewayが受け取ったデータを読み取り、ログやEmail作成のためにデータを加工します。
S3は、Lambdaで作成したデータを保存します。
Amazon SNSは、Lambdaで作成したデータを本文として、Emailに通知します。

以下サイトを参考に作成しました。非常に助かります。
Webhook参考1
Webhook参考2

本当はもっといろんなサイトを見ましたが、一番参考になったのがこの2つでした


■Lambda関数のソースサンプル

pythonの方が慣れていますが、上記参考サイトがjavascript形式でしたので見よう見まねで書いてみました。
初めてなので、色々とおかしいところあるかもしれません

'use strict';

const AWS = require('aws-sdk');
const s3 = new AWS.S3({ apiVersions: '2006-03-01' });
const bucketName = 'ここに名前を入れる';

//SNS通知部分--ここから--
var sns = new AWS.SNS({apiVersion: '2010-03-31', region: 'ap-northeast-1'});
function timer(ms, name){
    console.log('name: ${name} start!')
    return new Promise((resolve, resject) => {
        setTimeout(() => resolve(name), ms)
        })
}
//SNS通知部分--ここまで--

exports.handler = async (event, context) => {

    // ファイル名のために、現在時刻を取得する
    // LambdaのタイムゾーンはUTCのためJSTに変換する
    const now = new Date(Date.now() + ((new Date().getTimezoneOffset() + (9 * 60 * 60 * 1000))));

    // フォーマット通りに変換する
    const fileName = now.toISOString().replace(/-|:|Z/g, '').replace(/T|\./g, '_');

    const eventBody = JSON.parse(event.body);

    // フォルダ名にデバイスIDを設定する
    let dirName = getDeviceId(eventBody);

    //SNS通知部分--ここから--
    console.log('publish start')
    await timer(1000, publish(event, context));
    function publish(event, context) {
        sns.publish({
            Message: JSON.stringify(eventBody, null, 2),
            Subject: '名前をいれる ' ,
            TargetArn: 'arn:を入力する'
            }, function(err, data) {
                if (err) {
                    console.log(err.stack);
                    return "failed publish".err.stack;
                }
                console.log('publish sent');
                console.log(data);
        });
    }
    //SNS通知部分 --ここまで--
    
    try {
    // S3へデータを保存する。保存するファイルは「S3bucket/dirName/fileName.json」の形式となる。
    const data = await s3.putObject(
        createParamsToPutObject(bucketName, (dirName + '/' + fileName + '.json'), JSON.stringify(eventBody, null, 2)))
        .promise();
        // 成功したらログにSUCCESSを表示する
        console.log('SUCCESS', data);
        return createResponse(data.statusCode, data);
    } catch (err) {
    // 失敗したらログにERRORを表示する
     console.log('ERROR', err.stack);
     return createResponse(err.statusCode, err.message);
    }
    

};

// S3への保存のためのパラメータ(JSON)を作る
const createParamsToPutObject = (bucket, key, body) => {
    return {
        Bucket: bucket,
        Key: key,
        Body: body,
        ContentType: 'application/json'
    };
};

// レスポンスを作る
const createResponse = (code, body, headers) => {

    if (!headers) {
        headers = {};
    }

    const response = {
        statusCode: code,
        body: JSON.stringify(body),
        headers: headers
    };
    return response;
}

// リクエスト本文にデバイスIDが含まれていればデバイスを、そうでなければ'unknown'を返す
const getDeviceId = (jsonObject) => {
    if ('device' in jsonObject) {
        const deviceId = jsonObject.device.device_id;
        if (deviceId) {
            return deviceId;
        }
    }
    return 'unknown';
}


■テスト方法   

上記で紹介したURLを参考にコンソールのテストツールでもテストできますが
実際に他のサイトからwebhookを受けてみたいと思いました。以下を試しました。

WebブラウザでAPIを投げることができる
インストールしたりするのが大変だと思ったので調べてみると、Google Chromeの拡張機能で便利なものがありました。非常に使いやすく、1分くらいで使えちゃいます。
API Gatewayで作ったエンドポイントを入力して、適当にBodyを書いて、POSTするだけです。
操作画面は、以下の写真のような感じです。

※Bodyの内容が間違ってたり、エンドポイントを誤っているとサーバーエラーが出てしまいます

また、Webhookは色々なところで標準対応しているので、手短なところとしてGithubのWebhook通知との連携を試してみました。以下サイトを参考にしました
GithubでWebhook通知を行う

リポジトリにソースをアップしたら通知がくることを確認しました。
皆さんの参考になれば幸いです。即席で色々と書きましたが、詳しくは別の機会に。


Mainページに戻る