Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions bitreq/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
12 changes: 8 additions & 4 deletions bitreq/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,10 @@
//! # let _ = request.respond(response);
//! # });
//! #
//! use std::time::Duration;
//! #
//! # 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("</html>"));
//! assert_eq!(200, response.status_code);
//! assert_eq!("OK", response.reason_phrase);
Expand Down Expand Up @@ -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<dyn std::error::Error>> {
//! 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"))]
Expand Down Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions bitreq/src/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub struct Request {
params: Vec<(String, String)>,
headers: BTreeMap<String, String>,
body: Option<Vec<u8>>,
timeout: Option<u64>,
timeout: Option<Duration>,
pub(crate) pipelining: bool,
pub(crate) max_headers_size: Option<usize>,
pub(crate) max_status_line_len: Option<usize>,
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -403,10 +403,10 @@ impl ParsedRequest {
}

let timeout = config.timeout.or_else(|| match env::var("BITREQ_TIMEOUT") {
Ok(t) => t.parse::<u64>().ok(),
Ok(t) => t.parse::<u64>().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 })
}
Expand Down
16 changes: 10 additions & 6 deletions bitreq/tests/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ extern crate bitreq;
mod setup;

use std::io;
use std::time::Duration;

use self::setup::*;

Expand Down Expand Up @@ -35,15 +36,17 @@ 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());
}

#[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");
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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) =
Expand Down
24 changes: 6 additions & 18 deletions jsonrpc/src/http/bitreq_http.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand All @@ -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,
}
}
Expand All @@ -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<R>(&self, req: impl serde::Serialize) -> Result<R, Error>
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)?,
};

Expand Down
22 changes: 5 additions & 17 deletions jsonrpc/src/http/bitreq_http_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
Expand All @@ -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,
}
}
Expand All @@ -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<R>(&self, req: impl serde::Serialize) -> Result<R, crate::Error>
where
R: for<'a> serde::de::Deserialize<'a>,
Expand All @@ -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)?,
};

Expand Down
Loading