🐈

HTTPステータスを猫と表示するCLIツール「httpcat」

に公開

はじめに

HTTPリクエストを送信してステータスコードを確認する――開発者にとって日常的なタスクです。curlやwgetを使うのも良いですが、せっかくなら楽しく確認したいと思いませんか?

今回は、HTTPリクエストを送信し、レスポンスのステータスコードに応じた猫画像をターミナルに表示するCLIツール「httpcat」をRustで開発しました。この記事では、その実装過程と技術的なポイントを紹介します。

httpcatとは

httpcatは、指定したURLにHTTPリクエストを送信し、http.catから取得した猫画像をターミナルに直接表示するCLIツールです。ステータスコードに応じた猫画像が表示されます(例: 200 OK、404 Not Found、500 Internal Server Errorなど)。

使い方

httpcatの実行例
基本的な使い方はシンプルです:

$ httpcat https://example.com

🌐 Requesting: https://example.com

⏳ Loading...

✅ 200 OK

🐱 Here's your cat:

[ターミナルに猫画像が表示される]

⏱️  Response time: 234ms

主な機能

  • HTTPリクエストの送信(GET/POST/PUT/DELETE等に対応)
  • ステータスコードに応じた猫画像のターミナル表示
  • ASCII Artモードでの画像表示
  • レスポンスヘッダー・ボディの表示
  • カスタムヘッダーの追加
  • レスポンス時間の計測

技術スタック

このプロジェクトでは以下のクレートを使用しました:

  • reqwest: HTTP通信を担当
  • tokio: 非同期ランタイム
  • image: 画像処理
  • viuer: ターミナルでの画像表示
  • clap: CLIの引数パーサー
  • anyhow: エラーハンドリング
  • colored: カラー出力

実装のポイント

1. モジュール構成

機能ごとにモジュールを分割し、保守性を高めました:

src/
├── main.rs      # エントリーポイント
├── cli.rs       # CLI引数の定義
├── http.rs      # HTTPリクエスト処理
├── cat.rs       # 猫画像の取得・表示
├── display.rs   # ターミナル出力
└── ascii.rs     # ASCII Art変換

2. HTTPリクエストの実装

reqwestのブロッキングクライアントを使い、シンプルかつ堅牢なHTTP通信を実装しました:

pub fn send_request(
    url: &str,
    method: &str,
    request_headers: &[String],
) -> Result<HttpResponse> {
    let client = Client::builder()
        .timeout(Duration::from_secs(30))
        .build()
        .context("Failed to create HTTP client")?;

    let start = Instant::now();

    let response = match method.to_uppercase().as_str() {
        "GET" => client.get(url),
        "POST" => client.post(url),
        "PUT" => client.put(url),
        "DELETE" => client.delete(url),
        // ... その他のメソッド
        _ => anyhow::bail!("Unsupported HTTP method: {}", method),
    }
    .headers(headers)
    .send()
    .context("Failed to send HTTP request")?;

    let duration = start.elapsed();
    let status = response.status().as_u16();
    let body = response.text().unwrap_or_default();

    Ok(HttpResponse {
        status,
        status_text,
        headers: response_headers,
        body,
        duration,
    })
}

レスポンス時間を計測するため、Instant::now()でリクエスト前の時刻を記録し、レスポンス取得後に経過時間を計算しています。

3. ターミナルでの画像表示

viuerクレートを使うことで、ターミナルに直接画像を表示できます:

pub fn display_image(img: &DynamicImage, width: u32) -> Result<()> {
    let config = viuer::Config {
        width: Some(width),
        absolute_offset: false,
        ..Default::default()
    };

    viuer::print(img, &config)
        .context("Failed to display image in terminal")?;

    Ok(())
}

画像の幅を指定できるため、ターミナルのサイズに合わせた表示が可能です。

4. ASCII Art変換機能

--asciiオプションを使うと、画像をASCII Artに変換して表示できます:

pub fn image_to_ascii(img: &DynamicImage, width: u32) -> String {
    let aspect_ratio = img.height() as f32 / img.width() as f32;
    let height = (width as f32 * aspect_ratio * 0.5) as u32;

    let resized = img.resize_exact(
        width,
        height,
        image::imageops::FilterType::Lanczos3,
    );

    let gray = resized.to_luma8();
    let mut ascii_art = String::new();

    for y in 0..height {
        for x in 0..width {
            let pixel = gray.get_pixel(x, y)[0];
            let char_index = (pixel as usize * (ASCII_CHARS.len() - 1)) / 255;
            ascii_art.push(ASCII_CHARS[char_index]);
        }
        ascii_art.push('\n');
    }

    ascii_art
}

画像をグレースケールに変換し、各ピクセルの明度に応じてASCII文字を割り当てています。

5. ステータスコードに応じた色分け

coloredクレートを使い、ステータスコードごとに視覚的に分かりやすい出力を実装しました:

pub fn print_response_status(response: &HttpResponse) {
    let colored_status = if response.status >= 200 && response.status < 300 {
        format!("✅ {}", response.status_text).green()
    } else if response.status >= 300 && response.status < 400 {
        format!("➡️  {}", response.status_text).yellow()
    } else if response.status >= 400 && response.status < 500 {
        format!("❌ {}", response.status_text).red()
    } else if response.status >= 500 {
        format!("💥 {}", response.status_text).bright_red()
    } else {
        format!("ℹ️  {}", response.status_text).white()
    };

    println!("{}\n", colored_status);
}

成功は緑、クライアントエラーは赤、サーバーエラーは明るい赤で表示することで、一目でステータスを把握できます。

6. CLIインターフェース

clapのderiveマクロを使い、型安全なCLIを実装しました:

#[derive(Parser, Debug)]
#[command(name = "httpcat")]
#[command(about = "HTTP request tool with cat images based on status codes")]
pub struct Cli {
    /// Target URL to send HTTP request
    pub url: String,

    /// HTTP method to use
    #[arg(short = 'X', long, default_value = "GET")]
    pub method: String,

    /// Image width in terminal
    #[arg(long, default_value_t = 60)]
    pub size: u32,

    /// Display image as ASCII art
    #[arg(long)]
    pub ascii: bool,

    /// Don't display cat image
    #[arg(long)]
    pub no_image: bool,

    /// Show response headers
    #[arg(long)]
    pub headers: bool,

    /// Show response body
    #[arg(long)]
    pub body: bool,

    /// Add request header (can be used multiple times)
    #[arg(short = 'H', long = "header")]
    pub request_headers: Vec<String>,
}

使用例

POSTリクエストの送信

httpcat -X POST https://api.example.com/data

カスタムヘッダー付きリクエスト

httpcat -H "Authorization: Bearer token123" https://api.example.com

レスポンス詳細の表示

httpcat --headers --body https://example.com

ASCII Artモード

httpcat --ascii https://example.com

ステータス確認のみ(画像なし)

httpcat --no-image https://example.com

以下のコマンドで本コマンドをインストールできます:

cargo install httpcat

まとめ

「猫画像でHTTPステータスを確認する」というコンセプトで、実装を通じてRustの実践的な技術を学べました。

APIのデバッグやヘルスチェックの際、ぜひhttpcatを使ってみてください。きっと猫たちが開発を楽しくしてくれるはずです!

$ httpcat https://api.github.com
🌐 Requesting: https://api.github.com
⏳ Loading...
✅ 200 OK
🐱 Here's your cat:
[猫の画像]
⏱️  Response time: 123ms

インストール方法

httpcatはcrates.ioで公開されており、cargoを使って簡単にインストールできます:

$ cargo install httpcat

インストール後は、すぐに使い始められます:

$ httpcat https://zenn.dev/justhiro/articles/7cb145b58bffc0

リンク

Discussion