MomentoのWebhookを試してみる

2023.12.05

Introduction

MomentoでWebhook機能が使えるようになりました

Webhookを使えば、Momento Topicsへpublishしたタイミングで
任意のエンドポイントのアクションを実行することができます。

例えば、こちらの記事では、Momento TopicsとWebhookを利用した多言語チャットのデモを紹介しています。

デモではMomento Topicsへメッセージをpublishすると、
WebhookでAWS Lambdaが起動し、各言語へ翻訳してそれぞれのTopicにpublishします。
ユーザーは希望する言語(デモ画面右上)のTopicをsubscribeしているので、
選択した言語でメッセージを見ることができます。

本稿ではngrokとTypeScriptで実装したアプリを使って、
Momento Webhook機能を確認してみます。

Momento?

Momentoはこのblogで紹介している、
クラウドネイティブな各種サーバレスサービスです。
Data Caceh,メッセージング(Topics),Vectorデータベース(Vector Index)と、
用途に応じて各種サービスを提供しています。

Environment

  • MacBook Pro (13-inch, M1, 2020)
  • OS : MacOS 13.5.2
  • ngrok : 3.4.0
  • node : v20.8.1

Setup

Momentoの設定

まずはMomentoキャッシュを作成します。
Momento Consoleにアクセスしましょう。

momento-cloudflare-0

アカウントがまだないならここでSignupしてログインします。
ログインしたら、キャッシュ一覧からキャッシュ作成を選択し、
キャッシュ名を記述してクラウドプロバイダーとリージョンを指定します。
今回は「webhook_cache」という名前のキャッシュを作成しました。

次に、トークンのページにアクセスして、
先ほど作成したキャッシュのアクセス権限を持つ
Access Tokenを生成します。

「トークンを生成する」ボタンをクリックするとトークンが生成されるので、
環境変数に設定しておきましょう。

% export MOMENTO_TOKEN="<Auth Token>"

Webhook作成

作成したキャッシュの画面左メニューから「Webhooks」を選択します。
「Webhookの作成」ボタンを押して作成画面へいきましょう。

webhook-1

Webhook名は適当に決めて、トピック名は「webhook_topic」とします。
「Webhookの宛先」はあとで修正するので、適当に設定します。

webhook-2

Webhookを生成すると、それに対応するシークレット文字列が生成されます。
この文字列を使うと、リクエストがMomento から発信されたものかどうか検証できます。
これも覚えておきましょう。

ngrokの設定

ngrok(エングロック)はローカルで動作しているWebサーバを、
パブリックインターネット上に一時的に公開できるツールです。
例えば、自分のPCで開発しているWebアプリケーションを手軽に他の人に見せたり、
今回のようなWebhookサービスを試すときに便利です。

ngrokを起動すると公開用のURLが作成されるので、
そのURLを使用してローカル環境にアクセスできます。
このURLは、ngrokを停止するまで有効となります。

MacならHomebrewでインストールできます。

% brew install ngrok/ngrok/ngrok

あとはsignupしてauthtokenを取得して設定します。
ここなどを確認して設定しましょう。

設定できたら、↓のように起動できます。 これは、ローカルの3000番ポートを
インターネットに公開することになります。

% ngrok http 3000

起動すると下記のような情報が表示されます。
Forwardingに、実際に割り当てられたアドレスが表示されます。

ngrok                                                                               (Ctrl+C to quit)

Build better APIs with ngrok. Early access: ngrok.com/early-access

Session Status                online
Account                       xxxxxx (Plan: Free)
Version                       3.4.0
Region                        United States (us)
Latency                       881ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://xxxxxx.ngrok-free.app -> http://localhost:3000
Connections                   ttl     opn     rt1     rt5     p50     p90
                              2       0       0.02    0.01    0.03    0.05

HTTP Requests
-------------

GET /favicon.ico               404 File not found
GET /                          200 OK

3000番ポートをフォワードするように起動し、
割り当てられたアドレスをMomentoのWebhook画面の Webhookの宛先に設定しましょう。
これで事前準備は完了です。

Try

では、ngrokからフォワードする先のWebアプリを実装します。
まずは必要なモジュールをインストールしておきます。

% npm install @gomomento/sdk crypto --save
% npm install typescript ts-node @types/node --save-dev

最初はexpress使おうとしたんですが、
なぜかWebhookからPOSTのbodyが受け取れなかったので
httpモジュール使ってます。

POSTのリクエストを受けると、bodyをパースして、
うけとった値(body.text)を表示してます。
また、didRequestComeFromMomentoの結果がtrueであれば、
Webhookからきたリクエストだと判断されます。

//index.ts
import http from 'http';
import crypto from 'crypto';

//Webhookからのリクエストかどうかチェックする
function didRequestComeFromMomento (req:any,rawBody:string): boolean {
  const hash = crypto.createHmac("SHA3-256", "<Webhook生成時のシークレット文字列>");
  const hashed = hash.update(rawBody).digest('hex');
  return hashed === req.headers['momento-signature'];
}

const server = http.createServer((req, res) => {
  if (req.url === '/' && req.method === 'POST') {

    let body = '';
    req.on('data', chunk => {
      body += chunk.toString(); // raw body
    });

    req.on('end', () => {
      console.log('Body.text:', JSON.parse(body).text); 
      console.log("didRequestComeFromMomento : " 
        + didRequestComeFromMomento(req,body));
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ message: 'hello' }));
    });
  } else {
    res.writeHead(404);
    res.end();
  }
});

const port = 3000;
server.listen(port, () => console.log(`Server is running at http://localhost:${port}`));

デモではWebhookで起動されたLambdaがメッセージを各言語に翻訳して、
各言語用のTopicsにそれぞれ対応したメッセージをpublishしてます。

作成したモジュールを起動させておきます。

% npx ts-node index.ts
Server is running at http://localhost:3000

この時点で、ngrokから割り当てられたアドレスにアクセスすると
ローカルのサーバにフォワードされます。

ではMomento Topicsにメッセージをpublishして
Webhookを起動してみましょう。
Topicsにpublushするサンプルコードは下記です。

//pub.ts
import {
    TopicClient,
    TopicPublish,
    Configurations,
    CredentialProvider,
} from '@gomomento/sdk';

const cacheName = "webhook_cache";
const topicName = "webhook_topic";

async function main() {

    const momento = new TopicClient({
        configuration: Configurations.Laptop.v1(),
        credentialProvider: CredentialProvider.fromEnvironmentVariable({
            environmentVariableName: 'MOMENTO_TOKEN',
        }),
    });

    const message = { 'message': "bar" };
    console.log(
        `Publishing cacheName=${cacheName}, topicName=${topicName}, value=${message}`
    );

    const publishResponse = await momento.publish(
        cacheName, topicName, JSON.stringify(message));
    if (publishResponse instanceof TopicPublish.Success) {
        console.log('Value published successfully!');
    } else {
        console.log(`Error publishing value: ${publishResponse.toString()}`);
    }
}

main()
    .then(() => {
        console.log('publish success.');
    })
    .catch((e: Error) => {
        console.error(`Uncaught exception while running example: ${e.message}`);
        throw e;
    });

さきほど作成したMomento Cache名とTopics名を指定してpublishしてます。
では実行してみましょう。

% npx ts-node pub.ts

WEBサーバにWebhookリクエストがきており、
ログがでているはずです。

# WEBサーバのログ
・・・
Body.text: {"message":"bar"}
"event_timestamp":1701762660904,"publish_timestamp":1701762624505,"topic_sequence_number":1,"token_id":"","text":"{\"message\":\"bar\"}"}
didRequestComeFromMomento : true

ちなみに、プログラムでwebhook_topicをsubscribeしておけば
普通にpublishされたメッセージを受け取ることができます。

Summary

今回はMomentoの新機能、Webhookを動かしてみました。
デモでは多言語翻訳チャットを実装していますが、
他にもいろいろな使い方ができそうな機能です。

References