hyper_sync/client/
mod.rs

1//! HTTP Client
2//!
3//! # Usage
4//!
5//! The `Client` API is designed for most people to make HTTP requests.
6//! It utilizes the lower level `Request` API.
7//!
8//! ## GET
9//!
10//! ```no_run
11//! # use hyper_sync::Client;
12//! let client = Client::new();
13//!
14//! let res = client.get("http://example.domain").send().unwrap();
15//! assert_eq!(res.status, hyper_sync::Ok);
16//! ```
17//!
18//! The returned value is a `Response`, which provides easy access to
19//! the `status`, the `headers`, and the response body via the `Read`
20//! trait.
21//!
22//! ## POST
23//!
24//! ```no_run
25//! # use hyper_sync::Client;
26//! let client = Client::new();
27//!
28//! let res = client.post("http://example.domain")
29//!     .body("foo=bar")
30//!     .send()
31//!     .unwrap();
32//! assert_eq!(res.status, hyper_sync::Ok);
33//! ```
34//!
35//! # Sync
36//!
37//! The `Client` implements `Sync`, so you can share it among multiple threads
38//! and make multiple requests simultaneously.
39//!
40//! ```no_run
41//! # use hyper_sync::Client;
42//! use std::sync::Arc;
43//! use std::thread;
44//!
45//! // Note: an Arc is used here because `thread::spawn` creates threads that
46//! // can outlive the main thread, so we must use reference counting to keep
47//! // the Client alive long enough. Scoped threads could skip the Arc.
48//! let client = Arc::new(Client::new());
49//! let clone1 = client.clone();
50//! let clone2 = client.clone();
51//! thread::spawn(move || {
52//!     clone1.get("http://example.domain").send().unwrap();
53//! });
54//! thread::spawn(move || {
55//!     clone2.post("http://example.domain/post").body("foo=bar").send().unwrap();
56//! });
57//! ```
58use std::borrow::Cow;
59use std::default::Default;
60use std::io::{self, copy, Read};
61use std::fmt;
62
63use std::time::Duration;
64
65use url::Url;
66use url::ParseError as UrlError;
67
68use header::{Headers, Header};
69use header::{ContentLength, Host, Location};
70use method::Method;
71use net::{NetworkConnector, NetworkStream, SslClient};
72use Error;
73
74use self::proxy::{Proxy, tunnel};
75use self::scheme::Scheme;
76pub use self::pool::Pool;
77pub use self::request::Request;
78pub use self::response::Response;
79
80mod proxy;
81pub mod pool;
82pub mod request;
83pub mod response;
84
85use http::Protocol;
86use http::h1::Http11Protocol;
87
88
89/// A Client to use additional features with Requests.
90///
91/// Clients can handle things such as: redirect policy, connection pooling.
92pub struct Client {
93    protocol: Box<Protocol + Send + Sync>,
94    redirect_policy: RedirectPolicy,
95    read_timeout: Option<Duration>,
96    write_timeout: Option<Duration>,
97    proxy: Option<(Scheme, Cow<'static, str>, u16)>
98}
99
100impl fmt::Debug for Client {
101    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
102        fmt.debug_struct("Client")
103           .field("redirect_policy", &self.redirect_policy)
104           .field("read_timeout", &self.read_timeout)
105           .field("write_timeout", &self.write_timeout)
106           .field("proxy", &self.proxy)
107           .finish()
108    }
109}
110
111impl Client {
112
113    /// Create a new Client.
114    pub fn new() -> Client {
115        Client::with_pool_config(Default::default())
116    }
117
118    /// Create a new Client with a configured Pool Config.
119    pub fn with_pool_config(config: pool::Config) -> Client {
120        Client::with_connector(Pool::new(config))
121    }
122
123    /// Create a Client with an HTTP proxy to a (host, port).
124    pub fn with_http_proxy<H>(host: H, port: u16) -> Client
125    where H: Into<Cow<'static, str>> {
126        let host = host.into();
127        let proxy = tunnel((Scheme::Http, host.clone(), port));
128        let mut client = Client::with_connector(Pool::with_connector(Default::default(), proxy));
129        client.proxy = Some((Scheme::Http, host, port));
130        client
131    }
132
133    /// Create a Client using a proxy with a custom connector and SSL client.
134    pub fn with_proxy_config<C, S>(proxy_config: ProxyConfig<C, S>) -> Client
135    where C: NetworkConnector + Send + Sync + 'static,
136          C::Stream: NetworkStream + Send + Clone,
137          S: SslClient<C::Stream> + Send + Sync + 'static {
138
139        let scheme = proxy_config.scheme;
140        let host = proxy_config.host;
141        let port = proxy_config.port;
142        let proxy = Proxy {
143            proxy: (scheme.clone(), host.clone(), port),
144            connector: proxy_config.connector,
145            ssl: proxy_config.ssl,
146        };
147
148        let mut client = match proxy_config.pool_config {
149            Some(pool_config) => Client::with_connector(Pool::with_connector(pool_config, proxy)),
150            None => Client::with_connector(proxy),
151        };
152        client.proxy = Some((scheme, host, port));
153        client
154    }
155
156    /// Create a new client with a specific connector.
157    pub fn with_connector<C, S>(connector: C) -> Client
158    where C: NetworkConnector<Stream=S> + Send + Sync + 'static, S: NetworkStream + Send {
159        Client::with_protocol(Http11Protocol::with_connector(connector))
160    }
161
162    /// Create a new client with a specific `Protocol`.
163    pub fn with_protocol<P: Protocol + Send + Sync + 'static>(protocol: P) -> Client {
164        Client {
165            protocol: Box::new(protocol),
166            redirect_policy: Default::default(),
167            read_timeout: None,
168            write_timeout: None,
169            proxy: None,
170        }
171    }
172
173    /// Set the RedirectPolicy.
174    pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) {
175        self.redirect_policy = policy;
176    }
177
178    /// Set the read timeout value for all requests.
179    pub fn set_read_timeout(&mut self, dur: Option<Duration>) {
180        self.read_timeout = dur;
181    }
182
183    /// Set the write timeout value for all requests.
184    pub fn set_write_timeout(&mut self, dur: Option<Duration>) {
185        self.write_timeout = dur;
186    }
187
188    /// Build a Get request.
189    pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
190        self.request(Method::Get, url)
191    }
192
193    /// Build a Head request.
194    pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
195        self.request(Method::Head, url)
196    }
197
198    /// Build a Patch request.
199    pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
200        self.request(Method::Patch, url)
201    }
202
203    /// Build a Post request.
204    pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
205        self.request(Method::Post, url)
206    }
207
208    /// Build a Put request.
209    pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
210        self.request(Method::Put, url)
211    }
212
213    /// Build a Delete request.
214    pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
215        self.request(Method::Delete, url)
216    }
217
218
219    /// Build a new request using this Client.
220    pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
221        RequestBuilder {
222            client: self,
223            method: method,
224            url: url.into_url(),
225            body: None,
226            headers: None,
227        }
228    }
229}
230
231impl Default for Client {
232    fn default() -> Client { Client::new() }
233}
234
235/// Options for an individual Request.
236///
237/// One of these will be built for you if you use one of the convenience
238/// methods, such as `get()`, `post()`, etc.
239pub struct RequestBuilder<'a> {
240    client: &'a Client,
241    // We store a result here because it's good to keep RequestBuilder
242    // from being generic, but it is a nicer API to report the error
243    // from `send` (when other errors may be happening, so it already
244    // returns a `Result`). Why's it good to keep it non-generic? It
245    // stops downstream crates having to remonomorphise and recompile
246    // the code, which can take a while, since `send` is fairly large.
247    // (For an extreme example, a tiny crate containing
248    // `hyper_sync::Client::new().get("x").send().unwrap();` took ~4s to
249    // compile with a generic RequestBuilder, but 2s with this scheme,)
250    url: Result<Url, UrlError>,
251    headers: Option<Headers>,
252    method: Method,
253    body: Option<Body<'a>>,
254}
255
256impl<'a> RequestBuilder<'a> {
257
258    /// Set a request body to be sent.
259    pub fn body<B: Into<Body<'a>>>(mut self, body: B) -> RequestBuilder<'a> {
260        self.body = Some(body.into());
261        self
262    }
263
264    /// Add additional headers to the request.
265    pub fn headers(mut self, headers: Headers) -> RequestBuilder<'a> {
266        self.headers = Some(headers);
267        self
268    }
269
270    /// Add an individual new header to the request.
271    pub fn header<H: Header>(mut self, header: H) -> RequestBuilder<'a> {
272        {
273            let headers = match self.headers {
274                Some(ref mut h) => h,
275                None => {
276                    self.headers = Some(Headers::new());
277                    self.headers.as_mut().unwrap()
278                }
279            };
280
281            headers.set(header);
282        }
283        self
284    }
285
286    /// Execute this request and receive a Response back.
287    pub fn send(self) -> ::Result<Response> {
288        let RequestBuilder { client, method, url, headers, body } = self;
289        let mut url = try!(url);
290        trace!("send method={:?}, url={:?}, client={:?}", method, url, client);
291
292        let can_have_body = match method {
293            Method::Get | Method::Head => false,
294            _ => true
295        };
296
297        let mut body = if can_have_body {
298            body
299        } else {
300            None
301        };
302
303        loop {
304            let mut req = {
305                let (host, port) = try!(get_host_and_port(&url));
306                let mut message = try!(client.protocol.new_message(&host, port, url.scheme()));
307                if url.scheme() == "http" && client.proxy.is_some() {
308                    message.set_proxied(true);
309                }
310
311                let mut h = Headers::new();
312                h.set(Host::new(host.to_owned(), port));
313                if let Some(ref headers) = headers {
314                    h.extend(headers.iter());
315                }
316                let headers = h;
317                Request::with_headers_and_message(method.clone(), url.clone(), headers, message)
318            };
319
320            try!(req.set_write_timeout(client.write_timeout));
321            try!(req.set_read_timeout(client.read_timeout));
322
323            match (can_have_body, body.as_ref()) {
324                (true, Some(body)) => match body.size() {
325                    Some(size) => req.headers_mut().set(ContentLength(size)),
326                    None => (), // chunked, Request will add it automatically
327                },
328                (true, None) => req.headers_mut().set(ContentLength(0)),
329                _ => () // neither
330            }
331            let mut streaming = try!(req.start());
332            if let Some(mut rdr) = body.take() {
333                try!(copy(&mut rdr, &mut streaming));
334            }
335            let res = try!(streaming.send());
336            if !res.status.is_redirection() {
337                return Ok(res)
338            }
339            debug!("redirect code {:?} for {}", res.status, url);
340
341            let loc = {
342                // punching borrowck here
343                let loc = match res.headers.get::<Location>() {
344                    Some(loc) => {
345                        Some(url.join(loc))
346                    }
347                    None => {
348                        debug!("no Location header");
349                        // could be 304 Not Modified?
350                        None
351                    }
352                };
353                match loc {
354                    Some(r) => r,
355                    None => return Ok(res)
356                }
357            };
358            url = match loc {
359                Ok(u) => u,
360                Err(e) => {
361                    debug!("Location header had invalid URI: {:?}", e);
362                    return Ok(res);
363                }
364            };
365            match client.redirect_policy {
366                // separate branches because they can't be one
367                RedirectPolicy::FollowAll => (), //continue
368                RedirectPolicy::FollowIf(cond) if cond(&url) => (), //continue
369                _ => return Ok(res),
370            }
371        }
372    }
373}
374
375/// An enum of possible body types for a Request.
376pub enum Body<'a> {
377    /// A Reader does not necessarily know it's size, so it is chunked.
378    ChunkedBody(&'a mut (Read + 'a)),
379    /// For Readers that can know their size, like a `File`.
380    SizedBody(&'a mut (Read + 'a), u64),
381    /// A String has a size, and uses Content-Length.
382    BufBody(&'a [u8] , usize),
383}
384
385impl<'a> Body<'a> {
386    fn size(&self) -> Option<u64> {
387        match *self {
388            Body::SizedBody(_, len) => Some(len),
389            Body::BufBody(_, len) => Some(len as u64),
390            _ => None
391        }
392    }
393}
394
395impl<'a> Read for Body<'a> {
396    #[inline]
397    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
398        match *self {
399            Body::ChunkedBody(ref mut r) => r.read(buf),
400            Body::SizedBody(ref mut r, _) => r.read(buf),
401            Body::BufBody(ref mut r, _) => Read::read(r, buf),
402        }
403    }
404}
405
406impl<'a> Into<Body<'a>> for &'a [u8] {
407    #[inline]
408    fn into(self) -> Body<'a> {
409        Body::BufBody(self, self.len())
410    }
411}
412
413impl<'a> Into<Body<'a>> for &'a str {
414    #[inline]
415    fn into(self) -> Body<'a> {
416        self.as_bytes().into()
417    }
418}
419
420impl<'a> Into<Body<'a>> for &'a String {
421    #[inline]
422    fn into(self) -> Body<'a> {
423        self.as_bytes().into()
424    }
425}
426
427impl<'a, R: Read> From<&'a mut R> for Body<'a> {
428    #[inline]
429    fn from(r: &'a mut R) -> Body<'a> {
430        Body::ChunkedBody(r)
431    }
432}
433
434/// A helper trait to convert common objects into a Url.
435pub trait IntoUrl {
436    /// Consumes the object, trying to return a Url.
437    fn into_url(self) -> Result<Url, UrlError>;
438}
439
440impl IntoUrl for Url {
441    fn into_url(self) -> Result<Url, UrlError> {
442        Ok(self)
443    }
444}
445
446impl<'a> IntoUrl for &'a str {
447    fn into_url(self) -> Result<Url, UrlError> {
448        Url::parse(self)
449    }
450}
451
452impl<'a> IntoUrl for &'a String {
453    fn into_url(self) -> Result<Url, UrlError> {
454        Url::parse(self)
455    }
456}
457
458/// Proxy server configuration with a custom connector and TLS wrapper.
459pub struct ProxyConfig<C, S>
460where C: NetworkConnector + Send + Sync + 'static,
461      C::Stream: NetworkStream + Clone + Send,
462      S: SslClient<C::Stream> + Send + Sync + 'static {
463    scheme: Scheme,
464    host: Cow<'static, str>,
465    port: u16,
466    pool_config: Option<pool::Config>,
467    connector: C,
468    ssl: S,
469}
470
471impl<C, S> ProxyConfig<C, S>
472where C: NetworkConnector + Send + Sync + 'static,
473      C::Stream: NetworkStream + Clone + Send,
474      S: SslClient<C::Stream> + Send + Sync + 'static {
475
476    /// Create a new `ProxyConfig`.
477    #[inline]
478    pub fn new<H: Into<Cow<'static, str>>>(scheme: &str, host: H, port: u16, connector: C, ssl: S) -> ProxyConfig<C, S> {
479        ProxyConfig {
480            scheme: scheme.into(),
481            host: host.into(),
482            port: port,
483            pool_config: Some(pool::Config::default()),
484            connector: connector,
485            ssl: ssl,
486        }
487    }
488
489    /// Change the `pool::Config` for the proxy.
490    ///
491    /// Passing `None` disables the `Pool`.
492    ///
493    /// The default is enabled, with the default `pool::Config`.
494    pub fn set_pool_config(&mut self, pool_config: Option<pool::Config>) {
495        self.pool_config = pool_config;
496    }
497}
498
499/// Behavior regarding how to handle redirects within a Client.
500#[derive(Copy)]
501pub enum RedirectPolicy {
502    /// Don't follow any redirects.
503    FollowNone,
504    /// Follow all redirects.
505    FollowAll,
506    /// Follow a redirect if the contained function returns true.
507    FollowIf(fn(&Url) -> bool),
508}
509
510impl fmt::Debug for RedirectPolicy {
511    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
512        match *self {
513            RedirectPolicy::FollowNone => fmt.write_str("FollowNone"),
514            RedirectPolicy::FollowAll => fmt.write_str("FollowAll"),
515            RedirectPolicy::FollowIf(_) => fmt.write_str("FollowIf"),
516        }
517    }
518}
519
520// This is a hack because of upstream typesystem issues.
521impl Clone for RedirectPolicy {
522    fn clone(&self) -> RedirectPolicy {
523        *self
524    }
525}
526
527impl Default for RedirectPolicy {
528    fn default() -> RedirectPolicy {
529        RedirectPolicy::FollowAll
530    }
531}
532
533
534fn get_host_and_port(url: &Url) -> ::Result<(&str, u16)> {
535    let host = match url.host_str() {
536        Some(host) => host,
537        None => return Err(Error::Uri(UrlError::EmptyHost)),
538    };
539    trace!("host={:?}", host);
540    let port = match url.port_or_known_default() {
541        Some(port) => port,
542        None => return Err(Error::Uri(UrlError::InvalidPort)),
543    };
544    trace!("port={:?}", port);
545    Ok((host, port))
546}
547
548mod scheme {
549
550    #[derive(Clone, PartialEq, Eq, Debug, Hash)]
551    pub enum Scheme {
552        Http,
553        Https,
554        Other(String),
555    }
556
557    impl<'a> From<&'a str> for Scheme {
558        fn from(s: &'a str) -> Scheme {
559            match s {
560                "http" => Scheme::Http,
561                "https" => Scheme::Https,
562                s => Scheme::Other(String::from(s)),
563            }
564        }
565    }
566
567    impl AsRef<str> for Scheme {
568        fn as_ref(&self) -> &str {
569            match *self {
570                Scheme::Http => "http",
571                Scheme::Https => "https",
572                Scheme::Other(ref s) => s,
573            }
574        }
575    }
576
577}
578
579#[cfg(test)]
580mod tests {
581    use std::io::Read;
582    use header::Server;
583    use http::h1::Http11Message;
584    use mock::{MockStream, MockSsl};
585    use super::{Client, RedirectPolicy};
586    use super::scheme::Scheme;
587    use super::proxy::Proxy;
588    use super::pool::Pool;
589    use url::Url;
590
591    mock_connector!(MockRedirectPolicy {
592        "http://127.0.0.1" =>       "HTTP/1.1 301 Redirect\r\n\
593                                     Location: http://127.0.0.2\r\n\
594                                     Server: mock1\r\n\
595                                     \r\n\
596                                    "
597        "http://127.0.0.2" =>       "HTTP/1.1 302 Found\r\n\
598                                     Location: https://127.0.0.3\r\n\
599                                     Server: mock2\r\n\
600                                     \r\n\
601                                    "
602        "https://127.0.0.3" =>      "HTTP/1.1 200 OK\r\n\
603                                     Server: mock3\r\n\
604                                     \r\n\
605                                    "
606    });
607
608
609    #[test]
610    fn test_proxy() {
611        use super::pool::PooledStream;
612        type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>;
613        mock_connector!(ProxyConnector {
614            b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
615        });
616        let tunnel = Proxy {
617            connector: ProxyConnector,
618            proxy: (Scheme::Http, "example.proxy".into(), 8008),
619            ssl: MockSsl,
620        };
621        let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel));
622        client.proxy = Some((Scheme::Http, "example.proxy".into(), 8008));
623        let mut dump = vec![];
624        client.get("http://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap();
625
626        let box_message = client.protocol.new_message("127.0.0.1", 80, "http").unwrap();
627        let message = box_message.downcast::<Http11Message>().unwrap();
628        let stream =  message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_normal().unwrap();
629
630        let s = ::std::str::from_utf8(&stream.write).unwrap();
631        let request_line = "GET http://127.0.0.1/foo/bar HTTP/1.1\r\n";
632        assert!(s.starts_with(request_line), "{:?} doesn't start with {:?}", s, request_line);
633        assert!(s.contains("Host: 127.0.0.1\r\n"));
634    }
635
636    #[test]
637    fn test_proxy_tunnel() {
638        use super::pool::PooledStream;
639        type MessageStream = PooledStream<super::proxy::Proxied<MockStream, MockStream>>;
640
641        mock_connector!(ProxyConnector {
642            b"HTTP/1.1 200 OK\r\n\r\n",
643            b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"
644        });
645        let tunnel = Proxy {
646            connector: ProxyConnector,
647            proxy: (Scheme::Http, "example.proxy".into(), 8008),
648            ssl: MockSsl,
649        };
650        let mut client = Client::with_connector(Pool::with_connector(Default::default(), tunnel));
651        client.proxy = Some((Scheme::Http, "example.proxy".into(), 8008));
652        let mut dump = vec![];
653        client.get("https://127.0.0.1/foo/bar").send().unwrap().read_to_end(&mut dump).unwrap();
654
655        let box_message = client.protocol.new_message("127.0.0.1", 443, "https").unwrap();
656        let message = box_message.downcast::<Http11Message>().unwrap();
657        let stream = message.into_inner().downcast::<MessageStream>().unwrap().into_inner().into_tunneled().unwrap();
658
659        let s = ::std::str::from_utf8(&stream.write).unwrap();
660        let connect_line = "CONNECT 127.0.0.1:443 HTTP/1.1\r\nHost: 127.0.0.1:443\r\n\r\n";
661        assert_eq!(&s[..connect_line.len()], connect_line);
662
663        let s = &s[connect_line.len()..];
664        let request_line = "GET /foo/bar HTTP/1.1\r\n";
665        assert_eq!(&s[..request_line.len()], request_line);
666        assert!(s.contains("Host: 127.0.0.1\r\n"));
667    }
668
669    #[test]
670    fn test_redirect_followall() {
671        let mut client = Client::with_connector(MockRedirectPolicy);
672        client.set_redirect_policy(RedirectPolicy::FollowAll);
673
674        let res = client.get("http://127.0.0.1").send().unwrap();
675        assert_eq!(res.headers.get(), Some(&Server::new("mock3")));
676    }
677
678    #[test]
679    fn test_redirect_dontfollow() {
680        let mut client = Client::with_connector(MockRedirectPolicy);
681        client.set_redirect_policy(RedirectPolicy::FollowNone);
682        let res = client.get("http://127.0.0.1").send().unwrap();
683        assert_eq!(res.headers.get(), Some(&Server::new("mock1")));
684    }
685
686    #[test]
687    fn test_redirect_followif() {
688        fn follow_if(url: &Url) -> bool {
689            !url.as_str().contains("127.0.0.3")
690        }
691        let mut client = Client::with_connector(MockRedirectPolicy);
692        client.set_redirect_policy(RedirectPolicy::FollowIf(follow_if));
693        let res = client.get("http://127.0.0.1").send().unwrap();
694        assert_eq!(res.headers.get(), Some(&Server::new("mock2")));
695    }
696
697    mock_connector!(Issue640Connector {
698        b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\n",
699        b"GET",
700        b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n",
701        b"HEAD",
702        b"HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\n",
703        b"POST"
704    });
705
706    // see issue #640
707    #[test]
708    fn test_head_response_body_keep_alive() {
709        let client = Client::with_connector(Pool::with_connector(Default::default(), Issue640Connector));
710
711        let mut s = String::new();
712        client.get("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
713        assert_eq!(s, "GET");
714
715        let mut s = String::new();
716        client.head("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
717        assert_eq!(s, "");
718
719        let mut s = String::new();
720        client.post("http://127.0.0.1").send().unwrap().read_to_string(&mut s).unwrap();
721        assert_eq!(s, "POST");
722    }
723
724    #[test]
725    fn test_request_body_error_is_returned() {
726        mock_connector!(Connector {
727            b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\n",
728            b"HELLO"
729        });
730
731        struct BadBody;
732
733        impl ::std::io::Read for BadBody {
734            fn read(&mut self, _buf: &mut [u8]) -> ::std::io::Result<usize> {
735                Err(::std::io::Error::new(::std::io::ErrorKind::Other, "BadBody read"))
736            }
737        }
738
739        let client = Client::with_connector(Connector);
740        let err = client.post("http://127.0.0.1").body(&mut BadBody).send().unwrap_err();
741        assert_eq!(err.to_string(), "BadBody read");
742    }
743}