This is a simple HTTP server written in Rust using the hyper library. It includes a basic load testing utility to benchmark the server's performance.
- Asynchronous HTTP Server: Built with
hyper, a fast and correct HTTP library for Rust. - Metrics: Tracks the number of in-flight and completed requests.
- Load Tester: A simple utility to send a large number of concurrent requests to the server.
- Rust and Cargo installed on your system.
- Clone the repository:
git clone https://github.com/rferrari/http-crab.git cd http-server - Run the server:
The server will start on
cargo run
http://127.0.0.1:3000.
- In a separate terminal, run the load test:
This will send a large number of concurrent requests to the server and report when the test is complete.
cargo run --bin load_test
use hyper::{
service::{make_service_fn, service_fn},
Body, Request, Response, Server,
};
use std::{convert::Infallible, net::SocketAddr};
use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use tracing::info;
#[derive(Clone)]
struct Metrics {
in_flight: Arc<AtomicUsize>,
completed: Arc<AtomicU64>,
}
async fn handler(
_req: Request<Body>,
metrics: Metrics,
) -> Result<Response<Body>, Infallible> {
// request starts
metrics.in_flight.fetch_add(1, Ordering::Relaxed);
// simulate work (I/O, CPU, upstream call, etc.)
tokio::time::sleep(std::time::Duration::from_millis(5)).await;
let response = Response::new(Body::from("ok"));
// request ends
metrics.in_flight.fetch_sub(1, Ordering::Relaxed);
metrics.completed.fetch_add(1, Ordering::Relaxed);
Ok(response)
}
#[tokio::main]
async fn main() {
let metrics = Metrics {
in_flight: Arc::new(AtomicUsize::new(0)),
completed: Arc::new(AtomicU64::new(0)),
};
let metrics_clone = metrics.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
let mut last_completed = 0u64;
loop {
interval.tick().await;
let completed = metrics_clone.completed.load(Ordering::Relaxed);
let in_flight = metrics_clone.in_flight.load(Ordering::Relaxed);
let rps = completed - last_completed;
last_completed = completed;
tracing::info!(
rps,
in_flight,
total_completed = completed,
"server load"
);
}
});
tracing_subscriber::fmt::init();
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
let make_svc = make_service_fn(move |_| {
let metrics = metrics.clone();
async move {
Ok::<_, Infallible>(service_fn(move |req| {
handler(req, metrics.clone())
}))
}
});
let server = Server::bind(&addr).serve(make_svc);
info!("listening on http://{}", addr);
if let Err(e) = server.await {
eprintln!("server error: {}", e);
}
}use hyper::{Client, Uri};
use std::sync::Arc;
use tokio::task;
use tracing::info;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let client = Arc::new(Client::new());
let uri: Uri = "http://127.0.0.1:3000".parse().unwrap();
let concurrency = 500;
let requests_per_task = 100;
info!(
concurrency,
requests_per_task,
"starting load test"
);
let mut handles = Vec::new();
for _ in 0..concurrency {
let client = client.clone();
let uri = uri.clone();
handles.push(task::spawn(async move {
for _ in 0..requests_per_task {
let _ = client.get(uri.clone()).await;
}
}));
}
for h in handles {
let _ = h.await;
}
info!("load test completed");
}