From b44c37091dd325020e681ed431c34a0976abafe8 Mon Sep 17 00:00:00 2001 From: Harshil Date: Mon, 22 Jun 2026 19:04:27 +0530 Subject: [PATCH] feat: added timeout to stop indefinite requests --- src/builtin.rs | 6 ++++++ src/executor.rs | 3 ++- src/help.rs | 3 +++ src/main.rs | 20 +++++++++++++++----- src/parser.rs | 12 +++++++++++- src/runner.rs | 12 +++++++++++- src/state.rs | 11 +++++++++++ 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/builtin.rs b/src/builtin.rs index 37c14cd..7919cd2 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -22,6 +22,7 @@ pub enum Builtin { Vars, Requests, Clear, + Timeout(u64), } pub enum ControlFlow { @@ -97,6 +98,11 @@ pub fn handle( } } + Builtin::Timeout(secs) => { + ctx.set_timeout(secs); + println!("Request timeout set to {secs}s"); + } + Builtin::Clear => { ctx.clear(); println!("Session state cleared"); diff --git a/src/executor.rs b/src/executor.rs index c5e2de6..c51b6b7 100644 --- a/src/executor.rs +++ b/src/executor.rs @@ -53,7 +53,8 @@ pub fn execute(req: Request, ctx: &ShellState) -> Result { let base_url = ctx.get_base_url(); let global_headers = ctx.get_headers(); - let response = fetch(&req, base_url, global_headers); + let timeout_secs = ctx.get_timeout(); + let response = fetch(&req, base_url, global_headers, timeout_secs); match response { Ok((res, duration)) => Ok(display_response(res, duration)), diff --git a/src/help.rs b/src/help.rs index 8070d41..874bf72 100644 --- a/src/help.rs +++ b/src/help.rs @@ -10,6 +10,7 @@ pub fn help_text() -> String { {}: {} Show help {} Show version + {} Set request timeout {} {}: {} @@ -37,6 +38,7 @@ pub fn help_text() -> String { headers history rerun + timeout clear help exit @@ -48,6 +50,7 @@ pub fn help_text() -> String { "Options".yellow().bold(), "--help, -h".green().bold(), "--version, -v".green().bold(), + "--timeout ".green().bold(), "─".repeat(50).dimmed(), "Requests".yellow().bold(), "Method".green().bold(), diff --git a/src/main.rs b/src/main.rs index 1b1671b..fd195db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,7 +20,7 @@ fn history_path() -> PathBuf { home.join(".reqsh_history") } -fn shell_loop() { +fn run_repl(ctx: &mut ShellState) { let config = Config::builder() .history_ignore_space(true) .completion_type(CompletionType::List) @@ -28,7 +28,6 @@ fn shell_loop() { .build(); let hist_path = history_path(); - let mut ctx = ShellState::new(); let mut rl = Editor::with_config(config).unwrap(); rl.set_helper(Some(ShellHelper)); rl.load_history(&hist_path).unwrap_or_default(); @@ -54,7 +53,7 @@ fn shell_loop() { match parse(raw) { Ok(parsed) => match parsed { - Parsed::Builtin(cmd) => match handle(cmd, &mut ctx, rl.history()) { + Parsed::Builtin(cmd) => match handle(cmd, ctx, rl.history()) { Ok(ControlFlow::Continue) => {} Ok(ControlFlow::Exit) => { break; @@ -66,7 +65,7 @@ fn shell_loop() { Parsed::Request(req) => { ctx.set_last_request(req.clone()); - match execute(req, &ctx) { + match execute(req, ctx) { Ok(res) => { println!("{}", res); } @@ -150,7 +149,8 @@ fn main() { match args.as_slice() { [] => { - shell_loop(); + let mut ctx = ShellState::new(); + run_repl(&mut ctx); } [arg] if arg == "--help" || arg == "-h" => { @@ -161,6 +161,16 @@ fn main() { println!("reqsh {}", VERSION); } + [arg, value] if arg == "--timeout" => { + let secs: u64 = value.parse().unwrap_or_else(|_| { + eprintln!("Invalid timeout: {value}"); + std::process::exit(1); + }); + let mut ctx = ShellState::new(); + ctx.set_timeout(secs); + run_repl(&mut ctx); + } + [unknown] => { eprintln!("Unknown argument: {}", unknown); eprintln!("Try 'reqsh --help'"); diff --git a/src/parser.rs b/src/parser.rs index b7eb1ae..47c8b04 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -25,7 +25,7 @@ pub fn parse(input: String) -> Result { } "base" | "set" | "unset" | "header" | "headers" | "vars" | "requests" | "save" | "run" - | "help" | "history" | "rerun" | "clear" => { + | "help" | "history" | "rerun" | "clear" | "timeout" => { let result = parse_builtin(input)?; Ok(Parsed::Builtin(result)) } @@ -175,6 +175,16 @@ fn parse_builtin(line: String) -> Result { } "help" => Ok(Builtin::Help), "clear" => Ok(Builtin::Clear), + "timeout" => { + if tokens.len() != 2 { + Err("usage: timeout ".to_string()) + } else { + let secs = tokens[1] + .parse::() + .map_err(|e| format!("invalid timeout: {e}"))?; + Ok(Builtin::Timeout(secs)) + } + } "history" => Ok(Builtin::History), "rerun" => { if tokens.len() != 2 { diff --git a/src/runner.rs b/src/runner.rs index 84e8723..eb9f729 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -13,9 +13,19 @@ pub fn fetch( request: &Request, base_url: Option<&str>, global_headers: &HashMap, + timeout_secs: Option, ) -> Result<(Response, Duration), String> { // Client - let client = Client::new(); + let client = match timeout_secs { + Some(secs) => Client::builder() + .timeout(Duration::from_secs(secs)) + .build() + .map_err(|e| format!("failed to build client: {e}"))?, + None => Client::builder() + .timeout(Duration::from_secs(30)) + .build() + .map_err(|e| format!("failed to build client: {e}"))?, + }; // Url Constructor let full_url = if request.path.starts_with("http://") || request.path.starts_with("https://") { diff --git a/src/state.rs b/src/state.rs index e556997..7a909b8 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,6 +8,7 @@ pub struct ShellState { variables: HashMap, last_request: Option, saved_requests: HashMap, + timeout_secs: Option, } impl Default for ShellState { @@ -24,6 +25,7 @@ impl ShellState { variables: HashMap::new(), last_request: None, saved_requests: HashMap::new(), + timeout_secs: None, } } @@ -85,12 +87,21 @@ impl ShellState { self.variables.remove(name); } + pub fn get_timeout(&self) -> Option { + self.timeout_secs + } + + pub fn set_timeout(&mut self, secs: u64) { + self.timeout_secs = Some(secs); + } + pub fn clear(&mut self) { self.base_url = None; self.headers.clear(); self.variables.clear(); self.last_request = None; self.saved_requests.clear(); + self.timeout_secs = None; } }