(2022-05-14 23:10 追記: KV を使ってみたので、最後に追記します)
はじめに
前々から Cloudflare Workers が気になっていたので、SORACOM と繋げられるか試してみました。本当は R2 や D1 と絡めてみたかったのですが、贅沢は言わずにまずは Workers だけで試してみます。
事前準備
SORACOM のデバイスは SORACOM LTE-M Button for Enterprise を使いました。Slack のアカウントも準備しておきます。
手順
Slack の Incoming Webhook の準備
Slack のドキュメント Slack での Incoming Webhook の利用 | Slack を参考に、Incoming Webhook を準備します。
Cloudflare Workers のセットアップ
まずは、https://www.cloudflare.com/ja-jp/ からサインアップをします。
Cloudflare workers を作っていきます。
サブドメインを決めます。
無料プランを使います。
サインアップ時にメール認証をまだしていなかったら認証します。
できました。
次に CLI をセットアップしました (参考: Wrangler (command line) · Cloudflare Workers docs)。NodeJS が使える環境で、以下のコマンドでインストールします。
npm install -g wrangler
ログインは以下のコマンドで。
wrangler login
別途ブラウザを開いて OAuth してくれるのがありがたいです。
初期化します。英語ですが、対話形式でセットアップしてくれるのが良かったです。TypeScript のセットアップもしてくれました。
wrangler init my-worker
ローカルで起動します。
cd my-worker
wrangler dev src/index.ts
以後、index.ts ファイルを編集・保存すると自動で更新してくれるのも良かったです。テストの実行は localhost 宛に curl してできます。何もいじっていない状態で、Hello World
がちゃんと出てきました。console.log()
のデバッグ結果なんかも wrangler dev
しているウィンドウで出てきます。
$ curl localhost:8787
Hello World
デプロイも簡単です。
wrangler publish
Cloudflare Workers から Slack への POST
Cloudflare はかなりサンプルが充実していまして、ここから必要なものだけ拾っていけばやりたいことは実現できました。
今回は「ヘッダ認証」「POST で JSON を受け取り」「Slack へ POST」をしたかったので、以下のサンプルを参考にしました。
- Auth with headers · Cloudflare Workers docs
- Read POST · Cloudflare Workers docs
- Post JSON · Cloudflare Workers docs
書き方 (特に TypeScript が久しぶりだったので...) が正しいかはさておき、こんなプログラムを書きました。
const PRESHARED_AUTH_HEADER_KEY = 'X-Custom-PSK';
const PRESHARED_AUTH_HEADER_VALUE = 'mypresharedkey';
const SLACK_URL = '###Slack の Incoming Webhook の準備」で控えた URL###';
export default {
async fetch(request: Request): Promise<Response> {
const requestHeaders = request.headers;
if (requestHeaders.get(PRESHARED_AUTH_HEADER_KEY) != PRESHARED_AUTH_HEADER_VALUE) {
return new Response("Invalid key", {
status: 403
});
}
if (request.method != 'POST') {
return new Response("Method Not Allowed", {
status: 405
});
}
if (requestHeaders.get('content-type') != 'application/json') {
return new Response("Unsupported Media Type", {
status: 415
});
}
const requestInput = JSON.stringify(await request.json());
const message = JSON.stringify({
"text": requestInput
});
const requestInit: RequestInit = {
body: message,
method: 'POST',
headers: {
'content-type': 'application/json;charset=UTF-8',
},
};
const url = SLACK_URL;
const response = await fetch(url, requestInit);
return new Response(await response.text())
},
};
実際に Slack へ投稿されるか、まずはローカルで検証します。
curl -X POST -d '{"foo":"bar"}' http://localhost:8787 -H 'X-Custom-PSK:mypresharedkey' -H 'content-type: application/json'
成功すると、こんな感じで出てきます。
wrangler publish
でデプロイして、https://{プロジェクト名}.{サブドメイン}.workers.dev
へのリクエストでも成功するか確認します。
curl -X POST -d '{"hoge":"fuga"}' https://{プロジェクト名}.{サブドメイン}.workers.dev -H 'X-Custom-PSK:mypresharedkey' -H 'content-type: application/json'
SORACOM のセットアップ
今回は、SORACOM LTE-M Button for Enterprise (Enterprise Button) を使いました。このデバイスは UDP で SORACOM へデータを送るので、SORACOM Beam の UDP → HTTP/HTTPS エントリポイントを使います。デバイスや SORACOM Beam のセットアップ詳細は、ドキュメントを参照してください。
- SORACOM LTE-M Button for Enterprise ユーザーガイド | SORACOM LTE-M Button シリーズ | ソラコムユーザーサイト - SORACOM Users
- エントリポイントリファレンス: UDP → HTTP/HTTPS エントリポイント | SORACOM Beam | ソラコムユーザーサイト - SORACOM Users
まず、Enterprise Button を適当なグループへ所属させます。
次に、グループ名をクリックして、グループ設定画面にて「SORACOM Air for セルラー設定 > バイナリーパーサー」と「SORACOM Beam 設定」を編集・保存します。
「SORACOM Air for セルラー設定 > バイナリーパーサー」ではのフォーマットでは @button
とカスタムフォーマットを設定します。
「SORACOM Beam 設定」では「設定を追加する」をクリックして、以下のように設定します。
設定項目 | 設定内容 | 備考 |
---|---|---|
プロトコル | HTTPS | - |
転送先 - ホスト名 | https://{プロジェクト名}.{サブドメイン}.workers.dev | Cloudflare の設定 |
ポート番号 | 443 | - |
パス | 空欄 | - |
カスタムヘッダ - アクション | 追加 | - |
カスタムヘッダ - ヘッダ名 | X-Custom-PSK | Cloudflare の設定に合わせます |
カスタムヘッダ - 値 | mypresharedkey | Cloudflare の設定に合わせます |
SORACOM LTE-M Button for Enterprise からデータを送信
では試してみましょう...
きました!これだけだと Cloudflare から来ているかわからないのが残念ですが、できました。
まとめ
Cloudflare Workers、個人的には Amazon API Gateway + Lambda という感じでとても気に入りました。さらっと書けるので、Slack や LINE などへの投稿はこれで良いかも。次は KV、 R2 や D1 との組み合わせもやっていきたいと思います。
追記 - KV を使ってみた
KV を使ってみました。別記事に起こすほどではなかったので追記します。ただ、ちょっと使い方が正しいかは怪しいです。。本当は env
を使わず、こちらのドキュメント を参照しつつ KVNamespace
型を定義するだけでいけると思ったのですが、想定したように動作しなかったのでいったん env
を使っています。
まず、KV を作成します。--preview
はローカルテスト用です。
$ wrangler kv:namespace create "TEST_KV"
:namespace create "TEST_KV" --preview ⛅️ wrangler 2.0.5
-------------------
▲ [WARNING] No configured name present, using `worker` as a prefix for the title
🌀 Creating namespace with title "worker-TEST_KV"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "TEST_KV", id = "XXXXXXXXXXXXXXXXXXXXXXXXXXX" }
$ wrangler kv:namespace create "TEST_KV" --preview
⛅️ wrangler 2.0.5
-------------------
▲ [WARNING] No configured name present, using `worker` as a prefix for the title
🌀 Creating namespace with title "worker-TEST_KV_preview"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "TEST_KV", preview_id = "YYYYYYYYYYYYYYYYYYYYYYYYYYYY" }
得られた id
と preview_id
を wrangler.toml
に追記します。binding
の名前は任意で良いです。
kv_namespaces = [
{ binding = "KV", id = "XXXXXXXXXXXXXXXXXXXXXXXXXXX", preview_id = "YYYYYYYYYYYYYYYYYYYYYYYYYYYY" }
]
KV に Key, Value ペアを追加していきます。先ほど得られた id
と preview_id
を使って CLI でやるのが楽です。今回は最初の例でグローバル変数にしていたものを KV に入れていきます。
id="XXXXXXXXXXXXXXXXXXXXXXXXXXX"
wrangler kv:key put PRESHARED_KEY "mypresharedkey" --namespace-id ${id}
wrangler kv:key put PRESHARED_KEY_HEADER "X-Custom-PSK" --namespace-id ${id}
wrangler kv:key put SLACK_URL "###Slack の Incoming Webhook の準備」で控えた URL###" --namespace-id ${id}
id="YYYYYYYYYYYYYYYYYYYYYYYYYYYY"
wrangler kv:key put PRESHARED_KEY "mypresharedkey" --namespace-id ${id}
wrangler kv:key put PRESHARED_KEY_HEADER "X-Custom-PSK" --namespace-id ${id}
wrangler kv:key put SLACK_URL "###Slack の Incoming Webhook の準備」で控えた URL###" --namespace-id ${id}
プログラムも、KV を使うように修正します。せっかくなので、ボタンをクリックされた回数をカウントアップするようにしてみました。
export default {
async fetch(request: Request, env): Promise<Response> {
// Get parameters from KV
const preSharedKey: string = await env.KV.get("PRESHARED_KEY");
const preSharedKeyHeader: string = await env.KV.get("PRESHARED_KEY_HEADER");
const slackUrl: string = await env.KV.get("SLACK_URL");
// Confirm KV value is not null
if (!(preSharedKey && preSharedKeyHeader && slackUrl)) {
return new Response("Internal Server Error", {
status: 500,
});
}
const oldCount = await env.KV.get("COUNT");
let newCount: Number;
if (!oldCount) {
newCount = 1;
} else {
newCount = Number(oldCount) + 1;
}
await env.KV.put("COUNT", newCount);
// Authenticate with header
const requestHeaders = request.headers;
if (requestHeaders.get(preSharedKeyHeader) != preSharedKey) {
return new Response("Invalid key", {
status: 403,
});
}
// Only allow POST request
if (request.method != "POST") {
return new Response("Method Not Allowed", {
status: 405,
});
}
// Only allow "application/json" content-type
if (requestHeaders.get("content-type") != "application/json") {
return new Response("Unsupported Media Type", {
status: 415,
});
}
// Post data to Slack using data from client
const requestInput = JSON.stringify(await request.json());
const message = JSON.stringify({
text: "Click count is " + newCount + ". Value is " + requestInput,
});
const requestInit: RequestInit = {
body: message,
method: "POST",
headers: {
"content-type": "application/json;charset=UTF-8",
},
};
const response = await fetch(slackUrl, requestInit);
return new Response(await response.text());
},
};
最後にデプロイすれば、KV を使うように変更されます。図のように、カウントアップすることが確認できました。
ただドキュメントを読んだところ、「一般的に、書き込みは比較的頻繁には行わず、読み取りは迅速かつ頻繁に行う必要があるユースケースに向いている。(DeepL 訳)」なので、複数デバイスからのデータアップロードで同じ KV を書き換えるとかはやめた方が良さそうです。
とはいえ、やはりシンプルに使える良さがあるので、SORACOM との絡め方も今後考えていきたいと思います。