1use 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
89pub 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 pub fn new() -> Client {
115 Client::with_pool_config(Default::default())
116 }
117
118 pub fn with_pool_config(config: pool::Config) -> Client {
120 Client::with_connector(Pool::new(config))
121 }
122
123 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 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 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 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 pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) {
175 self.redirect_policy = policy;
176 }
177
178 pub fn set_read_timeout(&mut self, dur: Option<Duration>) {
180 self.read_timeout = dur;
181 }
182
183 pub fn set_write_timeout(&mut self, dur: Option<Duration>) {
185 self.write_timeout = dur;
186 }
187
188 pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
190 self.request(Method::Get, url)
191 }
192
193 pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
195 self.request(Method::Head, url)
196 }
197
198 pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
200 self.request(Method::Patch, url)
201 }
202
203 pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
205 self.request(Method::Post, url)
206 }
207
208 pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
210 self.request(Method::Put, url)
211 }
212
213 pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
215 self.request(Method::Delete, url)
216 }
217
218
219 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
235pub struct RequestBuilder<'a> {
240 client: &'a Client,
241 url: Result<Url, UrlError>,
251 headers: Option<Headers>,
252 method: Method,
253 body: Option<Body<'a>>,
254}
255
256impl<'a> RequestBuilder<'a> {
257
258 pub fn body<B: Into<Body<'a>>>(mut self, body: B) -> RequestBuilder<'a> {
260 self.body = Some(body.into());
261 self
262 }
263
264 pub fn headers(mut self, headers: Headers) -> RequestBuilder<'a> {
266 self.headers = Some(headers);
267 self
268 }
269
270 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 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 => (), },
328 (true, None) => req.headers_mut().set(ContentLength(0)),
329 _ => () }
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 let loc = match res.headers.get::<Location>() {
344 Some(loc) => {
345 Some(url.join(loc))
346 }
347 None => {
348 debug!("no Location header");
349 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 RedirectPolicy::FollowAll => (), RedirectPolicy::FollowIf(cond) if cond(&url) => (), _ => return Ok(res),
370 }
371 }
372 }
373}
374
375pub enum Body<'a> {
377 ChunkedBody(&'a mut (Read + 'a)),
379 SizedBody(&'a mut (Read + 'a), u64),
381 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
434pub trait IntoUrl {
436 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
458pub 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 #[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 pub fn set_pool_config(&mut self, pool_config: Option<pool::Config>) {
495 self.pool_config = pool_config;
496 }
497}
498
499#[derive(Copy)]
501pub enum RedirectPolicy {
502 FollowNone,
504 FollowAll,
506 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
520impl 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 #[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}