18
18
//! and parsing responses
19
19
//!
20
20
21
+ use std:: { error, io} ;
21
22
use std:: collections:: HashMap ;
22
- use std:: io;
23
- use std:: io:: Read ;
24
23
use std:: sync:: { Arc , Mutex } ;
25
24
26
- use hyper;
27
- use hyper:: client:: Client as HyperClient ;
28
- use hyper:: header:: { Authorization , Basic , ContentType , Headers } ;
29
25
use serde;
26
+ use base64;
27
+ use http;
30
28
use serde_json;
31
29
32
30
use super :: { Request , Response } ;
33
31
use util:: HashableValue ;
34
32
use error:: Error ;
35
33
34
+ /// An interface for an HTTP roundtripper that handles HTTP requests.
35
+ pub trait HttpRoundTripper {
36
+ /// The type of the http::Response body.
37
+ type ResponseBody : io:: Read ;
38
+ /// The type for errors generated by the roundtripper.
39
+ type Err : error:: Error ;
40
+
41
+ /// Make an HTTP request. In practice only POST request will be made.
42
+ fn request (
43
+ & self ,
44
+ http:: Request < & [ u8 ] > ,
45
+ ) -> Result < http:: Response < Self :: ResponseBody > , Self :: Err > ;
46
+ }
47
+
36
48
/// A handle to a remote JSONRPC server
37
- pub struct Client {
49
+ pub struct Client < R : HttpRoundTripper > {
38
50
url : String ,
39
51
user : Option < String > ,
40
52
pass : Option < String > ,
41
- client : HyperClient ,
53
+ roundtripper : R ,
42
54
nonce : Arc < Mutex < u64 > > ,
43
55
}
44
56
45
- impl Client {
57
+ impl < Rt : HttpRoundTripper + ' static > Client < Rt > {
46
58
/// Creates a new client
47
- pub fn new ( url : String , user : Option < String > , pass : Option < String > ) -> Client {
59
+ pub fn new (
60
+ roundtripper : Rt ,
61
+ url : String ,
62
+ user : Option < String > ,
63
+ pass : Option < String > ,
64
+ ) -> Client < Rt > {
48
65
// Check that if we have a password, we have a username; other way around is ok
49
66
debug_assert ! ( pass. is_none( ) || user. is_some( ) ) ;
50
67
51
68
Client {
52
69
url : url,
53
70
user : user,
54
71
pass : pass,
55
- client : HyperClient :: new ( ) ,
72
+ roundtripper : roundtripper ,
56
73
nonce : Arc :: new ( Mutex :: new ( 0 ) ) ,
57
74
}
58
75
}
@@ -78,52 +95,30 @@ impl Client {
78
95
// Build request
79
96
let request_raw = serde_json:: to_vec ( body) ?;
80
97
81
- // Setup connection
82
- let mut headers = Headers :: new ( ) ;
83
- headers. set ( ContentType :: json ( ) ) ;
98
+ // Send request
99
+ let mut request_builder = http:: Request :: post ( & self . url ) ;
100
+ request_builder. header ( "Content-Type" , "application/json-rpc" ) ;
101
+
102
+ // Set Authorization header
84
103
if let Some ( ref user) = self . user {
85
- headers. set ( Authorization ( Basic {
86
- username : user. clone ( ) ,
87
- password : self . pass . clone ( ) ,
88
- } ) ) ;
104
+ let mut auth = user. clone ( ) ;
105
+ auth. push ( ':' ) ;
106
+ if let Some ( ref pass) = self . pass {
107
+ auth. push_str ( & pass[ ..] ) ;
108
+ }
109
+ let value = format ! ( "Basic {}" , & base64:: encode( auth. as_bytes( ) ) ) ;
110
+ request_builder. header ( "Authorization" , value) ;
89
111
}
90
112
91
- // Send request
92
- let retry_headers = headers. clone ( ) ;
93
- let hyper_request = self . client . post ( & self . url ) . headers ( headers) . body ( & request_raw[ ..] ) ;
94
- let mut stream = match hyper_request. send ( ) {
95
- Ok ( s) => s,
96
- // Hyper maintains a pool of TCP connections to its various clients,
97
- // and when one drops it cannot tell until it tries sending. In this
98
- // case the appropriate thing is to re-send, which will cause hyper
99
- // to open a new connection. Jonathan Reem explained this to me on
100
- // IRC, citing vague technical reasons that the library itself cannot
101
- // do the retry transparently.
102
- Err ( hyper:: error:: Error :: Io ( e) ) => {
103
- if e. kind ( ) == io:: ErrorKind :: BrokenPipe
104
- || e. kind ( ) == io:: ErrorKind :: ConnectionAborted
105
- {
106
- try!( self
107
- . client
108
- . post ( & self . url )
109
- . headers ( retry_headers)
110
- . body ( & request_raw[ ..] )
111
- . send ( )
112
- . map_err ( Error :: Hyper ) )
113
- } else {
114
- return Err ( Error :: Hyper ( hyper:: error:: Error :: Io ( e) ) ) ;
115
- }
116
- }
117
- Err ( e) => {
118
- return Err ( Error :: Hyper ( e) ) ;
119
- }
120
- } ;
113
+ // Errors only on invalid header or builder reuse.
114
+ let http_request = request_builder. body ( & request_raw[ ..] ) . unwrap ( ) ;
115
+
116
+ let http_response =
117
+ self . roundtripper . request ( http_request) . map_err ( |e| Error :: Http ( Box :: new ( e) ) ) ?;
121
118
122
119
// nb we ignore stream.status since we expect the body
123
120
// to contain information about any error
124
- let response: R = serde_json:: from_reader ( & mut stream) ?;
125
- stream. bytes ( ) . count ( ) ; // Drain the stream so it can be reused
126
- Ok ( response)
121
+ Ok ( serde_json:: from_reader ( http_response. into_body ( ) ) ?)
127
122
}
128
123
129
124
/// Sends a request to a client
@@ -204,10 +199,24 @@ impl Client {
204
199
#[ cfg( test) ]
205
200
mod tests {
206
201
use super :: * ;
202
+ use std:: io;
203
+
204
+ struct RT ( ) ;
205
+ impl HttpRoundTripper for RT {
206
+ type ResponseBody = io:: Empty ;
207
+ type Err = io:: Error ;
208
+
209
+ fn request (
210
+ & self ,
211
+ _: http:: Request < & [ u8 ] > ,
212
+ ) -> Result < http:: Response < Self :: ResponseBody > , Self :: Err > {
213
+ Err ( io:: ErrorKind :: Other . into ( ) )
214
+ }
215
+ }
207
216
208
217
#[ test]
209
218
fn sanity ( ) {
210
- let client = Client :: new ( "localhost" . to_owned ( ) , None , None ) ;
219
+ let client = Client :: new ( RT ( ) , "localhost" . to_owned ( ) , None , None ) ;
211
220
assert_eq ! ( client. last_nonce( ) , 0 ) ;
212
221
let req1 = client. build_request ( "test" , & [ ] ) ;
213
222
assert_eq ! ( client. last_nonce( ) , 1 ) ;
0 commit comments