HTTPリクエスト/レスポンスを記録してリプレイするためのruby gem作った
RubyGem作ってみよう、Rackミドルウェア書いてみよう、の習作。
みたいな使い方。
実用上は、Goで書かれたgoreplayなど、もっと便利なツール群がある。 名前はこれをオマージュしてつけた。
どんなものか
使い方は大体こんな雰囲気でconfig.ruとかに書く。
use Rack::Rreplay.Middleware(directory: './tmp', format: :json), { sample: 5, extra_header_keys: %w[X-ACCESS-TOKEN], debug: true }
HTTPリクエスト/レスポンスのdumpを吐き出すdirectoryとformat(:msgpack or :json)を与えつつ、オプションをいくつか渡せるようになっている。 この設定であればHTTPリクエスト5回につき1回dumpするようになっている。
出力結果はJSONなら例えばこんな感じ。
{ "request": { "body": null, "headers": { "X-ACCESS-TOKEN": "eyJhY2Nlc3MtdG9rZW4iOiJGdkNyczdnT0pLYUN3SU5lTWNmMjVnIiwiY2xp%0AZW50IjoiZnlsc01qMHlfYTl3cHVpWmJiWUJodyIsInVpZCI6ImFsaWNlQGV4%0AYW1wbGUuY29tIn0%3D%0A", "content-type": "application/json", "cookie": null, "user-agent": "petitviolet curl" }, "method": "GET", "path": "/api/whoami", "query_strings": "" }, "response": { "body": "{\"id\":\"f7fb4b22-1792-477f-ab28-cd36491fd09f\",\"name\":\"alice\"}", "headers": { "Cache-Control": "max-age=0, private, must-revalidate", "Content-Type": "application/json; charset=utf-8", "X-Access-Token": "...", ... }, "status": 200 }, "response_time": "0.395582", "time": "2019-12-30T23:43:41+09:00", "uuid": "c80468a9-5424-4a17-a1bc-6ab325252d36" }
このログを使ってHTTPリクエストをリプレイするとこんな感じになる
$ bundle exec rreplay 'http://localhost:3000' ./tmp/rreplay.log.json -f json --verbose | jq -S . { "response": { "actual": { "response": { "body": "{\"id\":\"f7fb4b22-1792-477f-ab28-cd36491fd09f\",\"name\":\"alice\"}", "headers": { "Cache-Control": "max-age=0, private, must-revalidate", "Content-Type": "application/json; charset=utf-8", "X-Access-Token": "...", ... }, "status": "200" }, "response_time": "0.029326" }, "recorded": { "record": { "body": "{\"id\":\"f7fb4b22-1792-477f-ab28-cd36491fd09f\",\"name\":\"alice\"}", "headers": { "Cache-Control": "max-age=0, private, must-revalidate", "Content-Type": "application/json; charset=utf-8", "X-Access-Token": "...", ... }, "status": 200 }, "response_time": "0.395582" } }, "time": "2020-01-02T22:35:47+09:00", "uuid": "c80468a9-5424-4a17-a1bc-6ab325252d36" }
このように、あるリクエストをもう一度投げた時にデグレしてないかとかレスポンスタイムに大きな差がないか、というのをいい感じに頑張ればテストや性能検証とかに使えないかな〜とは思っているが何も実装はしていない。
実装について
Rackミドルウェアという都合上、initialize(app, ...)
とcall(env)
が定義されたクラスであることが求められる。
HTTPのリクエストとレスポンスを吐き出していくためファイルのローテーションをやりたいというモチベーションが湧いてきて、initializeするたびにLoggerをnewするのは流石に避けたかったのでClass.new
でミドルウェアを作ることにした。
use Rack::Rreplay.Middleware(directory: './tmp', format: :json), { sample: 1, extra_header_keys: %w[X-ACCESS-TOKEN], debug: true }
このようにconfig.ru等に書くとミドルウェアが有効になるが、Rack::Rreplay.Middleware
はClassオブジェクトを返す関数となっていて実装はこのあたりrreplay/rreplay.rb
関数の引数に与えたディレクトリ名を使ってログローテーションするLogger(正確にはLogger::LogDevice)のインスタンスをnewして使い回すためにClass.new
で作ったクラスのクラス変数にloggerを埋め込むというやり方をしている。
クラス変数、というだけでなるべく使いたくない気持ちにはなるので別の方法があればよかったが思いつかなかった。
また、JSONとMessagePackの2種類のフォーマットをサポートしてみている。JSONをMessagePackにして少し圧縮しているだけだが。
MessagePackはUTF-8なStringではないのでbinaryとしてファイルに書き込む必要があり、内部的に使用しているLogger::LogDevice
のコンストラクタにbinmode: true
を渡すことで動くようになる。
Loggerを使い回すという観点だとClass.new
ではなくLoggerをユーザ側から差し込んでもらう方がシンプルにはなるが、こういったフラグ管理などを考えるとこれで良かったのかもしれない。(一応差し込めるようにはなっている)