天然パーマです。

rjとtとjqコマンドでHTTPレスポンスを試験する

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 のワンライナー」には頼りなくない。

するとrjtというコマンドが出来た。 加えて 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_lookup
  • tcp_connection
  • tls_handshake
  • ttfb
  • total

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 になる。

rjtjq

rjtjq の 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 未満かどうかを試験できる。

rjtjq と 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/[email protected]
      - run:
          command: go install github.com/yusukebe/t/cmd/[email protected]
      - 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 を導入した時はすごく役に立った。

レポジトリ

rjt のレポジトリは以下である。

まとめ

JSON をパースするjq、HTTP レスポンスを JSON 化するrj、ターミナル上でユニットテストができるt 。3 つのコマンドを使って、HTTP レスポンスを試験する件について紹介した。あえて、ひとつのアプリケーションに機能を集約しないで、単一機能をもつコマンドをパイプでつないで目的を達成するというアプローチがよい。今でも特に rj コマンドは使うんだけど、これはつまり、コマンドをシンプルにしておいたから末永く使えるんだと思う。ワンライナーもいいけど、これはありだ。コマンドの組み合わせは楽しい。