Merge pull request #182 from hyperium/client
feat(client): add a new Client struct with super powers
This commit is contained in:
		
							
								
								
									
										32
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								README.md
									
									
									
									
									
								
							| @@ -42,18 +42,18 @@ Client: | |||||||
|  |  | ||||||
| ```rust | ```rust | ||||||
| fn main() { | fn main() { | ||||||
|  |     // Create a client. | ||||||
|  |     let mut client = Client::new(); | ||||||
|  |  | ||||||
|     // Creating an outgoing request. |     // Creating an outgoing request. | ||||||
|     let mut req = Request::get(Url::parse("http://www.gooogle.com/").unwrap()).unwrap(); |     let mut res = client.get("http://www.gooogle.com/") | ||||||
|  |         // set a header | ||||||
|  |         .header(Connection(vec![Close])) | ||||||
|  |         // let 'er go! | ||||||
|  |         .send(); | ||||||
|  |  | ||||||
|     // Setting a header. |  | ||||||
|     req.headers_mut().set(Connection(vec![Close])); |  | ||||||
|  |  | ||||||
|     // Start the Request, writing headers and starting streaming. |  | ||||||
|     let res = req.start().unwrap() |  | ||||||
|         // Send the Request. |  | ||||||
|         .send().unwrap() |  | ||||||
|     // Read the Response. |     // Read the Response. | ||||||
|         .read_to_string().unwrap(); |     let body = res.read_to_string().unwrap(); | ||||||
|  |  | ||||||
|     println!("Response: {}", res); |     println!("Response: {}", res); | ||||||
| } | } | ||||||
| @@ -64,22 +64,20 @@ fn main() { | |||||||
| [Client Bench:](./benches/client.rs) | [Client Bench:](./benches/client.rs) | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| running 3 tests | running 3 tests | ||||||
| test bench_curl  ... bench:    298416 ns/iter (+/- 132455) | test bench_curl  ... bench:    400253 ns/iter (+/- 143539) | ||||||
| test bench_http  ... bench:    292725 ns/iter (+/- 167575) | test bench_hyper ... bench:    181703 ns/iter (+/- 46529) | ||||||
| test bench_hyper ... bench:    222819 ns/iter (+/- 86615) |  | ||||||
|  |  | ||||||
| test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured | test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| [Mock Client Bench:](./benches/client_mock_tcp.rs) | [Mock Client Bench:](./benches/client_mock_tcp.rs) | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
| running 3 tests | running 3 tests | ||||||
| test bench_mock_curl  ... bench:     25254 ns/iter (+/- 2113) | test bench_mock_curl  ... bench:     53987 ns/iter (+/- 1735) | ||||||
| test bench_mock_http  ... bench:     43585 ns/iter (+/- 1206) | test bench_mock_http  ... bench:     43569 ns/iter (+/- 1409) | ||||||
| test bench_mock_hyper ... bench:     27153 ns/iter (+/- 2227) | test bench_mock_hyper ... bench:     20996 ns/iter (+/- 1742) | ||||||
|  |  | ||||||
| test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured | test result: ok. 0 passed; 0 failed; 0 ignored; 3 measured | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -8,6 +8,10 @@ extern crate test; | |||||||
| use std::fmt::{mod, Show}; | use std::fmt::{mod, Show}; | ||||||
| use std::io::net::ip::Ipv4Addr; | use std::io::net::ip::Ipv4Addr; | ||||||
| use hyper::server::{Request, Response, Server}; | use hyper::server::{Request, Response, Server}; | ||||||
|  | use hyper::method::Method::Get; | ||||||
|  | use hyper::header::Headers; | ||||||
|  | use hyper::Client; | ||||||
|  | use hyper::client::RequestBuilder; | ||||||
|  |  | ||||||
| fn listen() -> hyper::server::Listening { | fn listen() -> hyper::server::Listening { | ||||||
|     let server = Server::http(Ipv4Addr(127, 0, 0, 1), 0); |     let server = Server::http(Ipv4Addr(127, 0, 0, 1), 0); | ||||||
| @@ -22,9 +26,10 @@ macro_rules! try_return( | |||||||
|         } |         } | ||||||
|     }}) |     }}) | ||||||
|  |  | ||||||
| fn handle(_: Request, res: Response) { | fn handle(_r: Request, res: Response) { | ||||||
|  |     static BODY: &'static [u8] = b"Benchmarking hyper vs others!"; | ||||||
|     let mut res = try_return!(res.start()); |     let mut res = try_return!(res.start()); | ||||||
|     try_return!(res.write(b"Benchmarking hyper vs others!")) |     try_return!(res.write(BODY)) | ||||||
|     try_return!(res.end()); |     try_return!(res.end()); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -41,7 +46,7 @@ fn bench_curl(b: &mut test::Bencher) { | |||||||
|             .exec() |             .exec() | ||||||
|             .unwrap() |             .unwrap() | ||||||
|     }); |     }); | ||||||
|     listening.close().unwrap() |     listening.close().unwrap(); | ||||||
| } | } | ||||||
|  |  | ||||||
| #[deriving(Clone)] | #[deriving(Clone)] | ||||||
| @@ -67,17 +72,17 @@ fn bench_hyper(b: &mut test::Bencher) { | |||||||
|     let mut listening = listen(); |     let mut listening = listen(); | ||||||
|     let s = format!("http://{}/", listening.socket); |     let s = format!("http://{}/", listening.socket); | ||||||
|     let url = s.as_slice(); |     let url = s.as_slice(); | ||||||
|  |     let mut client = Client::new(); | ||||||
|  |     let mut headers = Headers::new(); | ||||||
|  |     headers.set(Foo); | ||||||
|     b.iter(|| { |     b.iter(|| { | ||||||
|         let mut req = hyper::client::Request::get(hyper::Url::parse(url).unwrap()).unwrap(); |         client.get(url).header(Foo).send().unwrap().read_to_string().unwrap(); | ||||||
|         req.headers_mut().set(Foo); |  | ||||||
|  |  | ||||||
|         req.start().unwrap() |  | ||||||
|             .send().unwrap() |  | ||||||
|             .read_to_string().unwrap() |  | ||||||
|     }); |     }); | ||||||
|     listening.close().unwrap() |     listening.close().unwrap() | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | doesn't handle keep-alive properly... | ||||||
| #[bench] | #[bench] | ||||||
| fn bench_http(b: &mut test::Bencher) { | fn bench_http(b: &mut test::Bencher) { | ||||||
|     let mut listening = listen(); |     let mut listening = listen(); | ||||||
| @@ -92,9 +97,10 @@ fn bench_http(b: &mut test::Bencher) { | |||||||
|         // cant unwrap because Err contains RequestWriter, which does not implement Show |         // cant unwrap because Err contains RequestWriter, which does not implement Show | ||||||
|         let mut res = match req.read_response() { |         let mut res = match req.read_response() { | ||||||
|             Ok(res) => res, |             Ok(res) => res, | ||||||
|             Err(..) => panic!("http response failed") |             Err((_, ioe)) => panic!("http response failed = {}", ioe) | ||||||
|         }; |         }; | ||||||
|         res.read_to_string().unwrap(); |         res.read_to_string().unwrap(); | ||||||
|     }); |     }); | ||||||
|     listening.close().unwrap() |     listening.close().unwrap() | ||||||
| } | } | ||||||
|  | */ | ||||||
|   | |||||||
| @@ -9,12 +9,13 @@ use test::Bencher; | |||||||
| use std::io::net::ip::{SocketAddr, Ipv4Addr}; | use std::io::net::ip::{SocketAddr, Ipv4Addr}; | ||||||
|  |  | ||||||
| use http::server::Server; | use http::server::Server; | ||||||
|  | use hyper::method::Method::Get; | ||||||
| use hyper::server::{Request, Response}; | use hyper::server::{Request, Response}; | ||||||
|  |  | ||||||
| static PHRASE: &'static [u8] = b"Benchmarking hyper vs others!"; | static PHRASE: &'static [u8] = b"Benchmarking hyper vs others!"; | ||||||
|  |  | ||||||
| fn request(url: hyper::Url) { | fn request(url: hyper::Url) { | ||||||
|     let req = hyper::client::Request::get(url).unwrap(); |     let req = hyper::client::Request::new(Get, url).unwrap(); | ||||||
|     req.start().unwrap().send().unwrap().read_to_string().unwrap(); |     req.start().unwrap().send().unwrap().read_to_string().unwrap(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,8 +4,7 @@ use std::os; | |||||||
| use std::io::stdout; | use std::io::stdout; | ||||||
| use std::io::util::copy; | use std::io::util::copy; | ||||||
|  |  | ||||||
| use hyper::Url; | use hyper::Client; | ||||||
| use hyper::client::Request; |  | ||||||
|  |  | ||||||
| fn main() { | fn main() { | ||||||
|     let args = os::args(); |     let args = os::args(); | ||||||
| @@ -17,26 +16,17 @@ fn main() { | |||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let url = match Url::parse(args[1].as_slice()) { |     let url = &*args[1]; | ||||||
|         Ok(url) => { |  | ||||||
|             println!("GET {}...", url) |  | ||||||
|             url |  | ||||||
|         }, |  | ||||||
|         Err(e) => panic!("Invalid URL: {}", e) |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|  |     let mut client = Client::new(); | ||||||
|  |  | ||||||
|     let req = match Request::get(url) { |     let mut res = match client.get(url).send() { | ||||||
|         Ok(req) => req, |         Ok(res) => res, | ||||||
|         Err(err) => panic!("Failed to connect: {}", err) |         Err(err) => panic!("Failed to connect: {}", err) | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let mut res = req |  | ||||||
|         .start().unwrap() // failure: Error writing Headers |  | ||||||
|         .send().unwrap(); // failure: Error reading Response head. |  | ||||||
|  |  | ||||||
|     println!("Response: {}", res.status); |     println!("Response: {}", res.status); | ||||||
|     println!("{}", res.headers); |     println!("Headers:\n{}", res.headers); | ||||||
|     match copy(&mut res, &mut stdout()) { |     match copy(&mut res, &mut stdout()) { | ||||||
|         Ok(..) => (), |         Ok(..) => (), | ||||||
|         Err(e) => panic!("Stream failure: {}", e) |         Err(e) => panic!("Stream failure: {}", e) | ||||||
|   | |||||||
| @@ -1,7 +1,399 @@ | |||||||
| //! HTTP Client | //! HTTP Client | ||||||
|  | //! | ||||||
|  | //! # Usage | ||||||
|  | //! | ||||||
|  | //! The `Client` API is designed for most people to make HTTP requests. | ||||||
|  | //! It utilizes the lower level `Request` API. | ||||||
|  | //! | ||||||
|  | //! ```no_run | ||||||
|  | //! use hyper::Client; | ||||||
|  | //! | ||||||
|  | //! let mut client = Client::new(); | ||||||
|  | //! | ||||||
|  | //! let mut res = client.get("http://example.domain").send().unwrap(); | ||||||
|  | //! assert_eq!(res.status, hyper::Ok); | ||||||
|  | //! ``` | ||||||
|  | //! | ||||||
|  | //! The returned value from is a `Response`, which provides easy access | ||||||
|  | //! to the `status`, the `headers`, and the response body via the `Writer` | ||||||
|  | //! trait. | ||||||
|  | use std::default::Default; | ||||||
|  | use std::io::IoResult; | ||||||
|  | use std::io::util::copy; | ||||||
|  | use std::iter::Extend; | ||||||
|  |  | ||||||
|  | use url::UrlParser; | ||||||
|  | use url::ParseError as UrlError; | ||||||
|  |  | ||||||
|  | use openssl::ssl::VerifyCallback; | ||||||
|  |  | ||||||
|  | use header::{Headers, Header, HeaderFormat}; | ||||||
|  | use header::common::{ContentLength, Location}; | ||||||
|  | use method::Method; | ||||||
|  | use net::{NetworkConnector, NetworkStream, HttpConnector}; | ||||||
|  | use status::StatusClass::Redirection; | ||||||
|  | use {Url, Port, HttpResult}; | ||||||
|  | use HttpError::HttpUriError; | ||||||
|  |  | ||||||
| pub use self::request::Request; | pub use self::request::Request; | ||||||
| pub use self::response::Response; | pub use self::response::Response; | ||||||
|  |  | ||||||
| pub mod request; | pub mod request; | ||||||
| pub mod response; | pub mod response; | ||||||
|  |  | ||||||
|  | /// A Client to use additional features with Requests. | ||||||
|  | /// | ||||||
|  | /// Clients can handle things such as: redirect policy. | ||||||
|  | pub struct Client<C> { | ||||||
|  |     connector: C, | ||||||
|  |     redirect_policy: RedirectPolicy, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Client<HttpConnector> { | ||||||
|  |  | ||||||
|  |     /// Create a new Client. | ||||||
|  |     pub fn new() -> Client<HttpConnector> { | ||||||
|  |         Client::with_connector(HttpConnector(None)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Set the SSL verifier callback for use with OpenSSL. | ||||||
|  |     pub fn set_ssl_verifier(&mut self, verifier: VerifyCallback) { | ||||||
|  |         self.connector = HttpConnector(Some(verifier)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<C: NetworkConnector<S>, S: NetworkStream> Client<C> { | ||||||
|  |  | ||||||
|  |     /// Create a new client with a specific connector. | ||||||
|  |     pub fn with_connector(connector: C) -> Client<C> { | ||||||
|  |         Client { | ||||||
|  |             connector: connector, | ||||||
|  |             redirect_policy: Default::default() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Set the RedirectPolicy. | ||||||
|  |     pub fn set_redirect_policy(&mut self, policy: RedirectPolicy) { | ||||||
|  |         self.redirect_policy = policy; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute a Get request. | ||||||
|  |     pub fn get<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         self.request(Method::Get, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute a Head request. | ||||||
|  |     pub fn head<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         self.request(Method::Head, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute a Post request. | ||||||
|  |     pub fn post<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         self.request(Method::Post, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute a Put request. | ||||||
|  |     pub fn put<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         self.request(Method::Put, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute a Delete request. | ||||||
|  |     pub fn delete<U: IntoUrl>(&mut self, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         self.request(Method::Delete, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /// Build a new request using this Client. | ||||||
|  |     pub fn request<U: IntoUrl>(&mut self, method: Method, url: U) -> RequestBuilder<U, C, S> { | ||||||
|  |         RequestBuilder { | ||||||
|  |             client: self, | ||||||
|  |             method: method, | ||||||
|  |             url: url, | ||||||
|  |             body: None, | ||||||
|  |             headers: None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Options for an individual Request. | ||||||
|  | /// | ||||||
|  | /// One of these will be built for you if you use one of the convenience | ||||||
|  | /// methods, such as `get()`, `post()`, etc. | ||||||
|  | pub struct RequestBuilder<'a, U: IntoUrl, C: NetworkConnector<S> + 'a, S: NetworkStream> { | ||||||
|  |     client: &'a mut Client<C>, | ||||||
|  |     url: U, | ||||||
|  |     headers: Option<Headers>, | ||||||
|  |     method: Method, | ||||||
|  |     body: Option<Body<'a>>, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a, U: IntoUrl, C: NetworkConnector<S>, S: NetworkStream> RequestBuilder<'a, U, C, S> { | ||||||
|  |  | ||||||
|  |     /// Set a request body to be sent. | ||||||
|  |     pub fn body<B: IntoBody<'a>>(mut self, body: B) -> RequestBuilder<'a, U, C, S> { | ||||||
|  |         self.body = Some(body.into_body()); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Add additional headers to the request. | ||||||
|  |     pub fn headers(mut self, headers: Headers) -> RequestBuilder<'a, U, C, S> { | ||||||
|  |         self.headers = Some(headers); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Add an individual new header to the request. | ||||||
|  |     pub fn header<H: Header + HeaderFormat>(mut self, header: H) -> RequestBuilder<'a, U, C, S> { | ||||||
|  |         { | ||||||
|  |             let mut headers = match self.headers { | ||||||
|  |                 Some(ref mut h) => h, | ||||||
|  |                 None => { | ||||||
|  |                     self.headers = Some(Headers::new()); | ||||||
|  |                     self.headers.as_mut().unwrap() | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             headers.set(header); | ||||||
|  |         } | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Execute this request and receive a Response back. | ||||||
|  |     pub fn send(self) -> HttpResult<Response> { | ||||||
|  |         let RequestBuilder { client, method, url, headers, body } = self; | ||||||
|  |         let mut url = try!(url.into_url()); | ||||||
|  |         debug!("client.request {} {}", method, url); | ||||||
|  |  | ||||||
|  |         let can_have_body = match &method { | ||||||
|  |             &Method::Get | &Method::Head => false, | ||||||
|  |             _ => true | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         let mut body = if can_have_body { | ||||||
|  |             body.map(|b| b.into_body()) | ||||||
|  |         } else { | ||||||
|  |              None | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         loop { | ||||||
|  |             let mut req = try!(Request::with_connector(method.clone(), url.clone(), &mut client.connector)); | ||||||
|  |             headers.as_ref().map(|headers| req.headers_mut().extend(headers.iter())); | ||||||
|  |  | ||||||
|  |             match (can_have_body, body.as_ref()) { | ||||||
|  |                 (true, Some(ref body)) => match body.size() { | ||||||
|  |                     Some(size) => req.headers_mut().set(ContentLength(size)), | ||||||
|  |                     None => (), // chunked, Request will add it automatically | ||||||
|  |                 }, | ||||||
|  |                 (true, None) => req.headers_mut().set(ContentLength(0)), | ||||||
|  |                 _ => () // neither | ||||||
|  |             } | ||||||
|  |             let mut streaming = try!(req.start()); | ||||||
|  |             body.take().map(|mut rdr| copy(&mut rdr, &mut streaming)); | ||||||
|  |             let res = try!(streaming.send()); | ||||||
|  |             if res.status.class() != Redirection { | ||||||
|  |                 return Ok(res) | ||||||
|  |             } | ||||||
|  |             debug!("redirect code {} for {}", res.status, url); | ||||||
|  |  | ||||||
|  |             let loc = { | ||||||
|  |                 // punching borrowck here | ||||||
|  |                 let loc = match res.headers.get::<Location>() { | ||||||
|  |                     Some(&Location(ref loc)) => { | ||||||
|  |                         Some(UrlParser::new().base_url(&url).parse(loc[])) | ||||||
|  |                     } | ||||||
|  |                     None => { | ||||||
|  |                         debug!("no Location header"); | ||||||
|  |                         // could be 304 Not Modified? | ||||||
|  |                         None | ||||||
|  |                     } | ||||||
|  |                 }; | ||||||
|  |                 match loc { | ||||||
|  |                     Some(r) => r, | ||||||
|  |                     None => return Ok(res) | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             url = match loc { | ||||||
|  |                 Ok(u) => { | ||||||
|  |                     inspect!("Location", u) | ||||||
|  |                 }, | ||||||
|  |                 Err(e) => { | ||||||
|  |                     debug!("Location header had invalid URI: {}", e); | ||||||
|  |                     return Ok(res); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  |             match client.redirect_policy { | ||||||
|  |                 // separate branches because they cant be one | ||||||
|  |                 RedirectPolicy::FollowAll => (), //continue | ||||||
|  |                 RedirectPolicy::FollowIf(cond) if cond(&url) => (), //continue | ||||||
|  |                 _ => return Ok(res), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// A helper trait to allow overloading of the body parameter. | ||||||
|  | pub trait IntoBody<'a> { | ||||||
|  |     /// Consumes self into an instance of `Body`. | ||||||
|  |     fn into_body(self) -> Body<'a>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// The target enum for the IntoBody trait. | ||||||
|  | pub enum Body<'a> { | ||||||
|  |     /// A Reader does not necessarily know it's size, so it is chunked. | ||||||
|  |     ChunkedBody(&'a mut (Reader + 'a)), | ||||||
|  |     /// For Readers that can know their size, like a `File`. | ||||||
|  |     SizedBody(&'a mut (Reader + 'a), uint), | ||||||
|  |     /// A String has a size, and uses Content-Length. | ||||||
|  |     BufBody(&'a [u8] , uint), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> Body<'a> { | ||||||
|  |     fn size(&self) -> Option<uint> { | ||||||
|  |         match *self { | ||||||
|  |             Body::SizedBody(_, len) | Body::BufBody(_, len) => Some(len), | ||||||
|  |             _ => None | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> Reader for Body<'a> { | ||||||
|  |     #[inline] | ||||||
|  |     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { | ||||||
|  |         match *self { | ||||||
|  |             Body::ChunkedBody(ref mut r) => r.read(buf), | ||||||
|  |             Body::SizedBody(ref mut r, _) => r.read(buf), | ||||||
|  |             Body::BufBody(ref mut r, _) => r.read(buf), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // To allow someone to pass a `Body::SizedBody()` themselves. | ||||||
|  | impl<'a> IntoBody<'a> for Body<'a> { | ||||||
|  |     #[inline] | ||||||
|  |     fn into_body(self) -> Body<'a> { | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> IntoBody<'a> for &'a [u8] { | ||||||
|  |     #[inline] | ||||||
|  |     fn into_body(self) -> Body<'a> { | ||||||
|  |         Body::BufBody(self, self.len()) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> IntoBody<'a> for &'a str { | ||||||
|  |     #[inline] | ||||||
|  |     fn into_body(self) -> Body<'a> { | ||||||
|  |         self.as_bytes().into_body() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a, R: Reader> IntoBody<'a> for &'a mut R { | ||||||
|  |     #[inline] | ||||||
|  |     fn into_body(self) -> Body<'a> { | ||||||
|  |         Body::ChunkedBody(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// A helper trait to convert common objects into a Url. | ||||||
|  | pub trait IntoUrl { | ||||||
|  |     /// Consumes the object, trying to return a Url. | ||||||
|  |     fn into_url(self) -> Result<Url, UrlError>; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl IntoUrl for Url { | ||||||
|  |     fn into_url(self) -> Result<Url, UrlError> { | ||||||
|  |         Ok(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<'a> IntoUrl for &'a str { | ||||||
|  |     fn into_url(self) -> Result<Url, UrlError> { | ||||||
|  |         Url::parse(self) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /// Behavior regarding how to handle redirects within a Client. | ||||||
|  | #[deriving(Copy, Clone)] | ||||||
|  | pub enum RedirectPolicy { | ||||||
|  |     /// Don't follow any redirects. | ||||||
|  |     FollowNone, | ||||||
|  |     /// Follow all redirects. | ||||||
|  |     FollowAll, | ||||||
|  |     /// Follow a redirect if the contained function returns true. | ||||||
|  |     FollowIf(fn(&Url) -> bool), | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Default for RedirectPolicy { | ||||||
|  |     fn default() -> RedirectPolicy { | ||||||
|  |         RedirectPolicy::FollowAll | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | fn get_host_and_port(url: &Url) -> HttpResult<(String, Port)> { | ||||||
|  |     let host = match url.serialize_host() { | ||||||
|  |         Some(host) => host, | ||||||
|  |         None => return Err(HttpUriError(UrlError::EmptyHost)) | ||||||
|  |     }; | ||||||
|  |     debug!("host={}", host); | ||||||
|  |     let port = match url.port_or_default() { | ||||||
|  |         Some(port) => port, | ||||||
|  |         None => return Err(HttpUriError(UrlError::InvalidPort)) | ||||||
|  |     }; | ||||||
|  |     debug!("port={}", port); | ||||||
|  |     Ok((host, port)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use header::common::Server; | ||||||
|  |     use super::{Client, RedirectPolicy}; | ||||||
|  |     use url::Url; | ||||||
|  |  | ||||||
|  |     mock_connector!(MockRedirectPolicy { | ||||||
|  |         "http://127.0.0.1" =>       "HTTP/1.1 301 Redirect\r\n\ | ||||||
|  |                                      Location: http://127.0.0.2\r\n\ | ||||||
|  |                                      Server: mock1\r\n\ | ||||||
|  |                                      \r\n\ | ||||||
|  |                                     " | ||||||
|  |         "http://127.0.0.2" =>       "HTTP/1.1 302 Found\r\n\ | ||||||
|  |                                      Location: https://127.0.0.3\r\n\ | ||||||
|  |                                      Server: mock2\r\n\ | ||||||
|  |                                      \r\n\ | ||||||
|  |                                     " | ||||||
|  |         "https://127.0.0.3" =>      "HTTP/1.1 200 OK\r\n\ | ||||||
|  |                                      Server: mock3\r\n\ | ||||||
|  |                                      \r\n\ | ||||||
|  |                                     " | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_redirect_followall() { | ||||||
|  |         let mut client = Client::with_connector(MockRedirectPolicy); | ||||||
|  |         client.set_redirect_policy(RedirectPolicy::FollowAll); | ||||||
|  |  | ||||||
|  |         let res = client.get("http://127.0.0.1").send().unwrap(); | ||||||
|  |         assert_eq!(res.headers.get(), Some(&Server("mock3".into_string()))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_redirect_dontfollow() { | ||||||
|  |         let mut client = Client::with_connector(MockRedirectPolicy); | ||||||
|  |         client.set_redirect_policy(RedirectPolicy::FollowNone); | ||||||
|  |         let res = client.get("http://127.0.0.1").send().unwrap(); | ||||||
|  |         assert_eq!(res.headers.get(), Some(&Server("mock1".into_string()))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_redirect_followif() { | ||||||
|  |         fn follow_if(url: &Url) -> bool { | ||||||
|  |             !url.serialize()[].contains("127.0.0.3") | ||||||
|  |         } | ||||||
|  |         let mut client = Client::with_connector(MockRedirectPolicy); | ||||||
|  |         client.set_redirect_policy(RedirectPolicy::FollowIf(follow_if)); | ||||||
|  |         let res = client.get("http://127.0.0.1").send().unwrap(); | ||||||
|  |         assert_eq!(res.headers.get(), Some(&Server("mock2".into_string()))); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|   | |||||||
| @@ -8,12 +8,11 @@ use method::Method::{Get, Post, Delete, Put, Patch, Head, Options}; | |||||||
| use header::Headers; | use header::Headers; | ||||||
| use header::common::{mod, Host}; | use header::common::{mod, Host}; | ||||||
| use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming}; | use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming}; | ||||||
| use HttpError::HttpUriError; |  | ||||||
| use http::{HttpWriter, LINE_ENDING}; | use http::{HttpWriter, LINE_ENDING}; | ||||||
| use http::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; | use http::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; | ||||||
| use version; | use version; | ||||||
| use HttpResult; | use HttpResult; | ||||||
| use client::Response; | use client::{Response, get_host_and_port}; | ||||||
|  |  | ||||||
|  |  | ||||||
| /// A client request to a remote server. | /// A client request to a remote server. | ||||||
| @@ -42,23 +41,14 @@ impl<W> Request<W> { | |||||||
| impl Request<Fresh> { | impl Request<Fresh> { | ||||||
|     /// Create a new client request. |     /// Create a new client request. | ||||||
|     pub fn new(method: method::Method, url: Url) -> HttpResult<Request<Fresh>> { |     pub fn new(method: method::Method, url: Url) -> HttpResult<Request<Fresh>> { | ||||||
|         let mut conn = HttpConnector; |         let mut conn = HttpConnector(None); | ||||||
|         Request::with_connector(method, url, &mut conn) |         Request::with_connector(method, url, &mut conn) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Create a new client request with a specific underlying NetworkStream. |     /// Create a new client request with a specific underlying NetworkStream. | ||||||
|     pub fn with_connector<C: NetworkConnector<S>, S: NetworkStream>(method: method::Method, url: Url, connector: &mut C) -> HttpResult<Request<Fresh>> { |     pub fn with_connector<C: NetworkConnector<S>, S: NetworkStream>(method: method::Method, url: Url, connector: &mut C) -> HttpResult<Request<Fresh>> { | ||||||
|         debug!("{} {}", method, url); |         debug!("{} {}", method, url); | ||||||
|         let host = match url.serialize_host() { |         let (host, port) = try!(get_host_and_port(&url)); | ||||||
|             Some(host) => host, |  | ||||||
|             None => return Err(HttpUriError) |  | ||||||
|         }; |  | ||||||
|         debug!("host={}", host); |  | ||||||
|         let port = match url.port_or_default() { |  | ||||||
|             Some(port) => port, |  | ||||||
|             None => return Err(HttpUriError) |  | ||||||
|         }; |  | ||||||
|         debug!("port={}", port); |  | ||||||
|  |  | ||||||
|         let stream: S = try!(connector.connect(host[], port, &*url.scheme)); |         let stream: S = try!(connector.connect(host[], port, &*url.scheme)); | ||||||
|         let stream = ThroughWriter(BufferedWriter::new(box stream as Box<NetworkStream + Send>)); |         let stream = ThroughWriter(BufferedWriter::new(box stream as Box<NetworkStream + Send>)); | ||||||
| @@ -80,30 +70,37 @@ impl Request<Fresh> { | |||||||
|  |  | ||||||
|     /// Create a new GET request. |     /// Create a new GET request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn get(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Get, url) } |     pub fn get(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Get, url) } | ||||||
|  |  | ||||||
|     /// Create a new POST request. |     /// Create a new POST request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn post(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Post, url) } |     pub fn post(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Post, url) } | ||||||
|  |  | ||||||
|     /// Create a new DELETE request. |     /// Create a new DELETE request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn delete(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Delete, url) } |     pub fn delete(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Delete, url) } | ||||||
|  |  | ||||||
|     /// Create a new PUT request. |     /// Create a new PUT request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn put(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Put, url) } |     pub fn put(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Put, url) } | ||||||
|  |  | ||||||
|     /// Create a new PATCH request. |     /// Create a new PATCH request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn patch(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Patch, url) } |     pub fn patch(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Patch, url) } | ||||||
|  |  | ||||||
|     /// Create a new HEAD request. |     /// Create a new HEAD request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn head(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Head, url) } |     pub fn head(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Head, url) } | ||||||
|  |  | ||||||
|     /// Create a new OPTIONS request. |     /// Create a new OPTIONS request. | ||||||
|     #[inline] |     #[inline] | ||||||
|  |     #[deprecated = "use hyper::Client"] | ||||||
|     pub fn options(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Options, url) } |     pub fn options(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Options, url) } | ||||||
|  |  | ||||||
|     /// Consume a Fresh Request, writing the headers and method, |     /// Consume a Fresh Request, writing the headers and method, | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ impl Response { | |||||||
|         debug!("{} {}", version, status); |         debug!("{} {}", version, status); | ||||||
|  |  | ||||||
|         let headers = try!(header::Headers::from_raw(&mut stream)); |         let headers = try!(header::Headers::from_raw(&mut stream)); | ||||||
|         debug!("{}", headers); |         debug!("Headers: [\n{}]", headers); | ||||||
|  |  | ||||||
|         let body = if headers.has::<TransferEncoding>() { |         let body = if headers.has::<TransferEncoding>() { | ||||||
|             match headers.get::<TransferEncoding>() { |             match headers.get::<TransferEncoding>() { | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ pub mod common; | |||||||
| /// | /// | ||||||
| /// This trait represents the construction and identification of headers, | /// This trait represents the construction and identification of headers, | ||||||
| /// and contains trait-object unsafe methods. | /// and contains trait-object unsafe methods. | ||||||
| pub trait Header: Any + Send + Sync { | pub trait Header: Clone + Any + Send + Sync { | ||||||
|     /// Returns the name of the header field this belongs to. |     /// Returns the name of the header field this belongs to. | ||||||
|     /// |     /// | ||||||
|     /// The market `Option` is to hint to the type system which implementation |     /// The market `Option` is to hint to the type system which implementation | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								src/http.rs
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								src/http.rs
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ use std::num::from_u16; | |||||||
| use std::str::{mod, SendStr}; | use std::str::{mod, SendStr}; | ||||||
|  |  | ||||||
| use url::Url; | use url::Url; | ||||||
|  | use url::ParseError as UrlError; | ||||||
|  |  | ||||||
| use method; | use method; | ||||||
| use status::StatusCode; | use status::StatusCode; | ||||||
| @@ -234,6 +235,7 @@ impl<W: Writer> Writer for HttpWriter<W> { | |||||||
|             ThroughWriter(ref mut w) => w.write(msg), |             ThroughWriter(ref mut w) => w.write(msg), | ||||||
|             ChunkedWriter(ref mut w) => { |             ChunkedWriter(ref mut w) => { | ||||||
|                 let chunk_size = msg.len(); |                 let chunk_size = msg.len(); | ||||||
|  |                 debug!("chunked write, size = {}", chunk_size); | ||||||
|                 try!(write!(w, "{:X}{}{}", chunk_size, CR as char, LF as char)); |                 try!(write!(w, "{:X}{}{}", chunk_size, CR as char, LF as char)); | ||||||
|                 try!(w.write(msg)); |                 try!(w.write(msg)); | ||||||
|                 w.write(LINE_ENDING) |                 w.write(LINE_ENDING) | ||||||
| @@ -419,7 +421,7 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> { | |||||||
|                     break; |                     break; | ||||||
|                 }, |                 }, | ||||||
|                 CR | LF => { |                 CR | LF => { | ||||||
|                     return Err(HttpUriError) |                     return Err(HttpUriError(UrlError::InvalidCharacter)) | ||||||
|                 }, |                 }, | ||||||
|                 b => s.push(b as char) |                 b => s.push(b as char) | ||||||
|             } |             } | ||||||
| @@ -431,27 +433,14 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> { | |||||||
|     if s.as_slice().starts_with("/") { |     if s.as_slice().starts_with("/") { | ||||||
|         Ok(AbsolutePath(s)) |         Ok(AbsolutePath(s)) | ||||||
|     } else if s.as_slice().contains("/") { |     } else if s.as_slice().contains("/") { | ||||||
|         match Url::parse(s.as_slice()) { |         Ok(AbsoluteUri(try!(Url::parse(s.as_slice())))) | ||||||
|             Ok(u) => Ok(AbsoluteUri(u)), |  | ||||||
|             Err(_e) => { |  | ||||||
|                 debug!("URL err {}", _e); |  | ||||||
|                 Err(HttpUriError) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } else { |     } else { | ||||||
|         let mut temp = "http://".to_string(); |         let mut temp = "http://".to_string(); | ||||||
|         temp.push_str(s.as_slice()); |         temp.push_str(s.as_slice()); | ||||||
|         match Url::parse(temp.as_slice()) { |         try!(Url::parse(temp.as_slice())); | ||||||
|             Ok(_u) => { |  | ||||||
|         todo!("compare vs u.authority()"); |         todo!("compare vs u.authority()"); | ||||||
|         Ok(Authority(s)) |         Ok(Authority(s)) | ||||||
|     } |     } | ||||||
|             Err(_e) => { |  | ||||||
|                 debug!("URL err {}", _e); |  | ||||||
|                 Err(HttpUriError) |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -138,6 +138,7 @@ extern crate mucell; | |||||||
| pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; | pub use std::io::net::ip::{SocketAddr, IpAddr, Ipv4Addr, Ipv6Addr, Port}; | ||||||
| pub use mimewrapper::mime; | pub use mimewrapper::mime; | ||||||
| pub use url::Url; | pub use url::Url; | ||||||
|  | pub use client::Client; | ||||||
| pub use method::Method::{Get, Head, Post, Delete}; | pub use method::Method::{Get, Head, Post, Delete}; | ||||||
| pub use status::StatusCode::{Ok, BadRequest, NotFound}; | pub use status::StatusCode::{Ok, BadRequest, NotFound}; | ||||||
| pub use server::Server; | pub use server::Server; | ||||||
| @@ -181,6 +182,10 @@ macro_rules! inspect( | |||||||
|     }) |     }) | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | #[macro_escape] | ||||||
|  | mod mock; | ||||||
|  |  | ||||||
| pub mod client; | pub mod client; | ||||||
| pub mod method; | pub mod method; | ||||||
| pub mod header; | pub mod header; | ||||||
| @@ -191,7 +196,6 @@ pub mod status; | |||||||
| pub mod uri; | pub mod uri; | ||||||
| pub mod version; | pub mod version; | ||||||
|  |  | ||||||
| #[cfg(test)] mod mock; |  | ||||||
|  |  | ||||||
| mod mimewrapper { | mod mimewrapper { | ||||||
|     /// Re-exporting the mime crate, for convenience. |     /// Re-exporting the mime crate, for convenience. | ||||||
| @@ -208,7 +212,7 @@ pub enum HttpError { | |||||||
|     /// An invalid `Method`, such as `GE,T`. |     /// An invalid `Method`, such as `GE,T`. | ||||||
|     HttpMethodError, |     HttpMethodError, | ||||||
|     /// An invalid `RequestUri`, such as `exam ple.domain`. |     /// An invalid `RequestUri`, such as `exam ple.domain`. | ||||||
|     HttpUriError, |     HttpUriError(url::ParseError), | ||||||
|     /// An invalid `HttpVersion`, such as `HTP/1.1` |     /// An invalid `HttpVersion`, such as `HTP/1.1` | ||||||
|     HttpVersionError, |     HttpVersionError, | ||||||
|     /// An invalid `Header`. |     /// An invalid `Header`. | ||||||
| @@ -223,7 +227,7 @@ impl Error for HttpError { | |||||||
|     fn description(&self) -> &str { |     fn description(&self) -> &str { | ||||||
|         match *self { |         match *self { | ||||||
|             HttpMethodError => "Invalid Method specified", |             HttpMethodError => "Invalid Method specified", | ||||||
|             HttpUriError => "Invalid Request URI specified", |             HttpUriError(_) => "Invalid Request URI specified", | ||||||
|             HttpVersionError => "Invalid HTTP version specified", |             HttpVersionError => "Invalid HTTP version specified", | ||||||
|             HttpHeaderError => "Invalid Header provided", |             HttpHeaderError => "Invalid Header provided", | ||||||
|             HttpStatusError => "Invalid Status provided", |             HttpStatusError => "Invalid Status provided", | ||||||
| @@ -234,6 +238,7 @@ impl Error for HttpError { | |||||||
|     fn cause(&self) -> Option<&Error> { |     fn cause(&self) -> Option<&Error> { | ||||||
|         match *self { |         match *self { | ||||||
|             HttpIoError(ref error) => Some(error as &Error), |             HttpIoError(ref error) => Some(error as &Error), | ||||||
|  |             HttpUriError(ref error) => Some(error as &Error), | ||||||
|             _ => None, |             _ => None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -245,6 +250,12 @@ impl FromError<IoError> for HttpError { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl FromError<url::ParseError> for HttpError { | ||||||
|  |     fn from_error(err: url::ParseError) -> HttpError { | ||||||
|  |         HttpUriError(err) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| //FIXME: when Opt-in Built-in Types becomes a thing, we can force these structs | //FIXME: when Opt-in Built-in Types becomes a thing, we can force these structs | ||||||
| //to be Send. For now, this has the compiler do a static check. | //to be Send. For now, this has the compiler do a static check. | ||||||
| fn _assert_send<T: Send>() { | fn _assert_send<T: Send>() { | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/mock.rs
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/mock.rs
									
									
									
									
									
								
							| @@ -73,3 +73,35 @@ impl NetworkConnector<MockStream> for MockConnector { | |||||||
|         Ok(MockStream::new()) |         Ok(MockStream::new()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /// new connectors must be created if you wish to intercept requests. | ||||||
|  | macro_rules! mock_connector ( | ||||||
|  |     ($name:ident { | ||||||
|  |         $($url:expr => $res:expr)* | ||||||
|  |     }) => ( | ||||||
|  |  | ||||||
|  |         struct $name; | ||||||
|  |  | ||||||
|  |         impl ::net::NetworkConnector<::mock::MockStream> for $name { | ||||||
|  |             fn connect(&mut self, host: &str, port: u16, scheme: &str) -> ::std::io::IoResult<::mock::MockStream> { | ||||||
|  |                 use std::collections::HashMap; | ||||||
|  |                 debug!("MockStream::connect({}, {}, {})", host, port, scheme); | ||||||
|  |                 let mut map = HashMap::new(); | ||||||
|  |                 $(map.insert($url, $res);)* | ||||||
|  |  | ||||||
|  |  | ||||||
|  |                 let key = format!("{}://{}", scheme, host); | ||||||
|  |                 // ignore port for now | ||||||
|  |                 match map.find(&&*key) { | ||||||
|  |                     Some(res) => Ok(::mock::MockStream { | ||||||
|  |                         write: ::std::io::MemWriter::new(), | ||||||
|  |                         read: ::std::io::MemReader::new(res.to_string().into_bytes()) | ||||||
|  |                     }), | ||||||
|  |                     None => panic!("{} doesn't know url {}", stringify!($name), key) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     ) | ||||||
|  | ) | ||||||
|   | |||||||
							
								
								
									
										12
									
								
								src/net.rs
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								src/net.rs
									
									
									
									
									
								
							| @@ -11,7 +11,8 @@ use std::mem::{mod, transmute, transmute_copy}; | |||||||
| use std::raw::{mod, TraitObject}; | use std::raw::{mod, TraitObject}; | ||||||
|  |  | ||||||
| use uany::UncheckedBoxAnyDowncast; | use uany::UncheckedBoxAnyDowncast; | ||||||
| use openssl::ssl::{SslStream, SslContext, Ssl}; | use openssl::ssl::{Ssl, SslStream, SslContext, VerifyCallback}; | ||||||
|  | use openssl::ssl::SslVerifyMode::SslVerifyPeer; | ||||||
| use openssl::ssl::SslMethod::Sslv23; | use openssl::ssl::SslMethod::Sslv23; | ||||||
| use openssl::ssl::error::{SslError, StreamError, OpenSslErrors, SslSessionClosed}; | use openssl::ssl::error::{SslError, StreamError, OpenSslErrors, SslSessionClosed}; | ||||||
|  |  | ||||||
| @@ -239,7 +240,7 @@ impl NetworkStream for HttpStream { | |||||||
|  |  | ||||||
| /// A connector that will produce HttpStreams. | /// A connector that will produce HttpStreams. | ||||||
| #[allow(missing_copy_implementations)] | #[allow(missing_copy_implementations)] | ||||||
| pub struct HttpConnector; | pub struct HttpConnector(pub Option<VerifyCallback>); | ||||||
|  |  | ||||||
| impl NetworkConnector<HttpStream> for HttpConnector { | impl NetworkConnector<HttpStream> for HttpConnector { | ||||||
|     fn connect(&mut self, host: &str, port: Port, scheme: &str) -> IoResult<HttpStream> { |     fn connect(&mut self, host: &str, port: Port, scheme: &str) -> IoResult<HttpStream> { | ||||||
| @@ -252,12 +253,11 @@ impl NetworkConnector<HttpStream> for HttpConnector { | |||||||
|             "https" => { |             "https" => { | ||||||
|                 debug!("https scheme"); |                 debug!("https scheme"); | ||||||
|                 let stream = try!(TcpStream::connect(addr)); |                 let stream = try!(TcpStream::connect(addr)); | ||||||
|                 let context = try!(SslContext::new(Sslv23).map_err(lift_ssl_error)); |                 let mut context = try!(SslContext::new(Sslv23).map_err(lift_ssl_error)); | ||||||
|  |                 self.0.as_ref().map(|cb| context.set_verify(SslVerifyPeer, Some(*cb))); | ||||||
|                 let ssl = try!(Ssl::new(&context).map_err(lift_ssl_error)); |                 let ssl = try!(Ssl::new(&context).map_err(lift_ssl_error)); | ||||||
|                 debug!("ssl set_hostname = {}", host); |  | ||||||
|                 try!(ssl.set_hostname(host).map_err(lift_ssl_error)); |                 try!(ssl.set_hostname(host).map_err(lift_ssl_error)); | ||||||
|                 debug!("ssl set_hostname done"); |                 let stream = try!(SslStream::new(&context, stream).map_err(lift_ssl_error)); | ||||||
|                 let stream = try!(SslStream::new_from(ssl, stream).map_err(lift_ssl_error)); |  | ||||||
|                 Ok(Https(stream)) |                 Ok(Https(stream)) | ||||||
|             }, |             }, | ||||||
|             _ => { |             _ => { | ||||||
|   | |||||||
| @@ -105,8 +105,8 @@ impl<L: NetworkListener<S, A>, S: NetworkStream, A: NetworkAcceptor<S>> Server<L | |||||||
|                                 }; |                                 }; | ||||||
|  |  | ||||||
|                                 keep_alive = match (req.version, req.headers.get::<Connection>()) { |                                 keep_alive = match (req.version, req.headers.get::<Connection>()) { | ||||||
|                                     (Http10, Some(conn)) if !conn.0.contains(&KeepAlive) => false, |                                     (Http10, Some(conn)) if !conn.contains(&KeepAlive) => false, | ||||||
|                                     (Http11, Some(conn)) if conn.0.contains(&Close)  => false, |                                     (Http11, Some(conn)) if conn.contains(&Close)  => false, | ||||||
|                                     _ => true |                                     _ => true | ||||||
|                                 }; |                                 }; | ||||||
|                                 res.version = req.version; |                                 res.version = req.version; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user