creating a rust http testing client
This is an example of a rust http test client that i used to run test periodically.
use clap::{Parser, ValueEnum};
use indicatif::{ProgressBar, ProgressStyle};
use reqwest::{Client, Method};
use std::collections::HashMap;
use tokio::time::{sleep, Duration}; // Added tokio sleep
use serde_json::Value;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)]
enum HttpMethod {
Get,
Post,
Put,
}
#[derive(Parser, Debug)]
#[command(author, version, about = "A simple HTTP load tester")]
struct Config {
/// HTTP Method to use
#[arg(short, long, value_enum, default_value_t = HttpMethod::Get)]
method: HttpMethod,
/// Target URL endpoint
#[arg(short, long)]
url: String,
/// Number of tests to run
#[arg(short, long, default_value_t = 10)]
count: u32,
/// Delay between requests in milliseconds
#[arg(short, long, default_value_t = 0)]
delay: u64,
/// JSON payload for POST/PUT requests (e.g., '{"key": "value"}')
#[arg(short, long)]
payload: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::parse();
let client = Client::new();
// Pre-validate JSON payload if it exists
let json_body: Option<Value> = if let Some(ref p) = config.payload {
match serde_json::from_str(p) {
Ok(v) => Some(v),
Err(e) => {
eprintln!("Error: Invalid JSON payload provided: {}", e);
std::process::exit(1);
}
}
} else {
None
};
// Setup Progress Bar
let pb = ProgressBar::new(config.count as u64);
pb.set_style(ProgressStyle::default_bar()
.template("{spinner:.green} [{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} ({eta})")?
.progress_chars("#>-"));
let mut success_count = 0;
let mut error_map: HashMap<u16, u32> = HashMap::new();
for i in 0..config.count {
let method = match config.method {
HttpMethod::Get => Method::GET,
HttpMethod::Post => Method::POST,
HttpMethod::Put => Method::PUT,
};
if i > 0 && config.delay > 0 {
sleep(Duration::from_millis(config.delay)).await;
}
// Build request and attach JSON if present
let mut request_builder = client.request(method, &config.url);
if let Some(ref body) = json_body {
request_builder = request_builder.json(body);
}
match request_builder.send().await {
Ok(response) => {
let status = response.status().as_u16();
if (200..300).contains(&status) {
success_count += 1;
} else {
*error_map.entry(status).or_insert(0) += 1;
}
}
Err(_) => {
// Handle network/connection errors as 0
*error_map.entry(0).or_insert(0) += 1;
}
}
pb.inc(1);
}
pb.finish_with_message("Tests Complete");
// --- Summary Report ---
println!("\n--- Test Summary ---");
println!("Total Requests: {}", config.count);
println!("Success (20x): {}", success_count);
if !error_map.is_empty() {
println!("Failures (grouped by status):");
for (code, count) in error_map {
let label = match code {
0 => "Connection Error".to_string(),
c if (400..500).contains(&c) => format!("{} (Client Error)", c),
c if (500..600).contains(&c) => format!("{} (Server Error)", c),
c => c.to_string(),
};
println!(" - {}: {}", label, count);
}
}
Ok(())
}
Cargo.toml
[package]
name = "http-tester"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.6.1", features = ["derive"] }
indicatif = "0.18.4"
reqwest = { version = "0.13.3", features = ["json"] }
serde_json = "1.0.149"
tokio = { version = "1.52.1", features = ["full"] }
Example code usage
Running GET
cargo run -- --url https://www.google.com --count 10 --delay 1000
cargo run -- --method post --url
https://www.google.com --count 10 --delay 1000
--payload '{"name": "rust-bot", "status": "active"}'
Comments