@@ -23,10 +23,7 @@ use bitcoin::hashes::{sha256, Hash};
2323use bitcoin:: hex:: { DisplayHex , FromHex } ;
2424use bitcoin:: { Address , Block , BlockHash , MerkleBlock , Script , Transaction , Txid } ;
2525
26- #[ allow( unused_imports) ]
27- use log:: { debug, error, info, trace} ;
28-
29- use reqwest:: { header, Body , Client , Response } ;
26+ use bitreq:: { Client , RequestExt , Response } ;
3027
3128use crate :: {
3229 AddressStats , BlockInfo , BlockStatus , BlockSummary , Builder , Error , MempoolRecentTx ,
@@ -35,61 +32,41 @@ use crate::{
3532} ;
3633
3734/// An async client for interacting with an Esplora API server.
38- #[ derive( Debug , Clone ) ]
35+ // FIXME: (@oleonardolima) there's no `Debug` implementation for `bitreq::Client`.
36+ #[ derive( Clone ) ]
3937pub struct AsyncClient < S = DefaultSleeper > {
4038 /// The URL of the Esplora Server.
4139 url : String ,
42- /// The inner [`reqwest::Client`] to make HTTP requests.
43- client : Client ,
40+ /// The proxy is ignored when targeting `wasm32`.
41+ proxy : Option < String > ,
42+ /// Socket timeout.
43+ timeout : Option < u64 > ,
44+ /// HTTP headers to set on every request made to Esplora server
45+ headers : HashMap < String , String > ,
4446 /// Number of times to retry a request
4547 max_retries : usize ,
48+ /// The inner [`reqwest::Client`] to make HTTP requests.
49+ client : Client ,
4650 /// Marker for the type of sleeper used
4751 marker : PhantomData < S > ,
4852}
4953
5054impl < S : Sleeper > AsyncClient < S > {
5155 /// Build an [`AsyncClient`] from a [`Builder`].
5256 pub fn from_builder ( builder : Builder ) -> Result < Self , Error > {
53- let mut client_builder = Client :: builder ( ) ;
54-
55- #[ cfg( not( target_arch = "wasm32" ) ) ]
56- if let Some ( proxy) = & builder. proxy {
57- client_builder = client_builder. proxy ( reqwest:: Proxy :: all ( proxy) ?) ;
58- }
59-
60- #[ cfg( not( target_arch = "wasm32" ) ) ]
61- if let Some ( timeout) = builder. timeout {
62- client_builder = client_builder. timeout ( core:: time:: Duration :: from_secs ( timeout) ) ;
63- }
64-
65- if !builder. headers . is_empty ( ) {
66- let mut headers = header:: HeaderMap :: new ( ) ;
67- for ( k, v) in builder. headers {
68- let header_name = header:: HeaderName :: from_lowercase ( k. to_lowercase ( ) . as_bytes ( ) )
69- . map_err ( |_| Error :: InvalidHttpHeaderName ( k) ) ?;
70- let header_value = header:: HeaderValue :: from_str ( & v)
71- . map_err ( |_| Error :: InvalidHttpHeaderValue ( v) ) ?;
72- headers. insert ( header_name, header_value) ;
73- }
74- client_builder = client_builder. default_headers ( headers) ;
75- }
57+ // TODO: (@oleonardolima) we should expose this to the final user through `Builder`.
58+ let cached_connections = 10 ;
59+ let client = Client :: new ( cached_connections) ;
7660
7761 Ok ( AsyncClient {
7862 url : builder. base_url ,
79- client : client_builder. build ( ) ?,
63+ proxy : builder. proxy ,
64+ timeout : builder. timeout ,
65+ headers : builder. headers ,
8066 max_retries : builder. max_retries ,
81- marker : PhantomData ,
82- } )
83- }
84-
85- /// Build an [`AsyncClient`] from a [`Client`].
86- pub fn from_client ( url : String , client : Client ) -> Self {
87- AsyncClient {
88- url,
8967 client,
90- max_retries : crate :: DEFAULT_MAX_RETRIES ,
9168 marker : PhantomData ,
92- }
69+ } )
9370 }
9471
9572 /// Make an HTTP GET request to given URL, deserializing to any `T` that
@@ -107,14 +84,13 @@ impl<S: Sleeper> AsyncClient<S> {
10784 let url = format ! ( "{}{}" , self . url, path) ;
10885 let response = self . get_with_retry ( & url) . await ?;
10986
110- if !response. status ( ) . is_success ( ) {
111- return Err ( Error :: HttpResponse {
112- status : response. status ( ) . as_u16 ( ) ,
113- message : response. text ( ) . await ?,
114- } ) ;
87+ if !is_success ( & response) {
88+ let status = u16:: try_from ( response. status_code ) . map_err ( Error :: StatusCode ) ?;
89+ let message = response. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
90+ return Err ( Error :: HttpResponse { status, message } ) ;
11591 }
11692
117- Ok ( deserialize :: < T > ( & response. bytes ( ) . await ? ) ?)
93+ Ok ( deserialize :: < T > ( response. as_bytes ( ) ) ?)
11894 }
11995
12096 /// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -147,14 +123,13 @@ impl<S: Sleeper> AsyncClient<S> {
147123 let url = format ! ( "{}{}" , self . url, path) ;
148124 let response = self . get_with_retry ( & url) . await ?;
149125
150- if !response. status ( ) . is_success ( ) {
151- return Err ( Error :: HttpResponse {
152- status : response. status ( ) . as_u16 ( ) ,
153- message : response. text ( ) . await ?,
154- } ) ;
126+ if !is_success ( & response) {
127+ let status = u16:: try_from ( response. status_code ) . map_err ( Error :: StatusCode ) ?;
128+ let message = response. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
129+ return Err ( Error :: HttpResponse { status, message } ) ;
155130 }
156131
157- response. json :: < T > ( ) . await . map_err ( Error :: Reqwest )
132+ response. json :: < T > ( ) . map_err ( Error :: BitReq )
158133 }
159134
160135 /// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -189,15 +164,14 @@ impl<S: Sleeper> AsyncClient<S> {
189164 let url = format ! ( "{}{}" , self . url, path) ;
190165 let response = self . get_with_retry ( & url) . await ?;
191166
192- if !response. status ( ) . is_success ( ) {
193- return Err ( Error :: HttpResponse {
194- status : response. status ( ) . as_u16 ( ) ,
195- message : response. text ( ) . await ?,
196- } ) ;
167+ if !is_success ( & response) {
168+ let status = u16:: try_from ( response. status_code ) . map_err ( Error :: StatusCode ) ?;
169+ let message = response. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
170+ return Err ( Error :: HttpResponse { status, message } ) ;
197171 }
198172
199- let hex_str = response. text ( ) . await ?;
200- Ok ( deserialize ( & Vec :: from_hex ( & hex_str) ?) ?)
173+ let hex_str = response. as_str ( ) ?;
174+ Ok ( deserialize ( & Vec :: from_hex ( hex_str) ?) ?)
201175 }
202176
203177 /// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -226,14 +200,13 @@ impl<S: Sleeper> AsyncClient<S> {
226200 let url = format ! ( "{}{}" , self . url, path) ;
227201 let response = self . get_with_retry ( & url) . await ?;
228202
229- if !response. status ( ) . is_success ( ) {
230- return Err ( Error :: HttpResponse {
231- status : response. status ( ) . as_u16 ( ) ,
232- message : response. text ( ) . await ?,
233- } ) ;
203+ if !is_success ( & response) {
204+ let status = u16:: try_from ( response. status_code ) . map_err ( Error :: StatusCode ) ?;
205+ let message = response. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
206+ return Err ( Error :: HttpResponse { status, message } ) ;
234207 }
235208
236- Ok ( response. text ( ) . await ? )
209+ Ok ( response. as_str ( ) ? . to_string ( ) )
237210 }
238211
239212 /// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -257,26 +230,25 @@ impl<S: Sleeper> AsyncClient<S> {
257230 ///
258231 /// This function will return an error either from the HTTP client, or the
259232 /// response's [`serde_json`] deserialization.
260- async fn post_request_bytes < T : Into < Body > > (
233+ async fn post_request_bytes < T : Into < Vec < u8 > > > (
261234 & self ,
262235 path : & str ,
263236 body : T ,
264237 query_params : Option < HashSet < ( & str , String ) > > ,
265238 ) -> Result < Response , Error > {
266239 let url: String = format ! ( "{}{}" , self . url, path) ;
267- let mut request = self . client . post ( url) . body ( body) ;
240+ let mut request: bitreq :: Request = bitreq :: post ( url) . with_body ( body) ;
268241
269- for param in query_params. unwrap_or_default ( ) {
270- request = request. query ( & param ) ;
242+ for ( key , value ) in query_params. unwrap_or_default ( ) {
243+ request = request. with_param ( key , value ) ;
271244 }
272245
273- let response = request. send ( ) . await ?;
246+ let response = request. send_async_with_client ( & self . client ) . await ?;
274247
275- if !response. status ( ) . is_success ( ) {
276- return Err ( Error :: HttpResponse {
277- status : response. status ( ) . as_u16 ( ) ,
278- message : response. text ( ) . await ?,
279- } ) ;
248+ if !is_success ( & response) {
249+ let status = u16:: try_from ( response. status_code ) . map_err ( Error :: StatusCode ) ?;
250+ let message = response. as_str ( ) . unwrap_or_default ( ) . to_string ( ) ;
251+ return Err ( Error :: HttpResponse { status, message } ) ;
280252 }
281253
282254 Ok ( response)
@@ -375,7 +347,7 @@ impl<S: Sleeper> AsyncClient<S> {
375347 pub async fn broadcast ( & self , transaction : & Transaction ) -> Result < Txid , Error > {
376348 let body = serialize :: < Transaction > ( transaction) . to_lower_hex_string ( ) ;
377349 let response = self . post_request_bytes ( "/tx" , body, None ) . await ?;
378- let txid = Txid :: from_str ( & response. text ( ) . await ?) . map_err ( Error :: HexToArray ) ?;
350+ let txid = Txid :: from_str ( response. as_str ( ) ?) . map_err ( Error :: HexToArray ) ?;
379351 Ok ( txid)
380352 }
381353
@@ -414,7 +386,7 @@ impl<S: Sleeper> AsyncClient<S> {
414386 )
415387 . await ?;
416388
417- Ok ( response. json :: < SubmitPackageResult > ( ) . await ?)
389+ Ok ( response. json :: < SubmitPackageResult > ( ) ?)
418390 }
419391
420392 /// Get the current height of the blockchain tip
@@ -606,21 +578,65 @@ impl<S: Sleeper> AsyncClient<S> {
606578 let mut delay = BASE_BACKOFF_MILLIS ;
607579 let mut attempts = 0 ;
608580
581+ let mut request = bitreq:: get ( url) ;
582+
583+ #[ cfg( not( target_arch = "wasm32" ) ) ]
584+ if let Some ( proxy) = & self . proxy {
585+ use bitreq:: Proxy ;
586+
587+ let proxy = Proxy :: new_http ( proxy. as_str ( ) ) ?;
588+ request = request. with_proxy ( proxy) ;
589+ }
590+
591+ #[ cfg( not( target_arch = "wasm32" ) ) ]
592+ if let Some ( timeout) = & self . timeout {
593+ request = request. with_timeout ( * timeout) ;
594+ }
595+
596+ if !self . headers . is_empty ( ) {
597+ request = request. with_headers ( & self . headers ) ;
598+ }
599+
609600 loop {
610- match self . client . get ( url ) . send ( ) . await ? {
611- resp if attempts < self . max_retries && is_status_retryable ( resp . status ( ) ) => {
601+ match request . clone ( ) . send_async_with_client ( & self . client ) . await ? {
602+ response if attempts < self . max_retries && is_retryable ( & response ) => {
612603 S :: sleep ( delay) . await ;
613604 attempts += 1 ;
614605 delay *= 2 ;
615606 }
616- resp => return Ok ( resp ) ,
607+ response => return Ok ( response ) ,
617608 }
618609 }
619610 }
620611}
621612
622- fn is_status_retryable ( status : reqwest:: StatusCode ) -> bool {
623- RETRYABLE_ERROR_CODES . contains ( & status. as_u16 ( ) )
613+ // /// Check if [`Response`] status is within 100-199.
614+ // fn is_informational(response: &Response) -> bool {
615+ // (100..200).contains(&response.status_code)
616+ // }
617+
618+ /// Check if [`Response`] status is within 200-299.
619+ fn is_success ( response : & Response ) -> bool {
620+ ( 200 ..300 ) . contains ( & response. status_code )
621+ }
622+
623+ // /// Check if [`Response`] status is within 300-399.
624+ // fn is_redirection(response: &Response) -> bool {
625+ // (300..400).contains(&response.status_code)
626+ // }
627+
628+ // /// Check if [`Response`] status is within 400-499.
629+ // fn is_client_error(response: &Response) -> bool {
630+ // (400..500).contains(&response.status_code)
631+ // }
632+
633+ // /// Check if [`Response`] status is within 500-599.
634+ // fn is_server_error(response: &Response) -> bool {
635+ // (500..600).contains(&response.status_code)
636+ // }
637+
638+ fn is_retryable ( response : & Response ) -> bool {
639+ RETRYABLE_ERROR_CODES . contains ( & ( response. status_code as u16 ) )
624640}
625641
626642/// Sleeper trait that allows any async runtime to be used.
0 commit comments