feat(client): add a new Client struct with super powers
- Includes ergonomic traits like IntoUrl and IntoBody, allowing easy usage. - Client can have a RedirectPolicy. - Client can have a SslVerifier. Updated benchmarks for client. (Disabled rust-http client bench since it hangs.)
This commit is contained in:
		| @@ -1,7 +1,399 @@ | ||||
| //! 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::response::Response; | ||||
|  | ||||
| pub mod request; | ||||
| 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::common::{mod, Host}; | ||||
| use net::{NetworkStream, NetworkConnector, HttpConnector, Fresh, Streaming}; | ||||
| use HttpError::HttpUriError; | ||||
| use http::{HttpWriter, LINE_ENDING}; | ||||
| use http::HttpWriter::{ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter}; | ||||
| use version; | ||||
| use HttpResult; | ||||
| use client::Response; | ||||
| use client::{Response, get_host_and_port}; | ||||
|  | ||||
|  | ||||
| /// A client request to a remote server. | ||||
| @@ -42,23 +41,14 @@ impl<W> Request<W> { | ||||
| impl Request<Fresh> { | ||||
|     /// Create a new client request. | ||||
|     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) | ||||
|     } | ||||
|  | ||||
|     /// 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>> { | ||||
|         debug!("{} {}", method, url); | ||||
|         let host = match url.serialize_host() { | ||||
|             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 (host, port) = try!(get_host_and_port(&url)); | ||||
|  | ||||
|         let stream: S = try!(connector.connect(host[], port, &*url.scheme)); | ||||
|         let stream = ThroughWriter(BufferedWriter::new(box stream as Box<NetworkStream + Send>)); | ||||
| @@ -80,30 +70,37 @@ impl Request<Fresh> { | ||||
|  | ||||
|     /// Create a new GET request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn get(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Get, url) } | ||||
|  | ||||
|     /// Create a new POST request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn post(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Post, url) } | ||||
|  | ||||
|     /// Create a new DELETE request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn delete(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Delete, url) } | ||||
|  | ||||
|     /// Create a new PUT request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn put(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Put, url) } | ||||
|  | ||||
|     /// Create a new PATCH request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn patch(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Patch, url) } | ||||
|  | ||||
|     /// Create a new HEAD request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn head(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Head, url) } | ||||
|  | ||||
|     /// Create a new OPTIONS request. | ||||
|     #[inline] | ||||
|     #[deprecated = "use hyper::Client"] | ||||
|     pub fn options(url: Url) -> HttpResult<Request<Fresh>> { Request::new(Options, url) } | ||||
|  | ||||
|     /// Consume a Fresh Request, writing the headers and method, | ||||
|   | ||||
| @@ -38,7 +38,7 @@ impl Response { | ||||
|         debug!("{} {}", version, status); | ||||
|  | ||||
|         let headers = try!(header::Headers::from_raw(&mut stream)); | ||||
|         debug!("{}", headers); | ||||
|         debug!("Headers: [\n{}]", headers); | ||||
|  | ||||
|         let body = if headers.has::<TransferEncoding>() { | ||||
|             match headers.get::<TransferEncoding>() { | ||||
|   | ||||
| @@ -30,7 +30,7 @@ pub mod common; | ||||
| /// | ||||
| /// This trait represents the construction and identification of headers, | ||||
| /// 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. | ||||
|     /// | ||||
|     /// The market `Option` is to hint to the type system which implementation | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/http.rs
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/http.rs
									
									
									
									
									
								
							| @@ -7,6 +7,7 @@ use std::num::from_u16; | ||||
| use std::str::{mod, SendStr}; | ||||
|  | ||||
| use url::Url; | ||||
| use url::ParseError as UrlError; | ||||
|  | ||||
| use method; | ||||
| use status::StatusCode; | ||||
| @@ -234,6 +235,7 @@ impl<W: Writer> Writer for HttpWriter<W> { | ||||
|             ThroughWriter(ref mut w) => w.write(msg), | ||||
|             ChunkedWriter(ref mut w) => { | ||||
|                 let chunk_size = msg.len(); | ||||
|                 debug!("chunked write, size = {}", chunk_size); | ||||
|                 try!(write!(w, "{:X}{}{}", chunk_size, CR as char, LF as char)); | ||||
|                 try!(w.write(msg)); | ||||
|                 w.write(LINE_ENDING) | ||||
| @@ -419,7 +421,7 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> { | ||||
|                     break; | ||||
|                 }, | ||||
|                 CR | LF => { | ||||
|                     return Err(HttpUriError) | ||||
|                     return Err(HttpUriError(UrlError::InvalidCharacter)) | ||||
|                 }, | ||||
|                 b => s.push(b as char) | ||||
|             } | ||||
| @@ -431,26 +433,13 @@ pub fn read_uri<R: Reader>(stream: &mut R) -> HttpResult<uri::RequestUri> { | ||||
|     if s.as_slice().starts_with("/") { | ||||
|         Ok(AbsolutePath(s)) | ||||
|     } else if s.as_slice().contains("/") { | ||||
|         match Url::parse(s.as_slice()) { | ||||
|             Ok(u) => Ok(AbsoluteUri(u)), | ||||
|             Err(_e) => { | ||||
|                 debug!("URL err {}", _e); | ||||
|                 Err(HttpUriError) | ||||
|             } | ||||
|         } | ||||
|         Ok(AbsoluteUri(try!(Url::parse(s.as_slice())))) | ||||
|     } else { | ||||
|         let mut temp = "http://".to_string(); | ||||
|         temp.push_str(s.as_slice()); | ||||
|         match Url::parse(temp.as_slice()) { | ||||
|             Ok(_u) => { | ||||
|                 todo!("compare vs u.authority()"); | ||||
|                 Ok(Authority(s)) | ||||
|             } | ||||
|             Err(_e) => { | ||||
|                 debug!("URL err {}", _e); | ||||
|                 Err(HttpUriError) | ||||
|             } | ||||
|         } | ||||
|         try!(Url::parse(temp.as_slice())); | ||||
|         todo!("compare vs u.authority()"); | ||||
|         Ok(Authority(s)) | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										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 mimewrapper::mime; | ||||
| pub use url::Url; | ||||
| pub use client::Client; | ||||
| pub use method::Method::{Get, Head, Post, Delete}; | ||||
| pub use status::StatusCode::{Ok, BadRequest, NotFound}; | ||||
| pub use server::Server; | ||||
| @@ -181,6 +182,10 @@ macro_rules! inspect( | ||||
|     }) | ||||
| ) | ||||
|  | ||||
| #[cfg(test)] | ||||
| #[macro_escape] | ||||
| mod mock; | ||||
|  | ||||
| pub mod client; | ||||
| pub mod method; | ||||
| pub mod header; | ||||
| @@ -191,7 +196,6 @@ pub mod status; | ||||
| pub mod uri; | ||||
| pub mod version; | ||||
|  | ||||
| #[cfg(test)] mod mock; | ||||
|  | ||||
| mod mimewrapper { | ||||
|     /// Re-exporting the mime crate, for convenience. | ||||
| @@ -208,7 +212,7 @@ pub enum HttpError { | ||||
|     /// An invalid `Method`, such as `GE,T`. | ||||
|     HttpMethodError, | ||||
|     /// An invalid `RequestUri`, such as `exam ple.domain`. | ||||
|     HttpUriError, | ||||
|     HttpUriError(url::ParseError), | ||||
|     /// An invalid `HttpVersion`, such as `HTP/1.1` | ||||
|     HttpVersionError, | ||||
|     /// An invalid `Header`. | ||||
| @@ -223,7 +227,7 @@ impl Error for HttpError { | ||||
|     fn description(&self) -> &str { | ||||
|         match *self { | ||||
|             HttpMethodError => "Invalid Method specified", | ||||
|             HttpUriError => "Invalid Request URI specified", | ||||
|             HttpUriError(_) => "Invalid Request URI specified", | ||||
|             HttpVersionError => "Invalid HTTP version specified", | ||||
|             HttpHeaderError => "Invalid Header provided", | ||||
|             HttpStatusError => "Invalid Status provided", | ||||
| @@ -234,6 +238,7 @@ impl Error for HttpError { | ||||
|     fn cause(&self) -> Option<&Error> { | ||||
|         match *self { | ||||
|             HttpIoError(ref error) => Some(error as &Error), | ||||
|             HttpUriError(ref error) => Some(error as &Error), | ||||
|             _ => 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 | ||||
| //to be Send. For now, this has the compiler do a static check. | ||||
| 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()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// 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 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::error::{SslError, StreamError, OpenSslErrors, SslSessionClosed}; | ||||
|  | ||||
| @@ -239,7 +240,7 @@ impl NetworkStream for HttpStream { | ||||
|  | ||||
| /// A connector that will produce HttpStreams. | ||||
| #[allow(missing_copy_implementations)] | ||||
| pub struct HttpConnector; | ||||
| pub struct HttpConnector(pub Option<VerifyCallback>); | ||||
|  | ||||
| impl NetworkConnector<HttpStream> for HttpConnector { | ||||
|     fn connect(&mut self, host: &str, port: Port, scheme: &str) -> IoResult<HttpStream> { | ||||
| @@ -252,12 +253,11 @@ impl NetworkConnector<HttpStream> for HttpConnector { | ||||
|             "https" => { | ||||
|                 debug!("https scheme"); | ||||
|                 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)); | ||||
|                 debug!("ssl set_hostname = {}", host); | ||||
|                 try!(ssl.set_hostname(host).map_err(lift_ssl_error)); | ||||
|                 debug!("ssl set_hostname done"); | ||||
|                 let stream = try!(SslStream::new_from(ssl, stream).map_err(lift_ssl_error)); | ||||
|                 let stream = try!(SslStream::new(&context, stream).map_err(lift_ssl_error)); | ||||
|                 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>()) { | ||||
|                                     (Http10, Some(conn)) if !conn.0.contains(&KeepAlive) => false, | ||||
|                                     (Http11, Some(conn)) if conn.0.contains(&Close)  => false, | ||||
|                                     (Http10, Some(conn)) if !conn.contains(&KeepAlive) => false, | ||||
|                                     (Http11, Some(conn)) if conn.contains(&Close)  => false, | ||||
|                                     _ => true | ||||
|                                 }; | ||||
|                                 res.version = req.version; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user