Rust で簡単な CLI ツールを作成してみました。
CLI ツールを作成するなら、clap が便利
- clap を使えば、簡単に CLI ツールが作成できる
- 見栄えを良くするために ansi_term で色付きの標準出力
Webページの応答時間を監視するCLIツール
今回は、応答時間を監視したい Web ページの URL を引数に持つ CLI ツールを作りました。
使用するクレート
cargo.toml は以下のようになっています。
[dependencies]
ansi_term = "0.12.1"
clap = { version = "3.2.16", features = ["derive"] }
chrono = "0.4.20"
reqwest = "0.11.11"
tokio = { version = "1.20.1", features = ["full"] }
- ansi_term
-
ターミナルでの標準出力時に色をつけるために使用します。
- clap
-
今回の主役。CLIツールの作成を簡単にしてくれるクレート、Rustの定番コマンドラインパーサーです。
- chrono
-
日時を扱うためのクレート。
- reqwest と tokio
-
非同期処理で HTTP リクエストを飛ばすために使用します。
clap でコマンドの引数を定義
derive ベースのマクロを使った記述方法で実装します。
マクロを追加していくことで簡単にコマンドラインの引数パーサーを実装できます。
// name = "monit" でパッケージ名とは異なる名前を定義
// about は cargo.toml の [package] description を出力してくれる
// arg_required_else_help = true で必須の引数なしで実行した場合に --help の内容を出力してくれる
#[derive(Parser, Debug)]
#[clap(name = "monit", version, about, long_about = None, arg_required_else_help = true)]
struct Args {
// 必須の定位引数
/// URL to be monitored for response time
url: String,
// 初期値ありのオプション引数
// value_name = "" で --help の <> の出力内容を変更
// default_value_t で初期値を定義
/// Interval seconds
#[clap(
short,
long,
value_name = "INTERVAL SECONDS",
default_value_t = 30
)]
interval: u64,
// 初期値ありのオプション引数
// 列挙型 Output を出力形式として指定できるように arg_enum を追加
// value_name = "" で --help の <> の出力内容を変更
// default_value で初期値を定義
/// Output type
#[clap(
short,
long,
arg_enum,
value_name = "OUTPUT TYPE",
default_value = "text"
)]
output: Output,
}
// ArgEnum でオプション引数の列挙型にできる
#[derive(Debug, Clone, ArgEnum, Copy)]
enum Output {
Csv,
Json,
Text,
}
初期値を定義する場合、Option型だとコンパイル時にエラーが発生した。
初期値の定義があるためツール実行時の引数の指定は省略可能だけど、あくまで”必須”のオプション引数であるためだと思われます。
–helpの実行結果
–help もしくは 引数 <URL> なしで実行した場合、以下のように出力されます。
monit 0.1.0
Monitor website response time.
USAGE:
monit [OPTIONS] <URL>
ARGS:
<URL> URL to be monitored for response time
OPTIONS:
-h, --help
Print help information
-i, --interval <INTERVAL SECONDS>
Interval seconds [default: 30]
-o, --output <OUTPUT TYPE>
Output type [default: text] [possible values: csv, json, text]
-V, --version
Print version information
deriveベースのサンプルコード
clap のマクロについては Github にサンプルコードがあるので、参考にしてみてください。
reqwest::getのレスポンスが返るまでの時間を計測する
GET の HTTP リクエストを実行しレスポンスが返ってくるまでにかかる時間を Web ページの応答速度として計測していきます。
let start = Instant::now();
let resp = reqwest::get(url).await;
let end = start.elapsed();
応答速度の出力は、下3桁までの秒数としました。
let response_time = format!(
"{}.{:03}",
self.elapsed.as_secs(),
self.elapsed.subsec_nanos() / 1_000_000
);
ansi_termで標準出力に色を付ける
reqwest::StatusCode のステータスコードが200番台の場合は成功として緑色で、それ以外は赤色で標準出力できるようにします。
ステータスコードが200番台かの判定は構造体 StatusCode に定義されている is_success() でおこなっています。
let color_msg = if status_code.is_success() {
Colour::Green.paint(&msg)
} else {
Colour::Red.paint(&msg)
};
loopとsleep
監視を目的とした CLI ツールなので、loop で無限に HTTP リクエストを飛ばすようにします。
loop で無限ループが発生しているので、CLI ツールを停める場合はCtrl + C
で停止させる必要があります。
また、インターバルなしで無限にWebページにアクセスしすぎるのは危険なので、スリープを挟みます。
sleep(Duration::from_secs(interval));
Windows 10では enable_ansi_support() が必要?
Windows 10環境でビルドしてコマンドプロンプト cmd.exe で実行すると、色付きの出力が正しく機能しません。
crates.io の ansi_term のページには以下のように注意書きがあります。
Note for Windows 10 users: On Windows 10, the application must enable ANSI support first:
https://crates.io/crates/ansi_term
let enabled = ansi_term::enable_ansi_support();
ansi_term::enable_ansi_support 関数を呼び出すことで、Windows 10 で ANSI コードのサポートを有効にできるようです。
この関数をMacの開発環境で実行するとコンパイルに失敗します。
Windows環境ではコンパイルする(?) みたいな制御がいるのでしょうか?
まだ解決策が分かっていません…
let _ = ansi_term::enable_ansi_support();
^^^^^^^^^^^^^^^^^^^ not found in `ansi_term`
まとめ
clap を使えば、簡単に CLI ツールを作成することができます。
また、CSVやJSON形式での出力メッセージをformat!
で実装してしました。
serde や csv などのクレートを使用すれば簡単に文字列に変換したり凝ったこともできたりしそうですが、今回の規模のツールであれば不要かなと判断しました。
あの Meta も、CLI ツールの作成は Rust での実装をおすすめしているようです!
ご参考までに;-)
For CLI tools, we recommend Rust. This is a new recommendation for this year.
https://engineering.fb.com/2022/07/27/developer-tools/programming-languages-endorsed-for-server-side-use-at-meta/
コメント