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