1818//! and parsing responses
1919//!
2020
21+ use std:: { error, io} ;
2122use std:: collections:: HashMap ;
22- use std:: io;
23- use std:: io:: Read ;
2423use std:: sync:: { Arc , Mutex } ;
2524
26- use hyper;
27- use hyper:: client:: Client as HyperClient ;
28- use hyper:: header:: { Authorization , Basic , ContentType , Headers } ;
2925use serde;
26+ use base64;
27+ use http;
3028use serde_json;
3129
3230use super :: { Request , Response } ;
3331use util:: HashableValue ;
3432use error:: Error ;
3533
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+
3648/// A handle to a remote JSONRPC server
37- pub struct Client {
49+ pub struct Client < R : HttpRoundTripper > {
3850 url : String ,
3951 user : Option < String > ,
4052 pass : Option < String > ,
41- client : HyperClient ,
53+ roundtripper : R ,
4254 nonce : Arc < Mutex < u64 > > ,
4355}
4456
45- impl Client {
57+ impl < Rt : HttpRoundTripper + ' static > Client < Rt > {
4658 /// 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 > {
4865 // Check that if we have a password, we have a username; other way around is ok
4966 debug_assert ! ( pass. is_none( ) || user. is_some( ) ) ;
5067
5168 Client {
5269 url : url,
5370 user : user,
5471 pass : pass,
55- client : HyperClient :: new ( ) ,
72+ roundtripper : roundtripper ,
5673 nonce : Arc :: new ( Mutex :: new ( 0 ) ) ,
5774 }
5875 }
@@ -78,52 +95,30 @@ impl Client {
7895 // Build request
7996 let request_raw = serde_json:: to_vec ( body) ?;
8097
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
84103 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) ;
89111 }
90112
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) ) ) ?;
121118
122119 // nb we ignore stream.status since we expect the body
123120 // 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 ( ) ) ?)
127122 }
128123
129124 /// Sends a request to a client
@@ -204,10 +199,24 @@ impl Client {
204199#[ cfg( test) ]
205200mod tests {
206201 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+ }
207216
208217 #[ test]
209218 fn sanity ( ) {
210- let client = Client :: new ( "localhost" . to_owned ( ) , None , None ) ;
219+ let client = Client :: new ( RT ( ) , "localhost" . to_owned ( ) , None , None ) ;
211220 assert_eq ! ( client. last_nonce( ) , 0 ) ;
212221 let req1 = client. build_request ( "test" , & [ ] ) ;
213222 assert_eq ! ( client. last_nonce( ) , 1 ) ;
0 commit comments