From 73cc41c8ce62f5c34537598183abd3bdb701a38d Mon Sep 17 00:00:00 2001 From: John Cantrell Date: Wed, 6 Dec 2023 12:06:22 -0500 Subject: [PATCH] add http header support --- src/async.rs | 14 ++++++++++- src/blocking.rs | 9 +++++++ src/lib.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/async.rs b/src/async.rs index 850ef3d..a6a70c0 100644 --- a/src/async.rs +++ b/src/async.rs @@ -24,7 +24,7 @@ use bitcoin::{ #[allow(unused_imports)] use log::{debug, error, info, trace}; -use reqwest::{Client, StatusCode}; +use reqwest::{header, Client, StatusCode}; use crate::{BlockStatus, BlockSummary, Builder, Error, MerkleProof, OutputStatus, Tx, TxStatus}; @@ -49,6 +49,18 @@ impl AsyncClient { client_builder = client_builder.timeout(core::time::Duration::from_secs(timeout)); } + if !builder.headers.is_empty() { + let mut headers = header::HeaderMap::new(); + for (k, v) in builder.headers { + let header_name = header::HeaderName::from_lowercase(k.to_lowercase().as_bytes()) + .map_err(|_| Error::InvalidHttpHeaderName(k))?; + let header_value = header::HeaderValue::from_str(&v) + .map_err(|_| Error::InvalidHttpHeaderValue(v))?; + headers.insert(header_name, header_value); + } + client_builder = client_builder.default_headers(headers); + } + Ok(Self::from_client(builder.base_url, client_builder.build()?)) } diff --git a/src/blocking.rs b/src/blocking.rs index d2afe7c..53e2a7b 100644 --- a/src/blocking.rs +++ b/src/blocking.rs @@ -36,6 +36,8 @@ pub struct BlockingClient { pub proxy: Option, /// Socket timeout. pub timeout: Option, + /// HTTP headers to set on every request made to Esplora server + pub headers: HashMap, } impl BlockingClient { @@ -45,6 +47,7 @@ impl BlockingClient { url: builder.base_url, proxy: builder.proxy, timeout: builder.timeout, + headers: builder.headers, } } @@ -60,6 +63,12 @@ impl BlockingClient { request = request.with_timeout(*timeout); } + if !self.headers.is_empty() { + for (key, value) in &self.headers { + request = request.with_header(key, value); + } + } + Ok(request) } diff --git a/src/lib.rs b/src/lib.rs index 94e9471..9e33fce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,6 +117,8 @@ pub struct Builder { pub proxy: Option, /// Socket timeout. pub timeout: Option, + /// HTTP headers to set on every request made to Esplora server + pub headers: HashMap, } impl Builder { @@ -126,6 +128,7 @@ impl Builder { base_url: base_url.to_string(), proxy: None, timeout: None, + headers: HashMap::new(), } } @@ -141,6 +144,12 @@ impl Builder { self } + /// Add a header to set on each request + pub fn header(mut self, key: &str, value: &str) -> Self { + self.headers.insert(key.to_string(), value.to_string()); + self + } + /// build a blocking client from builder #[cfg(feature = "blocking")] pub fn build_blocking(self) -> BlockingClient { @@ -181,6 +190,10 @@ pub enum Error { HeaderHeightNotFound(u32), /// Header hash not found HeaderHashNotFound(BlockHash), + /// Invalid HTTP Header name specified + InvalidHttpHeaderName(String), + /// Invalid HTTP Header value specified + InvalidHttpHeaderValue(String), } impl fmt::Display for Error { @@ -261,6 +274,13 @@ mod test { #[cfg(all(feature = "blocking", feature = "async"))] async fn setup_clients() -> (BlockingClient, AsyncClient) { + setup_clients_with_headers(HashMap::new()).await + } + + #[cfg(all(feature = "blocking", feature = "async"))] + async fn setup_clients_with_headers( + headers: HashMap, + ) -> (BlockingClient, AsyncClient) { PREMINE .get_or_init(|| async { let _miner = MINER.lock().await; @@ -270,7 +290,11 @@ mod test { let esplora_url = ELECTRSD.esplora_url.as_ref().unwrap(); - let builder = Builder::new(&format!("http://{}", esplora_url)); + let mut builder = Builder::new(&format!("http://{}", esplora_url)); + if !headers.is_empty() { + builder.headers = headers; + } + let blocking_client = builder.build_blocking(); let builder_async = Builder::new(&format!("http://{}", esplora_url)); @@ -851,4 +875,40 @@ mod test { let blocks_genesis_async = async_client.get_blocks(Some(0)).await.unwrap(); assert_eq!(blocks_genesis, blocks_genesis_async); } + + #[cfg(all(feature = "blocking", feature = "async"))] + #[tokio::test] + async fn test_get_tx_with_http_header() { + let headers = [( + "Authorization".to_string(), + "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".to_string(), + )] + .into(); + let (blocking_client, async_client) = setup_clients_with_headers(headers).await; + + let address = BITCOIND + .client + .get_new_address(Some("test"), Some(AddressType::Legacy)) + .unwrap() + .assume_checked(); + let txid = BITCOIND + .client + .send_to_address( + &address, + Amount::from_sat(1000), + None, + None, + None, + None, + None, + None, + ) + .unwrap(); + let _miner = MINER.lock().await; + generate_blocks_and_wait(1); + + let tx = blocking_client.get_tx(&txid).unwrap(); + let tx_async = async_client.get_tx(&txid).await.unwrap(); + assert_eq!(tx, tx_async); + } }