Sniff and replay HTTP requests and responses — perfect for mocking APIs during testing.

use async mutex (tokio mutex) instead of the ordinary mutex

+20 -18
+1 -1
Cargo.lock
··· 1317 1317 1318 1318 [[package]] 1319 1319 name = "replay" 1320 - version = "0.1.1" 1320 + version = "0.1.2" 1321 1321 dependencies = [ 1322 1322 "actix-web", 1323 1323 "clap",
+1 -1
Cargo.toml
··· 1 1 [package] 2 2 name = "replay" 3 - version = "0.1.1" 3 + version = "0.1.2" 4 4 edition = "2024" 5 5 description = "A simple HTTP request replay tool" 6 6 license = "Apache-2.0"
+3 -2
src/main.rs
··· 1 1 2 - use std::sync::{Arc, Mutex}; 2 + use std::sync::{Arc}; 3 3 4 4 use clap::{Arg, Command}; 5 5 use owo_colors::OwoColorize; 6 6 use proxy::start_server; 7 + use tokio::sync::Mutex; 7 8 8 9 pub mod proxy; 9 10 pub mod replay; ··· 50 51 let logs = store::load_logs_from_file(proxy::PROXY_LOG_FILE)?; 51 52 let logs = Arc::new(Mutex::new(logs)); 52 53 let listen = matches.get_one::<String>("listen").unwrap(); 53 - println!("Loaded {} mocks from {}", logs.lock().unwrap().len().magenta(), proxy::PROXY_LOG_FILE.magenta()); 54 + println!("Loaded {} mocks from {}", logs.lock().await.len().magenta(), proxy::PROXY_LOG_FILE.magenta()); 54 55 println!("Replay server is running on {}", listen.magenta()); 55 56 replay::start_replay_server(logs, listen).await?; 56 57 return Ok(());
+7 -8
src/proxy.rs
··· 1 - use std::{process::exit, sync::{Arc, Mutex}, thread, time::{SystemTime, UNIX_EPOCH}}; 1 + use std::{process::exit, sync::Arc, thread, time::{SystemTime, UNIX_EPOCH}}; 2 2 3 3 use http_body_util::{BodyExt, Full}; 4 4 use hyper::{body::{Buf, Bytes, Incoming}, server::conn::http1, Request, Response}; 5 5 use owo_colors::OwoColorize; 6 6 use serde::{Deserialize, Serialize}; 7 - use tokio::net::TcpListener; 7 + use tokio::{net::TcpListener, sync::Mutex}; 8 8 use hyper_util::rt::TokioIo; 9 9 10 10 use crate::{replay::start_replay_server, store::{save_logs_to_file, LogStore}}; ··· 46 46 tokio::spawn(async move { 47 47 loop { 48 48 tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; 49 - save_logs_to_file(&logs_for_saving, PROXY_LOG_FILE).unwrap_or_else(|e| { 50 - eprintln!("Error saving logs: {}", e); 51 - }); 49 + save_logs_to_file(&logs_for_saving, PROXY_LOG_FILE).await 50 + .unwrap_or_else(|e| eprintln!("Error saving logs to file: {}", e)); 52 51 } 53 52 }); 54 53 ··· 94 93 }); 95 94 96 95 if let Err(err) = http1::Builder::new() 97 - .keep_alive(false) // Disable keep-alive 98 - .max_buf_size(30 * 1024 * 1024) // 30 MB 96 + .keep_alive(false) 97 + .max_buf_size(30 * 1024 * 1024) 99 98 .serve_connection(io, service) 100 99 .await 101 100 { ··· 262 261 }; 263 262 264 263 { 265 - let mut logs_guard = logs.lock().unwrap(); 264 + let mut logs_guard = logs.lock().await; 266 265 if !logs_guard.iter() 267 266 .any(|log| 268 267 log.request.method == log_entry.request.method &&
+3 -3
src/replay.rs
··· 6 6 7 7 pub async fn start_replay_server(logs: LogStore, bind_address: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 8 8 if let Ok(file_logs) = load_logs_from_file(PROXY_LOG_FILE) { 9 - let mut logs_guard = logs.lock().unwrap(); 9 + let mut logs_guard = logs.lock().await; 10 10 for log in file_logs { 11 11 logs_guard.push(log); 12 12 } ··· 34 34 println!("Replay server received request: {}", key.magenta()); 35 35 36 36 let response = { 37 - let logs_guard = logs.lock().unwrap(); 37 + let logs_guard = logs.lock().await; 38 38 logs_guard.iter() 39 39 .find(|log| { 40 40 let log_key = build_request_key( ··· 75 75 } 76 76 77 77 async fn list_requests(logs: web::Data<LogStore>) -> impl Responder { 78 - let logs_guard = logs.lock().unwrap(); 78 + let logs_guard = logs.lock().await; 79 79 let requests: Vec<_> = logs_guard.iter().map(|log| { 80 80 let key = build_request_key( 81 81 &log.request.method,
+5 -3
src/store.rs
··· 2 2 fs::{File, OpenOptions}, 3 3 io::Write, 4 4 path::Path, 5 - sync::{Arc, Mutex}, 5 + sync::Arc, 6 6 }; 7 + 8 + use tokio::sync::Mutex; 7 9 8 10 use crate::proxy::ProxyLog; 9 11 10 12 pub type LogStore = Arc<Mutex<Vec<ProxyLog>>>; 11 13 12 - pub fn save_logs_to_file( 14 + pub async fn save_logs_to_file( 13 15 logs: &LogStore, 14 16 filename: &str, 15 17 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { 16 - let logs_guard = logs.lock().unwrap(); 18 + let logs_guard = logs.lock().await; 17 19 18 20 if logs_guard.is_empty() { 19 21 return Ok(());