From 6734bc835221ab2df4802aa2f506bde395016d03 Mon Sep 17 00:00:00 2001 From: Luis Schwab Date: Sat, 20 Jun 2026 12:57:12 -0300 Subject: [PATCH] feat(bitreq)!: use `Duration` for `Request::with_timeout` --- bitreq/CHANGELOG.md | 4 ++++ bitreq/src/lib.rs | 12 ++++++++---- bitreq/src/request.rs | 10 +++++----- bitreq/tests/main.rs | 16 ++++++++++------ jsonrpc/src/http/bitreq_http.rs | 24 ++++++------------------ jsonrpc/src/http/bitreq_http_async.rs | 22 +++++----------------- 6 files changed, 38 insertions(+), 50 deletions(-) diff --git a/bitreq/CHANGELOG.md b/bitreq/CHANGELOG.md index 60ade4774..754827e6d 100644 --- a/bitreq/CHANGELOG.md +++ b/bitreq/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased + +* Use `Duration` for `Request::with_timeout` [#642](https://github.com/rust-bitcoin/corepc/pull/642) + # 0.3.7 - 2026-05-28 * Some pipeline fixes in `bitreq` [#584](https://github.com/rust-bitcoin/corepc/pull/584) diff --git a/bitreq/src/lib.rs b/bitreq/src/lib.rs index 1f214dfa3..b0898c548 100644 --- a/bitreq/src/lib.rs +++ b/bitreq/src/lib.rs @@ -99,6 +99,7 @@ //! # #[cfg(feature = "std")] //! # fn main() -> Result<(), Box> { //! # use std::thread; +//! # use std::time::Duration; //! # use tiny_http::{Response, Server}; //! # //! # let server = Server::http("127.0.0.1:0")?; @@ -109,8 +110,9 @@ //! # let _ = request.respond(response); //! # }); //! # +//! # //! # let url = format!("http://{addr}/"); -//! let response = bitreq::get(&url).with_timeout(10).send()?; +//! let response = bitreq::get(&url).with_timeout(Duration::from_secs(10)).send()?; //! assert!(response.as_str()?.contains("")); //! assert_eq!(200, response.status_code); //! assert_eq!("OK", response.reason_phrase); @@ -178,15 +180,17 @@ //! ## Timeouts //! //! To avoid timing out, or limit the request's response time, use -//! `with_timeout(n)` before `send()`. The given value is in seconds. +//! `with_timeout(duration)` before `send()`. //! //! NOTE: There is no timeout by default. //! //! ```no_run //! # #[cfg(feature = "std")] //! # fn main() -> Result<(), Box> { +//! use std::time::Duration; +//! //! let response = bitreq::post("http://example.com") -//! .with_timeout(10) +//! .with_timeout(Duration::from_secs(10)) //! .send()?; //! # Ok(()) } //! # #[cfg(not(feature = "std"))] @@ -226,7 +230,7 @@ //! - Use [`with_timeout`](struct.Request.html#method.with_timeout) on //! your request to set the timeout per-request like so: //! ```text,ignore -//! bitreq::get("/").with_timeout(8).send(); +//! bitreq::get("/").with_timeout(Duration::from_secs(8)).send(); //! ``` //! - Set the environment variable `BITREQ_TIMEOUT` to the desired //! amount of seconds until timeout. Ie. if you have a program called diff --git a/bitreq/src/request.rs b/bitreq/src/request.rs index d39d6d89a..e0468fb07 100644 --- a/bitreq/src/request.rs +++ b/bitreq/src/request.rs @@ -89,7 +89,7 @@ pub struct Request { params: Vec<(String, String)>, headers: BTreeMap, body: Option>, - timeout: Option, + timeout: Option, pub(crate) pipelining: bool, pub(crate) max_headers_size: Option, pub(crate) max_status_line_len: Option, @@ -185,8 +185,8 @@ impl Request { } } - /// Sets the request timeout in seconds. - pub fn with_timeout(mut self, timeout: u64) -> Request { + /// Sets the request timeout. + pub fn with_timeout(mut self, timeout: Duration) -> Request { self.timeout = Some(timeout); self } @@ -403,10 +403,10 @@ impl ParsedRequest { } let timeout = config.timeout.or_else(|| match env::var("BITREQ_TIMEOUT") { - Ok(t) => t.parse::().ok(), + Ok(t) => t.parse::().ok().map(Duration::from_secs), Err(_) => None, }); - let timeout_at = timeout.map(|t| Instant::now() + Duration::from_secs(t)); + let timeout_at = timeout.map(|t| Instant::now() + t); Ok(ParsedRequest { url, redirects: Vec::new(), config, timeout_at }) } diff --git a/bitreq/tests/main.rs b/bitreq/tests/main.rs index 8d357f354..59f95c24f 100644 --- a/bitreq/tests/main.rs +++ b/bitreq/tests/main.rs @@ -4,6 +4,7 @@ extern crate bitreq; mod setup; use std::io; +use std::time::Duration; use self::setup::*; @@ -35,7 +36,8 @@ async fn test_json_using_serde() { #[tokio::test] async fn test_timeout_too_low() { setup(); - let request = bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(1); + let request = + bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(Duration::from_secs(1)); let result = maybe_make_request(request, true).await; assert!(result.is_err()); } @@ -43,7 +45,8 @@ async fn test_timeout_too_low() { #[tokio::test] async fn test_timeout_high_enough() { setup(); - let request = bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(3); + let request = + bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(Duration::from_secs(3)); let result = maybe_make_request(request, true).await.unwrap(); assert_eq!(result.as_str().unwrap(), "j: Q"); } @@ -178,8 +181,8 @@ async fn test_patch() { #[tokio::test] async fn tcp_connect_timeout() { let _listener = std::net::TcpListener::bind("127.0.0.1:32162").unwrap(); - let request = - bitreq::Request::new(bitreq::Method::Get, "http://127.0.0.1:32162").with_timeout(1); + let request = bitreq::Request::new(bitreq::Method::Get, "http://127.0.0.1:32162") + .with_timeout(Duration::from_secs(1)); let resp = maybe_make_request(request, true).await; assert!(resp.is_err()); if let Some(bitreq::Error::IoError(err)) = resp.err() { @@ -254,8 +257,9 @@ async fn test_future_drop_doesnt_hang() { // Here our cancellation detection should kick in, allowing the second request to open a fresh // connection and get a response immediately. let timesout = client.send_async(bitreq::get("http://example.com").with_pipelining()); - let request = - client.send_async(bitreq::get("http://example.com").with_timeout(10).with_pipelining()); + let request = client.send_async( + bitreq::get("http://example.com").with_timeout(Duration::from_secs(10)).with_pipelining(), + ); let start = Instant::now(); let (timedout, response) = diff --git a/jsonrpc/src/http/bitreq_http.rs b/jsonrpc/src/http/bitreq_http.rs index 717508ed1..2804ee6fc 100644 --- a/jsonrpc/src/http/bitreq_http.rs +++ b/jsonrpc/src/http/bitreq_http.rs @@ -19,16 +19,16 @@ use crate::{Request, Response}; const DEFAULT_URL: &str = "http://localhost"; const DEFAULT_PORT: u16 = 8332; // the default RPC port for bitcoind. #[cfg(not(jsonrpc_fuzz))] -const DEFAULT_TIMEOUT_SECONDS: u64 = 15; +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); #[cfg(jsonrpc_fuzz)] -const DEFAULT_TIMEOUT_SECONDS: u64 = 1; +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1); /// An HTTP transport that uses [`bitreq`] and is useful for running a bitcoind RPC client. #[derive(Clone, Debug)] pub struct BitreqHttpTransport { /// URL of the RPC server. url: String, - /// Timeout only supports second granularity. + /// Timeout to use for HTTP requests. timeout: Duration, /// The value of the `Authorization` HTTP header, i.e., a base64 encoding of 'user:password'. basic_auth: Option, @@ -38,7 +38,7 @@ impl Default for BitreqHttpTransport { fn default() -> Self { BitreqHttpTransport { url: format!("{}:{}", DEFAULT_URL, DEFAULT_PORT), - timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS), + timeout: DEFAULT_TIMEOUT, basic_auth: None, } } @@ -51,29 +51,17 @@ impl BitreqHttpTransport { /// Returns a builder for [`BitreqHttpTransport`]. pub fn builder() -> Builder { Builder::new() } - /// Returns the timeout in whole seconds, rounding positive sub-second values up to one. - fn timeout_secs(&self) -> u64 { - let secs = self.timeout.as_secs(); - if secs == 0 && self.timeout > Duration::from_secs(0) { - 1 - } else { - secs - } - } - fn request(&self, req: impl serde::Serialize) -> Result where R: for<'a> serde::de::Deserialize<'a>, { - let timeout_secs = self.timeout_secs(); - let req = match &self.basic_auth { Some(auth) => bitreq::Request::new(bitreq::Method::Post, &self.url) - .with_timeout(timeout_secs) + .with_timeout(self.timeout) .with_header("Authorization", auth) .with_json(&req)?, None => bitreq::Request::new(bitreq::Method::Post, &self.url) - .with_timeout(timeout_secs) + .with_timeout(self.timeout) .with_json(&req)?, }; diff --git a/jsonrpc/src/http/bitreq_http_async.rs b/jsonrpc/src/http/bitreq_http_async.rs index 0f47baeee..18938a537 100644 --- a/jsonrpc/src/http/bitreq_http_async.rs +++ b/jsonrpc/src/http/bitreq_http_async.rs @@ -14,14 +14,14 @@ use crate::{Request, Response}; const DEFAULT_URL: &str = "http://localhost"; const DEFAULT_PORT: u16 = 8332; // the default RPC port for bitcoind. -const DEFAULT_TIMEOUT_SECONDS: u64 = 15; +const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15); /// An HTTP transport that uses [`bitreq`] and is useful for running a bitcoind RPC client. #[derive(Clone, Debug)] pub struct BitreqHttpTransport { /// URL of the RPC server. url: String, - /// Timeout only supports second granularity. + /// Timeout to use for HTTP requests. timeout: Duration, /// The value of the `Authorization` HTTP header, i.e., a base64 encoding of 'user:password'. basic_auth: Option, @@ -31,7 +31,7 @@ impl Default for BitreqHttpTransport { fn default() -> Self { BitreqHttpTransport { url: format!("{}:{}", DEFAULT_URL, DEFAULT_PORT), - timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS), + timeout: DEFAULT_TIMEOUT, basic_auth: None, } } @@ -44,16 +44,6 @@ impl BitreqHttpTransport { /// Returns a builder for [`BitreqHttpTransport`]. pub fn builder() -> Builder { Builder::new() } - /// Returns the timeout in whole seconds, rounding positive sub-second values up to one. - fn timeout_secs(&self) -> u64 { - let secs = self.timeout.as_secs(); - if secs == 0 && self.timeout > Duration::from_secs(0) { - 1 - } else { - secs - } - } - async fn request(&self, req: impl serde::Serialize) -> Result where R: for<'a> serde::de::Deserialize<'a>, @@ -65,15 +55,13 @@ impl BitreqHttpTransport { where R: for<'a> serde::de::Deserialize<'a>, { - let timeout_secs = self.timeout_secs(); - let req = match &self.basic_auth { Some(auth) => bitreq::Request::new(bitreq::Method::Post, &self.url) - .with_timeout(timeout_secs) + .with_timeout(self.timeout) .with_header("Authorization", auth) .with_json(&req)?, None => bitreq::Request::new(bitreq::Method::Post, &self.url) - .with_timeout(timeout_secs) + .with_timeout(self.timeout) .with_json(&req)?, };