今後はこっちを更新していく予定です。
slackに毎日情報を流しているbotをcloudflare workersに移植してみる
前の記事で調べていた時にschedule triggerを使えば無料枠でもcron的な事ができそうだったので移植で素振りしてみる
cloudflare workersでslack botを動かすには@slack/botは動かないらしいので、以下の記事で紹介されているものを使う
各種slack用のトークンは上記の記事を参照
cron triggerについて
wrangler.ymlなどにcron設定を書くとその設定に従って以下の用に定義した関数を実行してくれる
時間設定
wrangler.ymlに設定するのはこんな感じ(ドキュメントに書かれているサンプル)
[triggers] # Schedule cron triggers: # - At every 3rd minute # - At 3PM on first day of the month # - At 11:59PM on the last weekday of the month crons = [ "*/3 * * * *", "0 15 1 * *", "59 23 LW * *" ]
cronsに設定できる時間は3つまでらしい
実装
初期のテンプレートだとsrc/index.tsにscheduled関数を定義するとそれが実行される
export default { async scheduled(event: ScheduledEvent, env: SlackEdgeAppEnv, ctx: ExecutionContext): Promise<void> { // 処理 };
eventには実行された時間と発火したcronの設定値("*/3 * * * *"のようなもの)が入っている。なのでevent.cronの値を見て3つで出し分ける事も可能だと思う。
実際に作ってみる
実際に実行するときはcloudflareに環境変数として設定するがローカルでテストする時のために.dev.varsにtoken設定などを書いておく
SLACK_BOT_TOKEN=xoxb-xxxxx SLACK_SIGNING_SECRET=xapp-xxxxx
scheduleのコードは以下。exec関数でslackに投稿するデータの取得や組み立て、投稿までを行っている。
import { SlackApp, SlackEdgeAppEnv } from "slack-cloudflare-workers"; import * as xxx from "./features/xxx/index"; export default { async scheduled( _event: ScheduledEvent, env: SlackEdgeAppEnv, _ctx: ExecutionContext, ): Promise<void> { try { const app = new SlackApp({ env }); await xxx.exec(app); console.log("done"); } catch (e) { console.error(e); throw e; } }, }; // execの中のイメージ export function exec(app: SlackApp<SlackEdgeAppEnv>) { const data = await xxxApi(); const text = processing(data); await app.client.chat.postMessage({ channel: "random", text, });
これだけで簡単に投稿することができた
テスト
wrangler devを実行する際に--test-scheduledというオプションを付けることでschedule発火用のエンドポイントが出てくる。
実行後以下のようにリクエストを投げることでscheduled関数を実行させることができる
curl "http://localhost:8787/__scheduled?cron=0+0+*+*+*"
疑問点
cron triggerで実行しconsole.logでログを出すようにしてみたがどこで確認できるのかわからず、失敗した時のデバッグで困った。
またcronの設定のTZについてUTC固定?なので日本時間で実行する場合は+9時間後に実行される事に注意する必要がある。
cloudflare workerを始める
cloudflare workerずっと気になってたのでhelloworldとそのテストを触ってみたメモ
事前にアカウントは作っておいたのでプロジェクト作る所から。
プロジェクトを作る
pnpm create cloudflare@latest
これを実行するとcliでログインしていなければブラウザが起動して、cloudflareにログインする
コード
hello worldのテンプレートを選択して、生成されたsrc/index.tsを確認すると以下のコードが生成されていた
export interface Env { } export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { return new Response('Hello World!'); }, };
Responseをnewしてreturnするとレスポンスを返せるのがわかる
メモ
トリガー
実行方法としては他にも
- Alarm
- Queue
- Scheduled(cron的な)
- 3つまでの制限あり
- Tail
- リアルタイムでログを処理しロギングサービスや分析サービスに送信するために使うらしい
などトリガーにすることもできる*1
Node.js Compatibility
Cloudflare workerは完全にnodejsのAPIをサポートしてるわけではない
ただNode.js compatibilityというのがありwrangler.tomlにcompatibility_flags = [ "nodejs_compat" ]と書いてフラグをオンにするとnode:bufferなど一部のAPIが使えるようになるらしい
環境変数
基本的にはwrangler.tomlの[vars]部分に記述するらしいが、gitignoreの対象ではないので内容によっては書くのに抵抗がある
そういう時はローカルで動かす時は.dev.varsというファイルを作り、その中に.envのように記述するとwrangler devした時に引数のenvに入る。
debug
デバッグはwrangler devで行う事ができる。実行するとローカルサーバーが起動する。
なのでdevを実行してるターミナルと、httpie*2を使ってリクエストを送るターミナルの2つを開いてやってみる。
# terminal A pnpm run dev # 裏でwrangler devを実行している # terminal B http localhost:8787 HTTP/1.1 200 OK Content-Length: 12 Content-Type: text/plain;charset=UTF-8 Hello World!
テスト
テストにはwranglerパッケージから出てるunstable_devを使う
hello worldのテンプレートにはテストツールが入っていないのでvitestを入れた*3
公式のサンプルで示されてるのはjsなのでtsとして書いた
import {unstable_dev, type UnstableDevWorker} from 'wrangler' import {describe, beforeAll, afterAll, it, expect} from 'vitest' describe("Worker", () => { let worker: UnstableDevWorker beforeAll(async () => { worker = await unstable_dev('src/index.ts', { experimental: { disableExperimentalWarning: true } }) }) afterAll(async () => { await worker.stop() }) it('should return Hello World', async () => { const resp = await worker.fetch() if(resp) { const text = await resp.text() expect(text).toMatchInlineSnapshot(`"Hello World!"`) } }) })
今回は文字列を返すシンプルなものだが、KVなどを使い始めるとminiflareを使えばテストできるのか今後確認したい
その他
quickstartsとしてHonoを使っているテンプレートやGraphQL Yogaのテンプレートのようなものまであるようなのでgenerateしてみて中を見るのも良さそう。
ただwarngler generateを実行すると
[WARNING] The
generatecommand is no longer supported and will be removed in a future version.
と表示されるのでそのうち動かなくなりそう
またasync fetch以外にもasync cronのように書くと定期実行できるみたいなのでそっちもそのうち試してみる
*1:cloudflareのコンソール上にそれぞれTriggersとして定義されている
*3:ts-nodeとか使うとnodeのテストランナーでもできるとは思うTest runner | Node.js v21.2.0 Documentation