Webhookを作るときのデバッグに役に立つものを作った
その名もWebhookDebugger 実はRailsのアプリケーションで15分位でシュッと書いたものを単機能だったのを、素のRackアプリケーションにして書き直したものである。
以下に簡単にこのアプリケーションについての機能を簡単に説明しておく。
基本はオウム返しをするだけのアプリケーションだが...
ロジックはすごい簡単で、GETで送るクエリパラメータやPOSTのform-dataないしはjsonをレスポンスに含めて返してくれるものである。 それだけだとただのオウム返しAPIでWebhookを作るときに何も便利にはならない。 そこで一工夫を凝らしてみることでWebhookを作るときに便利にしてみた。
1. どのパス、どのHTTPメソッドを指定してもリクエストを受ける取ることができる。
request.binなどの外部サービスでも良いのだけど、これは受け取る先のURLパスが決まってしまっている。 どのパスに送信するかというのをユニットテストレベルではなく、ちゃんと目で確認したいときに重宝するだろう。
curl -XPOST http://localhost:9292/hoge/fuga | jq { "path": "/hoge/fuga", "request_method": "POST", "status_code": 200 } curl http://localhost:9292/nick | jq { "path": "/nick", "response_status_code": 200, "request_method": "GET", "status_code": 200 }
2. 任意のHTTPステータスコードを返させることができる
パラメータに response_status_code
というものが存在し、これに対して任意のHTTPステータスコードを設定することができる。
コレにより、5xx系のエラーレスポンスの場合はX分後に送信キューに再送処理を積む。他にも4xx系だったら再送処理無しで異常終了にする。あとは5回以上エラーになったら再送をやめる。
といったことの確認が簡単に出来るようになる。
以下のコードの場合、レスポンスのHTTPステータスコードが必ず401になるようになる。
curl -XPOST http://localhost:9292/hoge/fuga?response_status_code=401 | jq { "path": "/hoge/fuga", "response_time": 1000, "response_status_code": 401, "request_method": "POST", "status_code": 200 }
3. レスポンスの待機時間を設定することができる
パラメータに response_time
を設定することで、任意のミリ秒分だけ、レスポンスを返す時間を遅らせることができる。(waitで雑に設定しているだけなので、きっかり1秒とかではなく、ベストエフォートです。)
コレによりサーバレスポンスに時間がかかってしまった場合、一旦失敗として扱って送信キューに再送処理を積む。といったことの確認が簡単になった。
curl -XPOST http://localhost:9292/hoge/fuga?response_time=1000 | jq { "path": "/hoge/fuga", "response_time": 1000, "request_method": "POST", "status_code": 200 }
以上の3つがあることで、 オウム返しによる、レスポンスでの送信内容にチェック、 エラー時のWebhookの再送機能やレスポンス待機時間によるエラーハンドリングなどが実現可能になった。
Webhook送信元のアプリケーションがdocker-composeで管理されている場合、Webhookの送信先として、このアプリケーションのコンテナを定義しておくのが良いと思う。
なかなかWebhook送信先のアプリケーションの実装について知識がないと、 こういうテストがなかなか難しかったり、送信先のアプリケーションの完成を待たないとちゃんとテストできないとかがありがちなのだけど、 このAPIを利用することでエラーケースのテストや再送のテストが用意になって、効率がグッと向上した。
どういったわけか、業務上Webhookを作る機会が何故か多かったし、 これからもWebhook処理も書く必要はかならずあるので、このAPIは今後も重宝することだろう。
Sinatraアプリを素のRackアプリケーションに書き換えた
Rackとは
Webサーバ/Webアプリケーションフレームワーク間のインタフェースの役割を果たすライブラリで、 Rackを利用してフレームワークやアプリケーションのインターフェース部分を実装することで、 Webサーバを変更したり、逆にWebサーバを変更しないでもRackでインタフェースを実装されたフレームに置き換えることができる。
Rubyだと、WEBrickやUnicron、PumaにPassengerといろいろなアプリケーションサーバがあるけど、 多少挙動に違いがあるにせよ、RailsやSinatraといった性質の異なるフレームワークが殆どのアプリケーションサーバで稼働するのは Rackでインタフェース部分を実装されているからである。
他の言語
むしろRubyのRackは後発で、Pythonで既にあった、WSGIに影響されて実装されたと言われている。 PerlではPSGIがそれに相当する。*1
Rackについてぼんやりと理解していたけど、 このあたりに乗っているリンクを眺めて更に理解を深めることができたので、興味があると見てみると良いでしょう。
アプリケーションを実際に書き換えてみた。
Rackってそう言えばちゃんと触ったこと無いな〜と思ったので、とりあえず触ってみることに。
なんかちょうどいい感じのSinatraアプリケーションのogp_parse_apiが転がっていたので、 今回はコレをSinatraから素のRackアプリケーションに書き換えて見ることにする。 ogp_parse_apiについては、過去のブログに機能的には詳しく書いているので、よかったら見てくれ。
というわけで簡単に作ったアプリケーションをRackだけで書き換えてみた。 ルーティングするものも1つしか無いし、機能が全く無いのであっさりできた。
これに取り組んだ大きなモチベーションとしては、技術的なアドバイスをくれる人がいるのだけど、 その人がとある案件の対処法にRackでアプリケーション書いて対応しようとした。 という話を聞いたのがきっかけで、Rubyエンジニアで2年弱やってきているのに、 素のRackアプリケーションを書いたことがないのは、 Rubyエンジニアとしてモグリなんじゃないのかと思えて焦りを感じてるし、素振りがてらやってみた。
good-bye sinatra by webuilder240 · Pull Request #1 · webuilder240/ogp_parse_api · GitHub
書き換えてみてよかったこと
- Sinatraの依存から開放された。
- 他のGemに依存しているので、まあ誤差だと思うけど。
- それ目線で言うならDockerイメージをalpineベースにしたり、nokogiriに依存するGemをなくす方が先。
- Rackのことがなんとなくわかった、ちょっとは仲良くなれた。
- 本当に単機能であればRackで書くのもありかなと思いました。
- 今後nodejsとかやngx_mrubyに置き換えるのも見据えるようなアプリケーションの場合、それよりも敷居が低い素のRack実装は、調べることも少なくある程度はパフォーマンス稼げるし取り掛かりとしていいのかなと。
- 速度は未計測...
まとめ
まぁ本当に素振りでやったのでただの自己満足です。 ただ、こういう適当に遊べる環境を持っておくのは大事だと思いました。
*1:AuthorはRebuild.fmでおなじみの宮川さんです。
javascript経由で<script>タグを呼び出して実行する方法
これやりたい経緯
静的ファイル上でユーザーエージェントを判別して、広告タグを出し分けたいというのを実現するための方法と一つとしてです。 本当は配信先の広告タグスクリプトでよしなにやっててほしいんですが、 そういうわけにも行かないときはやっぱりあるので...
サンプルコード
jsfiddleにでも置きたいと思っていたのだけど、jsfiddleでは複数ファイルのスクリプトの実行をサポートしていないように見えたので、 とりあえずgistにでも置いておく。 まぁ、結果から言うと普通に出来た。
load dynamic execute script tag
まとめ
ただ、配信先の広告タグをコレでよしなに出し分けれたとしても問題があるかもしれないし、 収益に関わる案件なので、導入は慎重に行きたい。
毎週技術的なブログを書く為に実践していること
今年のお正月からなんとなく毎週ブログを更新するというのを初めて10回を超えたので自分なりに振り返ってみようと思う。
ネタ探しについて
まだ10回なんだけど、業務内容にも恵まれてるのかもしれんがぶっちゃけあまり困ってないです。 ネタはたくさんある方だと思っているけど、裏を取るために動かしたり色々調べたりするのがとにかく面倒...*1
過去にやって見たことを引っ張り出してる。
別に技術ブログには「今やっていること」「将来やりたいこと」を書くというレギュレーションはない。 三年前に書き散らしたコードをブラッシュアップしてブログに出す、のでもいいと思う。
自分の記事だと、 これは投稿した時点よりも2〜3ヶ月前にライブラリの素体は出来上がっていたので、 ブログに投稿する記念にHerokuにデプロイとDockerイメージとしてPushしたくらいで、他に特別なことはしていない。
自作ライブラリについてをブログに書く
自分で便利で作ってみたライブラリとアウトプットを両立するためにライブラリを書くだけじゃなくて、 日本語でもいいから、説明をブログに残しておくというのはいい方法かもしれない。 自分はまだ経験ないけど、誰かに見つけてもらって使ってもらう...なんてこともあるかもしれないし。*2
アップデートした場合は、アップデートについて書くのもいいかもしれない。 些細なアップデートでも立派なアウトプットである。
本当に些細・ニッチなものでもとにかく投稿してる
忘れてはならないのは、「バズ」のために記事を書いているわけではないということだ。 自分の書いたブログはひたすらはてブ0がならんでいるし、承認欲求を満たすことは出来ていない。 個人的にムカつくのでリンクは貼らないけど、 Qiitaにあるような「フロントエンドの用語・技術だけを雑に集めた雑なリンク集」がブックマークを集めるのが昨今の現状である。
とにかく、他人の評価は気にせずに、自分で正しいと思ったことをとにかく続けることに専念しよう。 承認欲求に関しては、まぁブックマークきたらうれしいねーくらいにしておいたほうが良いかもしれない。 webuilder240.hatenablog.com
最初の一歩を大事にする為に...
かける時に書けるだけ書く、でも欲張らない
とにかく馬力があるときにまとめて7割位まで書けるときは書いてしまったほうが良いと思っている。 書きたいことだけをブログに下書きでつらつら並べておいて、 その後はその項目について調べたりで終わるくらいにするのが一番いいと思ってる。
誘惑に負けない方法
明らかにモンハンをやってる時期は手を抜いていた。 手を抜くことはわかっていたので、軽めの記事で「とにかく繋ごう」と言う気持ちで、 あっさり目のものを書いたり、先に書いた通り、事前に8割方書けるときに貯金しておくという方法で乗り切った。
誘惑に負けない方法は個人的には無いと思っており、 「好きなことを好きなときにやる」のが一番なので、その時は「しっかりゲームで遊ぶ」ことを最優先にしてました。 またプログラミングばかりやるフェーズはやってくると思っているので...
今の所モンハンに200時間くらい時間を溶かしてるけど、無理なく続いてる。*3
今はとりあえず続けるだけを目標にしているだけなので、半年か1年続けたらやめようと思ってる。
やめたあとは定期投稿でなくていいので、アウトプットの質を高める方をやってみようかなと思っている。
FirestoreをRubyのgoogle-api-clientから叩く
そもそもRubyからFirestoreを叩きたい理由
- FirestoreはフロントエンドからWriteできるけど、実装の複雑さを避けるためにフロントエンドからはWriteしないで、 チャットのAPI経由でFirestoreに対してWriteを行う機構が必要だったため。*1 *2
方法
ここまでの方法は調べればいくらでもあるので、他の記事等を参考にして進めて欲しい。 ちょっと画面がややこしいので、この部分を含めても解説しても良かった気がするが時間がないので、また追記します。 以降は実際にコードとしてどう書けばいいか、というのを雑に並べておきます。
require 'google/apis/firestore_v1beta1' SCOPE = ['https://www.googleapis.com/auth/datastore', 'https://www.googleapis.com/auth/cloud-platform'] PROJECT_ROOT_PATH = "projects/hoge-project/databases/(default)" client = Google::Apis::FirestoreV1beta1::FirestoreService.new client.authorization = Google::Auth::ServiceAccountCredentials.make_creds( json_key_io: File.open("#{Rails.root}/config/hogehoge.json"), scope: SCOPE ) message = Message.first # firestoreにドキュメントを追加 def push_firestore(message) client.create_project_database_document_document("#{PROJECT_ROOT_PATH}/documents", 'messages', message_doc(message)) end # firestoreの指定したドキュメントを削除 def destroy_firestore(firestore_message_id) client.delete_project_database_document("#{PROJECT_ROOT_PATH}/documents/#{firestore_message_id}") end # firestoreのフィールド設定 def message_doc(message) doc = Google::Apis::FirestoreV1beta1::Document.new() doc.fields = { "id": { integer_value: message.id }, "content": { string_value: message.content }, "created_at": { timestamp_value: message.created_at.rfc3339 }, "updated_at": { timestamp_value: message.updated_at.rfc3339 } } doc end
これでとりあえずgoogle-api-client経由でドキュメントの作成と削除ができる状態になった。 ほかにもたくさんFirestoreのAPIがあるみたいなので、それはまた今後眺めてみることにする。
感想
- 特にexampleのコードとかなかったし、とにかくわかりづらい印象だった。Gemの中身をちゃんと読みまないと絶対にわからん。
- 各項目に対して型を設定するのがいちいち面倒、ちゃんと使う分には何某かのうっすいラッパーくらいあっていいかも。
参考リンク
*1:あとは私がサーバサイドエンジニアだったのでというのも理由ひとつ
*2:参考: https://speakerdeck.com/sota1235/realtime-messaging-with-firebase-number-phpcon2017