9
9
// You may not use this file except in accordance with one or both of these
10
10
// licenses.
11
11
12
- //! Esplora by way of `reqwest` HTTP client.
13
-
14
- use std:: collections:: HashMap ;
15
- use std:: marker:: PhantomData ;
16
- use std:: str:: FromStr ;
12
+ //! Esplora by way of `asyn_minreq` HTTP client.
17
13
18
14
use bitcoin:: consensus:: { deserialize, serialize, Decodable , Encodable } ;
19
15
use bitcoin:: hashes:: { sha256, Hash } ;
@@ -22,32 +18,44 @@ use bitcoin::Address;
22
18
use bitcoin:: {
23
19
block:: Header as BlockHeader , Block , BlockHash , MerkleBlock , Script , Transaction , Txid ,
24
20
} ;
25
-
26
- #[ allow( unused_imports) ]
27
- use log:: { debug, error, info, trace} ;
28
-
29
- use reqwest:: { header, Client , Response } ;
21
+ use std:: collections:: HashMap ;
22
+ use std:: marker:: PhantomData ;
23
+ use std:: str:: FromStr ;
30
24
31
25
use crate :: api:: AddressStats ;
32
26
use crate :: {
33
27
BlockStatus , BlockSummary , Builder , Error , MerkleProof , OutputStatus , Tx , TxStatus ,
34
28
BASE_BACKOFF_MILLIS , RETRYABLE_ERROR_CODES ,
35
29
} ;
30
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
31
+ use async_minreq:: { Method , Request , Response } ;
32
+ #[ cfg( feature = "async" ) ]
33
+ use reqwest:: { header, Client , Response } ;
34
+
35
+ #[ allow( unused_imports) ]
36
+ use log:: { debug, error, info, trace} ;
37
+
38
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
39
+ /// Valid HTTP code
40
+ const VALID_HTTP_CODE : i32 = 299 ;
36
41
37
42
#[ derive( Debug , Clone ) ]
38
43
pub struct AsyncClient < S = DefaultSleeper > {
39
44
/// The URL of the Esplora Server.
40
45
url : String ,
41
- /// The inner [`reqwest::Client`] to make HTTP requests.
42
- client : Client ,
43
- /// Number of times to retry a request
46
+ /// Number of times to retry a request.
44
47
max_retries : usize ,
45
-
46
- /// Marker for the type of sleeper used
48
+ #[ cfg( feature = "async" ) ]
49
+ client : Client ,
50
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
51
+ /// Default headers (applied to every request).
52
+ headers : HashMap < String , String > ,
53
+ /// Marker for the sleeper.
47
54
marker : PhantomData < S > ,
48
55
}
49
56
50
57
impl < S : Sleeper > AsyncClient < S > {
58
+ #[ cfg( feature = "async" ) ]
51
59
/// Build an async client from a builder
52
60
pub fn from_builder ( builder : Builder ) -> Result < Self , Error > {
53
61
let mut client_builder = Client :: builder ( ) ;
@@ -82,6 +90,18 @@ impl<S: Sleeper> AsyncClient<S> {
82
90
} )
83
91
}
84
92
93
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
94
+ /// Build an async client from a builder
95
+ pub fn from_builder ( builder : Builder ) -> Result < Self , Error > {
96
+ Ok ( AsyncClient {
97
+ url : builder. base_url ,
98
+ max_retries : builder. max_retries ,
99
+ headers : builder. headers ,
100
+ marker : PhantomData ,
101
+ } )
102
+ }
103
+
104
+ #[ cfg( feature = "async" ) ]
85
105
pub fn from_client ( url : String , client : Client ) -> Self {
86
106
AsyncClient {
87
107
url,
@@ -90,6 +110,15 @@ impl<S: Sleeper> AsyncClient<S> {
90
110
marker : PhantomData ,
91
111
}
92
112
}
113
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
114
+ pub fn from_client ( url : String , headers : HashMap < String , String > ) -> Self {
115
+ AsyncClient {
116
+ url,
117
+ headers,
118
+ max_retries : crate :: DEFAULT_MAX_RETRIES ,
119
+ marker : PhantomData ,
120
+ }
121
+ }
93
122
94
123
/// Make an HTTP GET request to given URL, deserializing to any `T` that
95
124
/// implement [`bitcoin::consensus::Decodable`].
@@ -106,14 +135,32 @@ impl<S: Sleeper> AsyncClient<S> {
106
135
let url = format ! ( "{}{}" , self . url, path) ;
107
136
let response = self . get_with_retry ( & url) . await ?;
108
137
109
- if !response. status ( ) . is_success ( ) {
110
- return Err ( Error :: HttpResponse {
111
- status : response. status ( ) . as_u16 ( ) ,
112
- message : response. text ( ) . await ?,
113
- } ) ;
138
+ #[ cfg( feature = "async" ) ]
139
+ {
140
+ if !response. status ( ) . is_success ( ) {
141
+ return Err ( Error :: HttpResponse {
142
+ status : response. status ( ) . as_u16 ( ) ,
143
+ message : response. text ( ) . await ?,
144
+ } ) ;
145
+ }
146
+
147
+ Ok ( deserialize :: < T > ( & response. bytes ( ) . await ?) ?)
114
148
}
115
149
116
- Ok ( deserialize :: < T > ( & response. bytes ( ) . await ?) ?)
150
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
151
+ {
152
+ if response. status_code > VALID_HTTP_CODE {
153
+ return Err ( Error :: HttpResponse {
154
+ status : response. status_code as u16 ,
155
+ message : match response. as_str ( ) {
156
+ Ok ( resp) => resp. to_string ( ) ,
157
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
158
+ } ,
159
+ } ) ;
160
+ }
161
+
162
+ return Ok ( deserialize :: < T > ( response. as_bytes ( ) ) ?) ;
163
+ }
117
164
}
118
165
119
166
/// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -146,14 +193,31 @@ impl<S: Sleeper> AsyncClient<S> {
146
193
let url = format ! ( "{}{}" , self . url, path) ;
147
194
let response = self . get_with_retry ( & url) . await ?;
148
195
149
- if !response. status ( ) . is_success ( ) {
150
- return Err ( Error :: HttpResponse {
151
- status : response. status ( ) . as_u16 ( ) ,
152
- message : response. text ( ) . await ?,
153
- } ) ;
196
+ #[ cfg( feature = "async" ) ]
197
+ {
198
+ if !response. status ( ) . is_success ( ) {
199
+ return Err ( Error :: HttpResponse {
200
+ status : response. status ( ) . as_u16 ( ) ,
201
+ message : response. text ( ) . await ?,
202
+ } ) ;
203
+ }
204
+
205
+ response. json :: < T > ( ) . await . map_err ( Error :: Reqwest )
154
206
}
207
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
208
+ {
209
+ if response. status_code > VALID_HTTP_CODE {
210
+ return Err ( Error :: HttpResponse {
211
+ status : response. status_code as u16 ,
212
+ message : match response. as_str ( ) {
213
+ Ok ( resp) => resp. to_string ( ) ,
214
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
215
+ } ,
216
+ } ) ;
217
+ }
155
218
156
- response. json :: < T > ( ) . await . map_err ( Error :: Reqwest )
219
+ return response. json ( ) . map_err ( Error :: AsyncMinreq ) ;
220
+ }
157
221
}
158
222
159
223
/// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -188,15 +252,38 @@ impl<S: Sleeper> AsyncClient<S> {
188
252
let url = format ! ( "{}{}" , self . url, path) ;
189
253
let response = self . get_with_retry ( & url) . await ?;
190
254
191
- if !response. status ( ) . is_success ( ) {
192
- return Err ( Error :: HttpResponse {
193
- status : response. status ( ) . as_u16 ( ) ,
194
- message : response. text ( ) . await ?,
195
- } ) ;
255
+ #[ cfg( feature = "async" ) ]
256
+ {
257
+ if !response. status ( ) . is_success ( ) {
258
+ return Err ( Error :: HttpResponse {
259
+ status : response. status ( ) . as_u16 ( ) ,
260
+ message : response. text ( ) . await ?,
261
+ } ) ;
262
+ }
263
+
264
+ let hex_str = response. text ( ) . await ?;
265
+ Ok ( deserialize ( & Vec :: from_hex ( & hex_str) ?) ?)
196
266
}
197
267
198
- let hex_str = response. text ( ) . await ?;
199
- Ok ( deserialize ( & Vec :: from_hex ( & hex_str) ?) ?)
268
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
269
+ {
270
+ if response. status_code > VALID_HTTP_CODE {
271
+ return Err ( Error :: HttpResponse {
272
+ status : response. status_code as u16 ,
273
+ message : match response. as_str ( ) {
274
+ Ok ( resp) => resp. to_string ( ) ,
275
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
276
+ } ,
277
+ } ) ;
278
+ }
279
+
280
+ let hex_str = match response. as_str ( ) {
281
+ Ok ( resp) => resp. to_string ( ) ,
282
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
283
+ } ;
284
+
285
+ return Ok ( deserialize ( & Vec :: from_hex ( & hex_str) ?) ?) ;
286
+ }
200
287
}
201
288
202
289
/// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -225,14 +312,35 @@ impl<S: Sleeper> AsyncClient<S> {
225
312
let url = format ! ( "{}{}" , self . url, path) ;
226
313
let response = self . get_with_retry ( & url) . await ?;
227
314
228
- if !response. status ( ) . is_success ( ) {
229
- return Err ( Error :: HttpResponse {
230
- status : response. status ( ) . as_u16 ( ) ,
231
- message : response. text ( ) . await ?,
232
- } ) ;
315
+ #[ cfg( feature = "async" ) ]
316
+ {
317
+ if !response. status ( ) . is_success ( ) {
318
+ return Err ( Error :: HttpResponse {
319
+ status : response. status ( ) . as_u16 ( ) ,
320
+ message : response. text ( ) . await ?,
321
+ } ) ;
322
+ }
323
+
324
+ Ok ( response. text ( ) . await ?)
233
325
}
234
326
235
- Ok ( response. text ( ) . await ?)
327
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
328
+ {
329
+ if response. status_code > VALID_HTTP_CODE {
330
+ return Err ( Error :: HttpResponse {
331
+ status : response. status_code as u16 ,
332
+ message : match response. as_str ( ) {
333
+ Ok ( resp) => resp. to_string ( ) ,
334
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
335
+ } ,
336
+ } ) ;
337
+ }
338
+
339
+ return Ok ( match response. as_str ( ) {
340
+ Ok ( resp) => resp. to_string ( ) ,
341
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
342
+ } ) ;
343
+ }
236
344
}
237
345
238
346
/// Make an HTTP GET request to given URL, deserializing to `Option<T>`.
@@ -263,15 +371,36 @@ impl<S: Sleeper> AsyncClient<S> {
263
371
let url = format ! ( "{}{}" , self . url, path) ;
264
372
let body = serialize :: < T > ( & body) . to_lower_hex_string ( ) ;
265
373
266
- let response = self . client . post ( url) . body ( body) . send ( ) . await ?;
374
+ #[ cfg( feature = "async" ) ]
375
+ {
376
+ let response = self . client . post ( url) . body ( body) . send ( ) . await ?;
267
377
268
- if !response. status ( ) . is_success ( ) {
269
- return Err ( Error :: HttpResponse {
270
- status : response. status ( ) . as_u16 ( ) ,
271
- message : response. text ( ) . await ?,
272
- } ) ;
273
- }
378
+ if !response. status ( ) . is_success ( ) {
379
+ return Err ( Error :: HttpResponse {
380
+ status : response. status ( ) . as_u16 ( ) ,
381
+ message : response. text ( ) . await ?,
382
+ } ) ;
383
+ }
384
+
385
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
386
+ {
387
+ let mut request = Request :: new ( Method :: Post , & url) . with_body ( body) ;
388
+ for ( key, value) in & self . headers {
389
+ request = request. with_header ( key, value) ;
390
+ }
274
391
392
+ let response = request. send ( ) . await . map_err ( Error :: AsyncMinreq ) ?;
393
+ if response. status_code > VALID_HTTP_CODE {
394
+ return Err ( Error :: HttpResponse {
395
+ status : response. status_code as u16 ,
396
+ message : match response. as_str ( ) {
397
+ Ok ( resp) => resp. to_string ( ) ,
398
+ Err ( _) => return Err ( Error :: InvalidResponse ) ,
399
+ } ,
400
+ } ) ;
401
+ }
402
+ }
403
+ }
275
404
Ok ( ( ) )
276
405
}
277
406
@@ -454,6 +583,7 @@ impl<S: Sleeper> AsyncClient<S> {
454
583
& self . url
455
584
}
456
585
586
+ #[ cfg( feature = "async" ) ]
457
587
/// Get the underlying [`Client`].
458
588
pub fn client ( & self ) -> & Client {
459
589
& self . client
@@ -465,6 +595,7 @@ impl<S: Sleeper> AsyncClient<S> {
465
595
let mut delay = BASE_BACKOFF_MILLIS ;
466
596
let mut attempts = 0 ;
467
597
598
+ #[ cfg( feature = "async" ) ]
468
599
loop {
469
600
match self . client . get ( url) . send ( ) . await ? {
470
601
resp if attempts < self . max_retries && is_status_retryable ( resp. status ( ) ) => {
@@ -475,13 +606,40 @@ impl<S: Sleeper> AsyncClient<S> {
475
606
resp => return Ok ( resp) ,
476
607
}
477
608
}
609
+
610
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
611
+ {
612
+ loop {
613
+ let mut request = Request :: new ( Method :: Get , url) ;
614
+ for ( key, value) in & self . headers {
615
+ request = request. with_header ( key, value) ;
616
+ }
617
+
618
+ match request. send ( ) . await ? {
619
+ resp if attempts < self . max_retries
620
+ && is_status_retryable ( resp. status_code ) =>
621
+ {
622
+ S :: sleep ( delay) . await ;
623
+ attempts += 1 ;
624
+ delay *= 2 ;
625
+ }
626
+ resp => return Ok ( resp) ,
627
+ }
628
+ }
629
+ }
478
630
}
479
631
}
480
632
633
+ #[ cfg( feature = "async" ) ]
481
634
fn is_status_retryable ( status : reqwest:: StatusCode ) -> bool {
482
635
RETRYABLE_ERROR_CODES . contains ( & status. as_u16 ( ) )
483
636
}
484
637
638
+ #[ cfg( all( feature = "async-minreq" , not( feature = "async" ) ) ) ]
639
+ fn is_status_retryable ( status : i32 ) -> bool {
640
+ RETRYABLE_ERROR_CODES . contains ( & ( status as u16 ) )
641
+ }
642
+
485
643
pub trait Sleeper : ' static {
486
644
type Sleep : std:: future:: Future < Output = ( ) > ;
487
645
fn sleep ( dur : std:: time:: Duration ) -> Self :: Sleep ;
0 commit comments