Skip to content

Commit 6734bc8

Browse files
committed
feat(bitreq)!: use Duration for Request::with_timeout
1 parent bd129f4 commit 6734bc8

6 files changed

Lines changed: 38 additions & 50 deletions

File tree

bitreq/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# Unreleased
2+
3+
* Use `Duration` for `Request::with_timeout` [#642](https://github.com/rust-bitcoin/corepc/pull/642)
4+
15
# 0.3.7 - 2026-05-28
26

37
* Some pipeline fixes in `bitreq` [#584](https://github.com/rust-bitcoin/corepc/pull/584)

bitreq/src/lib.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
//! # #[cfg(feature = "std")]
100100
//! # fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
101101
//! # use std::thread;
102+
//! # use std::time::Duration;
102103
//! # use tiny_http::{Response, Server};
103104
//! #
104105
//! # let server = Server::http("127.0.0.1:0")?;
@@ -109,8 +110,9 @@
109110
//! # let _ = request.respond(response);
110111
//! # });
111112
//! #
113+
//! #
112114
//! # let url = format!("http://{addr}/");
113-
//! let response = bitreq::get(&url).with_timeout(10).send()?;
115+
//! let response = bitreq::get(&url).with_timeout(Duration::from_secs(10)).send()?;
114116
//! assert!(response.as_str()?.contains("</html>"));
115117
//! assert_eq!(200, response.status_code);
116118
//! assert_eq!("OK", response.reason_phrase);
@@ -178,15 +180,17 @@
178180
//! ## Timeouts
179181
//!
180182
//! To avoid timing out, or limit the request's response time, use
181-
//! `with_timeout(n)` before `send()`. The given value is in seconds.
183+
//! `with_timeout(duration)` before `send()`.
182184
//!
183185
//! NOTE: There is no timeout by default.
184186
//!
185187
//! ```no_run
186188
//! # #[cfg(feature = "std")]
187189
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
190+
//! use std::time::Duration;
191+
//!
188192
//! let response = bitreq::post("http://example.com")
189-
//! .with_timeout(10)
193+
//! .with_timeout(Duration::from_secs(10))
190194
//! .send()?;
191195
//! # Ok(()) }
192196
//! # #[cfg(not(feature = "std"))]
@@ -226,7 +230,7 @@
226230
//! - Use [`with_timeout`](struct.Request.html#method.with_timeout) on
227231
//! your request to set the timeout per-request like so:
228232
//! ```text,ignore
229-
//! bitreq::get("/").with_timeout(8).send();
233+
//! bitreq::get("/").with_timeout(Duration::from_secs(8)).send();
230234
//! ```
231235
//! - Set the environment variable `BITREQ_TIMEOUT` to the desired
232236
//! amount of seconds until timeout. Ie. if you have a program called

bitreq/src/request.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub struct Request {
8989
params: Vec<(String, String)>,
9090
headers: BTreeMap<String, String>,
9191
body: Option<Vec<u8>>,
92-
timeout: Option<u64>,
92+
timeout: Option<Duration>,
9393
pub(crate) pipelining: bool,
9494
pub(crate) max_headers_size: Option<usize>,
9595
pub(crate) max_status_line_len: Option<usize>,
@@ -185,8 +185,8 @@ impl Request {
185185
}
186186
}
187187

188-
/// Sets the request timeout in seconds.
189-
pub fn with_timeout(mut self, timeout: u64) -> Request {
188+
/// Sets the request timeout.
189+
pub fn with_timeout(mut self, timeout: Duration) -> Request {
190190
self.timeout = Some(timeout);
191191
self
192192
}
@@ -403,10 +403,10 @@ impl ParsedRequest {
403403
}
404404

405405
let timeout = config.timeout.or_else(|| match env::var("BITREQ_TIMEOUT") {
406-
Ok(t) => t.parse::<u64>().ok(),
406+
Ok(t) => t.parse::<u64>().ok().map(Duration::from_secs),
407407
Err(_) => None,
408408
});
409-
let timeout_at = timeout.map(|t| Instant::now() + Duration::from_secs(t));
409+
let timeout_at = timeout.map(|t| Instant::now() + t);
410410

411411
Ok(ParsedRequest { url, redirects: Vec::new(), config, timeout_at })
412412
}

bitreq/tests/main.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ extern crate bitreq;
44
mod setup;
55

66
use std::io;
7+
use std::time::Duration;
78

89
use self::setup::*;
910

@@ -35,15 +36,17 @@ async fn test_json_using_serde() {
3536
#[tokio::test]
3637
async fn test_timeout_too_low() {
3738
setup();
38-
let request = bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(1);
39+
let request =
40+
bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(Duration::from_secs(1));
3941
let result = maybe_make_request(request, true).await;
4042
assert!(result.is_err());
4143
}
4244

4345
#[tokio::test]
4446
async fn test_timeout_high_enough() {
4547
setup();
46-
let request = bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(3);
48+
let request =
49+
bitreq::get(url("/slow_a")).with_body("Q".to_string()).with_timeout(Duration::from_secs(3));
4750
let result = maybe_make_request(request, true).await.unwrap();
4851
assert_eq!(result.as_str().unwrap(), "j: Q");
4952
}
@@ -178,8 +181,8 @@ async fn test_patch() {
178181
#[tokio::test]
179182
async fn tcp_connect_timeout() {
180183
let _listener = std::net::TcpListener::bind("127.0.0.1:32162").unwrap();
181-
let request =
182-
bitreq::Request::new(bitreq::Method::Get, "http://127.0.0.1:32162").with_timeout(1);
184+
let request = bitreq::Request::new(bitreq::Method::Get, "http://127.0.0.1:32162")
185+
.with_timeout(Duration::from_secs(1));
183186
let resp = maybe_make_request(request, true).await;
184187
assert!(resp.is_err());
185188
if let Some(bitreq::Error::IoError(err)) = resp.err() {
@@ -254,8 +257,9 @@ async fn test_future_drop_doesnt_hang() {
254257
// Here our cancellation detection should kick in, allowing the second request to open a fresh
255258
// connection and get a response immediately.
256259
let timesout = client.send_async(bitreq::get("http://example.com").with_pipelining());
257-
let request =
258-
client.send_async(bitreq::get("http://example.com").with_timeout(10).with_pipelining());
260+
let request = client.send_async(
261+
bitreq::get("http://example.com").with_timeout(Duration::from_secs(10)).with_pipelining(),
262+
);
259263

260264
let start = Instant::now();
261265
let (timedout, response) =

jsonrpc/src/http/bitreq_http.rs

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,16 @@ use crate::{Request, Response};
1919
const DEFAULT_URL: &str = "http://localhost";
2020
const DEFAULT_PORT: u16 = 8332; // the default RPC port for bitcoind.
2121
#[cfg(not(jsonrpc_fuzz))]
22-
const DEFAULT_TIMEOUT_SECONDS: u64 = 15;
22+
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15);
2323
#[cfg(jsonrpc_fuzz)]
24-
const DEFAULT_TIMEOUT_SECONDS: u64 = 1;
24+
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
2525

2626
/// An HTTP transport that uses [`bitreq`] and is useful for running a bitcoind RPC client.
2727
#[derive(Clone, Debug)]
2828
pub struct BitreqHttpTransport {
2929
/// URL of the RPC server.
3030
url: String,
31-
/// Timeout only supports second granularity.
31+
/// Timeout to use for HTTP requests.
3232
timeout: Duration,
3333
/// The value of the `Authorization` HTTP header, i.e., a base64 encoding of 'user:password'.
3434
basic_auth: Option<String>,
@@ -38,7 +38,7 @@ impl Default for BitreqHttpTransport {
3838
fn default() -> Self {
3939
BitreqHttpTransport {
4040
url: format!("{}:{}", DEFAULT_URL, DEFAULT_PORT),
41-
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS),
41+
timeout: DEFAULT_TIMEOUT,
4242
basic_auth: None,
4343
}
4444
}
@@ -51,29 +51,17 @@ impl BitreqHttpTransport {
5151
/// Returns a builder for [`BitreqHttpTransport`].
5252
pub fn builder() -> Builder { Builder::new() }
5353

54-
/// Returns the timeout in whole seconds, rounding positive sub-second values up to one.
55-
fn timeout_secs(&self) -> u64 {
56-
let secs = self.timeout.as_secs();
57-
if secs == 0 && self.timeout > Duration::from_secs(0) {
58-
1
59-
} else {
60-
secs
61-
}
62-
}
63-
6454
fn request<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
6555
where
6656
R: for<'a> serde::de::Deserialize<'a>,
6757
{
68-
let timeout_secs = self.timeout_secs();
69-
7058
let req = match &self.basic_auth {
7159
Some(auth) => bitreq::Request::new(bitreq::Method::Post, &self.url)
72-
.with_timeout(timeout_secs)
60+
.with_timeout(self.timeout)
7361
.with_header("Authorization", auth)
7462
.with_json(&req)?,
7563
None => bitreq::Request::new(bitreq::Method::Post, &self.url)
76-
.with_timeout(timeout_secs)
64+
.with_timeout(self.timeout)
7765
.with_json(&req)?,
7866
};
7967

jsonrpc/src/http/bitreq_http_async.rs

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ use crate::{Request, Response};
1414

1515
const DEFAULT_URL: &str = "http://localhost";
1616
const DEFAULT_PORT: u16 = 8332; // the default RPC port for bitcoind.
17-
const DEFAULT_TIMEOUT_SECONDS: u64 = 15;
17+
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(15);
1818

1919
/// An HTTP transport that uses [`bitreq`] and is useful for running a bitcoind RPC client.
2020
#[derive(Clone, Debug)]
2121
pub struct BitreqHttpTransport {
2222
/// URL of the RPC server.
2323
url: String,
24-
/// Timeout only supports second granularity.
24+
/// Timeout to use for HTTP requests.
2525
timeout: Duration,
2626
/// The value of the `Authorization` HTTP header, i.e., a base64 encoding of 'user:password'.
2727
basic_auth: Option<String>,
@@ -31,7 +31,7 @@ impl Default for BitreqHttpTransport {
3131
fn default() -> Self {
3232
BitreqHttpTransport {
3333
url: format!("{}:{}", DEFAULT_URL, DEFAULT_PORT),
34-
timeout: Duration::from_secs(DEFAULT_TIMEOUT_SECONDS),
34+
timeout: DEFAULT_TIMEOUT,
3535
basic_auth: None,
3636
}
3737
}
@@ -44,16 +44,6 @@ impl BitreqHttpTransport {
4444
/// Returns a builder for [`BitreqHttpTransport`].
4545
pub fn builder() -> Builder { Builder::new() }
4646

47-
/// Returns the timeout in whole seconds, rounding positive sub-second values up to one.
48-
fn timeout_secs(&self) -> u64 {
49-
let secs = self.timeout.as_secs();
50-
if secs == 0 && self.timeout > Duration::from_secs(0) {
51-
1
52-
} else {
53-
secs
54-
}
55-
}
56-
5747
async fn request<R>(&self, req: impl serde::Serialize) -> Result<R, crate::Error>
5848
where
5949
R: for<'a> serde::de::Deserialize<'a>,
@@ -65,15 +55,13 @@ impl BitreqHttpTransport {
6555
where
6656
R: for<'a> serde::de::Deserialize<'a>,
6757
{
68-
let timeout_secs = self.timeout_secs();
69-
7058
let req = match &self.basic_auth {
7159
Some(auth) => bitreq::Request::new(bitreq::Method::Post, &self.url)
72-
.with_timeout(timeout_secs)
60+
.with_timeout(self.timeout)
7361
.with_header("Authorization", auth)
7462
.with_json(&req)?,
7563
None => bitreq::Request::new(bitreq::Method::Post, &self.url)
76-
.with_timeout(timeout_secs)
64+
.with_timeout(self.timeout)
7765
.with_json(&req)?,
7866
};
7967

0 commit comments

Comments
 (0)