Web 開発者は HTTP レスポンスをよく見る。 以前 CDN を導入する際に、キャッシュがヒットしているかどうか、どこのエッジがキャッシュを返しているかを確認するためにヘッダをよく見ていた。また、ヘッダだけではなく、TTFB といったレスポンスタイムも気にしている。とにかく HTTP レスポンスをよく見る。
HTTP レスポンスを確認する3つの方法
Chrome さえあれば DevTools を見て一目瞭然である。
とはいえ、コマンドラインで確認したい時がしばしばある。
GUI を操作するよりも手軽である。
その場合はcurlコマンドを叩けばよい。
curl -LIXGET https://httpbin.org
これでプロトコル、ステータス、ヘッダが分かる。
また、レスポンスタイムを測りたければ、その名もttfb.shというcurlをラップしたコマンドラインツールがある。
この3つさえあれば、事足りそうである。 が、欲が出てきた。
やりたいこと
やりたいことは以下だ。
- 一つのコマンドだけでヘッダもレスポンスタイムも見たい。
- 構造化されたデータで欲しい。というか JSON で欲しい。
- JSON なら
jqで要素を抽出できる。
さらに欲を言えば、
- HTTP/3 にも対応して欲しい(
curlの場合別途ビルドしないといけない)。 - レスポンスの内容が正しく返ってきているかを試験したい。
これらを全てコマンドラインでやりたい。 そして、「Perl のワンライナー」には頼りなくない。
するとrjとtというコマンドが出来た。
加えて jq という JSON をパースしてくれるコマンドも使うと面白いことになった。
rj
rjは以下の機能がある。
- HTTP レスポンスを構造化して JSON で印字。
- ステータス、ヘッダ、プロトコル、それに加えて
timingでレスポンスタイムが分かる。 -AオプションでUser-Agentを指定できる。-uオプションで Basic 認証に対応。-Hオプションでリクエストヘッダの追加。--http3オプションで HTTP/3 に対応。
jq コマンドと組み合わせるとこんな感じ。
rj https://httpbin.org | jq
jq のおかげで色がついている。いい感じだ。
timing で取れる値は以下の通り。
dns_lookuptcp_connectiontls_handshakettfbtotal
TTFB = Time To First Byte をどう定義するか悩んだが
(今回で言うtotalを TTFB と呼ぶこともあるみたい)、
Chrome の DevTools の挙動になるべく近くした。
以下のスクリーンショットの緑色のラインである。
rjはターミナル上で動くコマンドラインなので、watchコマンドとjqコマンドを組み合わせて、
5秒ごとにtimingを確認する、なんてことができる。
パフォーマンスを目視で計測したい時に捗る。
Homebrew の curl は HTTP/3 に対応せず、
別途自分でビルドし直さないといけなくて面倒である。
rjでは--http3コマンドで HTTP/3 を解釈するようにした。
HTTP/3 の場合、そもそも UDP だったりするのでそのままのやり方では timing の値が取れなかった。
ただ、total に関しては正しい値、だと思うので(たぶん) HTTP/2 との比較も出来て参考になる。
t
t コマンドは rj 関係なくとも独立で動く「ターミナル上でテストを行うためのコマンド」である。
スクショを見てもらうのが分かりやすい。
つまり、第一引数と第二引数を比べて、PASS か FAIL を出す。 期待する値と実際の値を比べるユニットテストがターミナル上で書けるわけだ。
これ面白いのは、パイプで値を受け取れるので、
echo '{ "message": "hello" }' | jq -r .message | t hello
ができるのだ。これはつまり
{ "message": "hello" }という JSON 文字列を出力- パイプで受け取り
jqでパース、messageに対応する値を出力 helloをパイプで受けとり、tコマンドで値を比べる
結果、PASS になる。
rj と t と jq
rj と t と jq の 3 つのコマンドを組み合わせると面白い。
rj https://yusukebe.com/ | jq .code | t 200
これで死活監視ができる。
もちろん、ステータス404 を返すページでは FAIL する。
500番台じゃなければ(500 未満なら)、というのも書ける。
rjで受け取ったレスポンスヘッダやレスポンスタイムだったらどれもtコマンドに渡して、テストできてしまう。
rj https://example.com/ | jq -r '.header."x-cache"' | t HIT
と書けば、キャッシュにヒットしているかどうかを試験できる。
rj https://yusukebe.com/ | jq -r .timing.ttfb | t -o '>' 0.1
と書けば、ttfb が 100ms 未満かどうかを試験できる。
rj と t と jq と CI
最後に面白いものをお見せしよう。 この 3 つのコマンドを CI 上で動かして、定期的に実行するのだ。 以下 CircleCI の設定ファイルは確実に動く。 コマンドをインストールして CLI を実行しているだけである。
version: 2.1
jobs:
test:
parameters:
url:
type: string
docker:
- image: cimg/go:1.17.3
steps:
- run:
command: go install github.com/yusukebe/rj/cmd/rj@latest
- run:
command: go install github.com/yusukebe/t/cmd/t@latest
- run:
name: Test << parameters.url >>
command: rj <<parameters.url>> | jq '.code' | t 200
workflows:
test_workflow:
jobs:
- test:
url: https://example.com/
ほらね。
これは簡単な死活監視だけど、例えば
- CDN のキャッシュがヒットしているか
- レスポンスは十分速く返ってきてるか
- ヘッダの内容が急に変わってないか
など「コマンドラインを変えるだけで」継続的に試験出来てしまう。 CI なので当然ながら FAIL すればメールなりで通知することが可能だ。
実際問題、トラベルブックで Fastly を導入した時はすごく役に立った。
レポジトリ
rj と t のレポジトリは以下である。
loading...
loading...
まとめ
JSON をパースするjq、HTTP レスポンスを JSON 化するrj、ターミナル上でユニットテストができるt 。3 つのコマンドを使って、HTTP レスポンスを試験する件について紹介した。あえて、ひとつのアプリケーションに機能を集約しないで、単一機能をもつコマンドをパイプでつないで目的を達成するというアプローチがよい。今でも特に rj コマンドは使うんだけど、これはつまり、コマンドをシンプルにしておいたから末永く使えるんだと思う。ワンライナーもいいけど、これはありだ。コマンドの組み合わせは楽しい。