大量 or 同時に届くWebhookをサーバーレスで手軽に確認する


最初にまとめ

  • 取りこぼしてもよいので手軽に確認する
  • 一斉にこないが大量に届く
    • GoogleAppScript
  • 確実に手軽に確認する
    • Firebase Realtime Database
  • 未確認だけど手軽かも
    • ngrok

ニーズ

通知用途で大量に通知されるWebhookがpublicなネットワークを通って漏れなく届くか、内容が問題ないか、確認する必要があった。
ただ、いちいちサーバー立てて・・・というのはめんどくさいのでサーバーレスでできないかも検討した。

受け取るWebhookの内容

下記のようなbodyが通知されるとする。
{ "foo": "aaaa", "bar": 1111, "maybeFoo": "AAAA" }
{ "foo": "bbbb", "bar": 2222, "maybeBar": "BBBB" }
maybeFooとmaybeBarはOptional。

webhook.site

お手軽簡単。
URLにアクセスするだけでWebhook受け取りのためのURLが発行され、通知の内容を表示するViewer付き。
ただし、しばらく使ってると通知が届かなくなったり、同時に受け取ったWebhookを取りこぼすことがあるので、漏れなく届くかどうかの確認には使えなかった。

GoogleAppScript

Webhookは単なるPOSTリクエストなので、もしかしたらGASであれば取りこぼし無くいけるかも。
しかも、スプレッドシートと連携してるし、データ整理もしやすいかも。
と思いチャレンジ。

単純なPOSTリクエストの受け取り

まずは単純にPOSTリクエストを受け取って、Jsonをparseして、該当の値をシートに保存する。

simpleWebhookLog.gs
function doPost(e) {
  if (e == null || e.postData == null || e.postData.contents == null) {
    return;
  }
  doSomething(e.postData.contents);
  return "OK";
}

function doSomething(contents) {
  var reqBody = JSON.parse(contents);

  var ss = SpreadsheetApp.getActive();
  var sheet = ss.getActiveSheet();

  var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];

  var values = [];
  for (i in headers) {
    var header = headers[i];
    var val = "";
    switch(header) {
      case "Date":
        val = new Date();
        break;
      case "foo":
        val = reqBody["foo"];
        break;
      case "bar":
        val = reqBody["bar"];
        break;
      case "maybeFoo":
        if (reqBody["maybeFoo"] != undefined) {
          val = reqBody["maybeFoo"];
        }
        break;
      case "maybeBar":
        if (reqBody["maybeBar"] != undefined) {
          val = reqBody["maybeBar"];
        }
        break;
    }
    values.push(val);
  }

  sheet.appendRow(values);
}

結果としては、ほどほどに受け取れたが、やはり大量に同時に送ると取りこぼしが発生する。

非同期でキャッシュしながら受け取る

下記記事を見つけたので、そのままパクらせていただきました。
Webhookで起動したGAS(Google AppsScript)の応答を非同期処理で高速化する
doSomething(a)の代わりに、addJobQueue(a)を呼び出して、
addJobQueue(a, b)timeDrivenFunction()を少し簡略化して下記のようにしてみた。

asyncWebhookLog.gs
function addJobQueue(contents) {
  cache = CacheService.getScriptCache();
  var data = cache.get("key");

  if (data == null) {
    data = [];
  } else {
    data = data.split(';');
  }

  data.push(contents);
  cache.put("key", data.join(';'), 60*5);

  return;
}

function timeDrivenFunction() {
  cache = CacheService.getScriptCache();
  var data = cache.get("key");
  cache.remove("key");

  if (data == null) {
    return;
  } else {
    data = data.split(';');
  }

  for (var i = 0; i < data.length; i++) {
    doSomething(data[i]);
  }
  return;
}

これを非同期実行して、テスト!
結果としては、先程よりも漏れなく受け取れるようになったが、やはり受け取り漏れが発生した。
これがGASの限界か・・・!!!

ngrok

手段を探しているうちにngrokを見つけたが、セキュリティ的に社内から使うのが少し怖かったので却下。

Firebase Realtime Database

またまた調べているうちに、どうやらRealtime Databaseは直接RESTを叩くことで、リクエスト内容を直接保存することができるらしい。
ということで下記を参考にチャレンジ!1
Firebase Realtime Database を Rest API と Golang でいじってみる

Firebaseコンソールから新しいプロジェクトを作って、Database -> Realtime DatabaseからRESTのエンドポイントとなるURLをコピー。

さらに下記画面からシークレットを取得。

ここからリクエスト用URLを作成。例えばこんな感じ。
https://test-app-75f30.firebaseio.com/message.json?auth=ここをsecretにする

これでWebhookを送ると・・・。できた!!

大量送信 and 同時送信でも耐えうるので、問題なし!
気をつけることは、使いすぎると料金がかかってしまいそうなので、テストが終わったらWebhookの通知先を変えたり、定期的にデータを削除したりしておく。

続き

ElmでView書いてみたよ編


  1. もしかしたら方法として古い可能性があることと、シークレットを使ってるのでセキュリティ的に注意したほうがいいかも。