handle redirects
This commit is contained in:
		| @@ -10,9 +10,10 @@ license = "MIT/Apache-2.0" | |||||||
|  |  | ||||||
| [dependencies] | [dependencies] | ||||||
| hyper = { version = "0.9" , default-features = false } | hyper = { version = "0.9" , default-features = false } | ||||||
|  | log = "0.3" | ||||||
| serde = "0.8" | serde = "0.8" | ||||||
| serde_json = "0.8" | serde_json = "0.8" | ||||||
| log = "0.3" | url = "1.0" | ||||||
|  |  | ||||||
| [dependencies.native-tls] | [dependencies.native-tls] | ||||||
| git = "https://github.com/sfackler/rust-native-tls" | git = "https://github.com/sfackler/rust-native-tls" | ||||||
| @@ -25,4 +26,3 @@ tls = ["native-tls"] | |||||||
|  |  | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| env_logger = "0.3" | env_logger = "0.3" | ||||||
| serde_derive = "0.8" |  | ||||||
|   | |||||||
| @@ -1,40 +0,0 @@ | |||||||
| //#![feature(proc_macro)] |  | ||||||
|  |  | ||||||
| extern crate reqwest; |  | ||||||
| extern crate env_logger; |  | ||||||
| //#[macro_use] extern crate serde_derive; |  | ||||||
|  |  | ||||||
| /* |  | ||||||
| #[derive(Serialize)] |  | ||||||
| struct Thingy { |  | ||||||
|     a: i32, |  | ||||||
|     b: bool, |  | ||||||
|     c: String, |  | ||||||
| } |  | ||||||
| */ |  | ||||||
|  |  | ||||||
| fn main() { |  | ||||||
|     env_logger::init().unwrap(); |  | ||||||
|  |  | ||||||
|     println!("POST https://httpbin.org/post"); |  | ||||||
|  |  | ||||||
|     /* |  | ||||||
|     let thingy = Thingy { |  | ||||||
|         a: 5, |  | ||||||
|         b: true, |  | ||||||
|         c: String::from("reqwest") |  | ||||||
|     }; |  | ||||||
|     */ |  | ||||||
|  |  | ||||||
|     let client = reqwest::Client::new(); |  | ||||||
|     let mut res = client.post("https://httpbin.org/post") |  | ||||||
|         .body("foo=bar") |  | ||||||
|         .send().unwrap(); |  | ||||||
|  |  | ||||||
|     println!("Status: {}", res.status()); |  | ||||||
|     println!("Headers:\n{}", res.headers()); |  | ||||||
|  |  | ||||||
|     ::std::io::copy(&mut res, &mut ::std::io::stdout()).unwrap(); |  | ||||||
|  |  | ||||||
|     println!("\n\nDone."); |  | ||||||
| } |  | ||||||
							
								
								
									
										148
									
								
								src/client.rs
									
									
									
									
									
								
							
							
						
						
									
										148
									
								
								src/client.rs
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | |||||||
| use std::io::{self, Read}; | use std::io::{self, Read}; | ||||||
|  |  | ||||||
| use hyper::header::{Headers, ContentType, UserAgent}; | use hyper::client::IntoUrl; | ||||||
|  | use hyper::header::{Headers, ContentType, Location, Referer, UserAgent}; | ||||||
| use hyper::method::Method; | use hyper::method::Method; | ||||||
| use hyper::status::StatusCode; | use hyper::status::StatusCode; | ||||||
| use hyper::version::HttpVersion; | use hyper::version::HttpVersion; | ||||||
| @@ -26,28 +27,35 @@ pub struct Client { | |||||||
|  |  | ||||||
| impl Client { | impl Client { | ||||||
|     /// Constructs a new `Client`. |     /// Constructs a new `Client`. | ||||||
|     pub fn new() -> Client { |     pub fn new() -> ::Result<Client> { | ||||||
|         Client { |         let mut client = try!(new_hyper_client()); | ||||||
|             inner: new_hyper_client() |         client.set_redirect_policy(::hyper::client::RedirectPolicy::FollowNone); | ||||||
|         } |         Ok(Client { | ||||||
|  |             inner: client | ||||||
|  |         }) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Convenience method to make a `GET` request to a URL. |     /// Convenience method to make a `GET` request to a URL. | ||||||
|     pub fn get(&self, url: &str) -> RequestBuilder { |     pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder { | ||||||
|         self.request(Method::Get, Url::parse(url).unwrap()) |         self.request(Method::Get, url) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Convenience method to make a `POST` request to a URL. |     /// Convenience method to make a `POST` request to a URL. | ||||||
|     pub fn post(&self, url: &str) -> RequestBuilder { |     pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder { | ||||||
|         self.request(Method::Post, Url::parse(url).unwrap()) |         self.request(Method::Post, url) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /// Convenience method to make a `HEAD` request to a URL. | ||||||
|  |     pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder { | ||||||
|  |         self.request(Method::Head, url) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Start building a `Request` with the `Method` and `Url`. |     /// Start building a `Request` with the `Method` and `Url`. | ||||||
|     /// |     /// | ||||||
|     /// Returns a `RequestBuilder`, which will allow setting headers and |     /// Returns a `RequestBuilder`, which will allow setting headers and | ||||||
|     /// request body before sending. |     /// request body before sending. | ||||||
|     pub fn request(&self, method: Method, url: Url) -> RequestBuilder { |     pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { | ||||||
|         debug!("request {:?} \"{}\"", method, url); |         let url = url.into_url(); | ||||||
|         RequestBuilder { |         RequestBuilder { | ||||||
|             client: self, |             client: self, | ||||||
|             method: method, |             method: method, | ||||||
| @@ -61,19 +69,19 @@ impl Client { | |||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(not(feature = "tls"))] | #[cfg(not(feature = "tls"))] | ||||||
| fn new_hyper_client() -> ::hyper::Client { | fn new_hyper_client() -> ::Result<::hyper::Client> { | ||||||
|     ::hyper::Client::new() |     Ok(::hyper::Client::new()) | ||||||
| } | } | ||||||
|  |  | ||||||
| #[cfg(feature = "tls")] | #[cfg(feature = "tls")] | ||||||
| fn new_hyper_client() -> ::hyper::Client { | fn new_hyper_client() -> ::Result<::hyper::Client> { | ||||||
|     use tls::TlsClient; |     use tls::TlsClient; | ||||||
|     ::hyper::Client::with_connector( |     Ok(::hyper::Client::with_connector( | ||||||
|         ::hyper::client::Pool::with_connector( |         ::hyper::client::Pool::with_connector( | ||||||
|             Default::default(), |             Default::default(), | ||||||
|             ::hyper::net::HttpsConnector::new(TlsClient::new().unwrap()) |             ::hyper::net::HttpsConnector::new(try!(TlsClient::new())) | ||||||
|         ) |         ) | ||||||
|     ) |     )) | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -82,7 +90,7 @@ pub struct RequestBuilder<'a> { | |||||||
|     client: &'a Client, |     client: &'a Client, | ||||||
|  |  | ||||||
|     method: Method, |     method: Method, | ||||||
|     url: Url, |     url: Result<Url, ::UrlError>, | ||||||
|     _version: HttpVersion, |     _version: HttpVersion, | ||||||
|     headers: Headers, |     headers: Headers, | ||||||
|  |  | ||||||
| @@ -91,6 +99,15 @@ pub struct RequestBuilder<'a> { | |||||||
|  |  | ||||||
| impl<'a> RequestBuilder<'a> { | impl<'a> RequestBuilder<'a> { | ||||||
|     /// Add a `Header` to this Request. |     /// Add a `Header` to this Request. | ||||||
|  |     /// | ||||||
|  |     /// ```no_run | ||||||
|  |     /// use reqwest::header::UserAgent; | ||||||
|  |     /// let client = reqwest::Client::new().expect("client failed to construct"); | ||||||
|  |     /// | ||||||
|  |     /// let res = client.get("https://www.rust-lang.org") | ||||||
|  |     ///     .header(UserAgent("foo".to_string())) | ||||||
|  |     ///     .send(); | ||||||
|  |     /// ``` | ||||||
|     pub fn header<H: ::header::Header + ::header::HeaderFormat>(mut self, header: H) -> RequestBuilder<'a> { |     pub fn header<H: ::header::Header + ::header::HeaderFormat>(mut self, header: H) -> RequestBuilder<'a> { | ||||||
|         self.headers.set(header); |         self.headers.set(header); | ||||||
|         self |         self | ||||||
| @@ -109,6 +126,20 @@ impl<'a> RequestBuilder<'a> { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /// Send a JSON body. | ||||||
|  |     /// | ||||||
|  |     /// Sets the body to the JSON serialization of the passed value, and | ||||||
|  |     /// also sets the `Content-Type: application/json` header. | ||||||
|  |     /// | ||||||
|  |     /// ```no_run | ||||||
|  |     /// # use std::collections::HashMap; | ||||||
|  |     /// let mut map = HashMap::new(); | ||||||
|  |     /// map.insert("lang", "rust"); | ||||||
|  |     /// | ||||||
|  |     /// let res = reqwest::post("http://www.rust-lang.org") | ||||||
|  |     ///     .json(map) | ||||||
|  |     ///     .send(); | ||||||
|  |     /// ``` | ||||||
|     pub fn json<T: Serialize>(mut self, json: T) -> RequestBuilder<'a> { |     pub fn json<T: Serialize>(mut self, json: T) -> RequestBuilder<'a> { | ||||||
|         let body = serde_json::to_vec(&json).expect("serde to_vec cannot fail"); |         let body = serde_json::to_vec(&json).expect("serde to_vec cannot fail"); | ||||||
|         self.headers.set(ContentType::json()); |         self.headers.set(ContentType::json()); | ||||||
| @@ -122,18 +153,79 @@ impl<'a> RequestBuilder<'a> { | |||||||
|             self.headers.set(UserAgent(DEFAULT_USER_AGENT.to_owned())); |             self.headers.set(UserAgent(DEFAULT_USER_AGENT.to_owned())); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let mut req = self.client.inner.request(self.method, self.url) |         let client = self.client; | ||||||
|             .headers(self.headers); |         let mut method = self.method; | ||||||
|  |         let mut url = try!(self.url); | ||||||
|  |         let mut headers = self.headers; | ||||||
|  |         let mut body = self.body; | ||||||
|  |  | ||||||
|         if let Some(ref b) = self.body { |         let mut redirect_count = 0; | ||||||
|             let body = body::as_hyper_body(b); |  | ||||||
|             req = req.body(body); |         loop { | ||||||
|  |             let res = { | ||||||
|  |                 debug!("request {:?} \"{}\"", method, url); | ||||||
|  |                 let mut req = client.inner.request(method.clone(), url.clone()) | ||||||
|  |                     .headers(headers.clone()); | ||||||
|  |  | ||||||
|  |                 if let Some(ref b) = body { | ||||||
|  |                     let body = body::as_hyper_body(&b); | ||||||
|  |                     req = req.body(body); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 try!(req.send()) | ||||||
|  |             }; | ||||||
|  |             body.take(); | ||||||
|  |  | ||||||
|  |             match res.status { | ||||||
|  |                 StatusCode::MovedPermanently | | ||||||
|  |                 StatusCode::Found => { | ||||||
|  |  | ||||||
|  |                     //TODO: turn this into self.redirect_policy.check() | ||||||
|  |                     if redirect_count > 10 { | ||||||
|  |                         return Err(::Error::TooManyRedirects); | ||||||
|  |                     } | ||||||
|  |                     redirect_count += 1; | ||||||
|  |  | ||||||
|  |                     method = match method { | ||||||
|  |                         Method::Post | Method::Put => Method::Get, | ||||||
|  |                         m => m | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     headers.set(Referer(url.to_string())); | ||||||
|  |  | ||||||
|  |                     let loc = { | ||||||
|  |                         let loc = res.headers.get::<Location>().map(|loc| url.join(loc)); | ||||||
|  |                         if let Some(loc) = loc { | ||||||
|  |                             loc | ||||||
|  |                         } else { | ||||||
|  |                             return Ok(Response { | ||||||
|  |                                 inner: res | ||||||
|  |                             }); | ||||||
|  |                         } | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     url = match loc { | ||||||
|  |                         Ok(u) => u, | ||||||
|  |                         Err(e) => { | ||||||
|  |                             debug!("Location header had invalid URI: {:?}", e); | ||||||
|  |                             return Ok(Response { | ||||||
|  |                                 inner: res | ||||||
|  |                             }) | ||||||
|  |                         } | ||||||
|  |                     }; | ||||||
|  |  | ||||||
|  |                     debug!("redirecting to '{}'", url); | ||||||
|  |  | ||||||
|  |                     //TODO: removeSensitiveHeaders(&mut headers, &url); | ||||||
|  |  | ||||||
|  |                 }, | ||||||
|  |                 _ => { | ||||||
|  |                     return Ok(Response { | ||||||
|  |                         inner: res | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let res = try!(req.send()); |  | ||||||
|         Ok(Response { |  | ||||||
|             inner: res |  | ||||||
|         }) |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -1,17 +1,60 @@ | |||||||
|  | use std::error::Error as StdError; | ||||||
|  | use std::fmt; | ||||||
|  |  | ||||||
| /// The Errors that may occur when processing a `Request`. | /// The Errors that may occur when processing a `Request`. | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub enum Error { | pub enum Error { | ||||||
|     /// An HTTP error from the `hyper` crate. |     /// An HTTP error from the `hyper` crate. | ||||||
|     Http(::hyper::Error), |     Http(::hyper::Error), | ||||||
|  |     /// A request tried to redirect too many times. | ||||||
|  |     TooManyRedirects, | ||||||
|     #[doc(hidden)] |     #[doc(hidden)] | ||||||
|     __DontMatchMe, |     __DontMatchMe, | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl fmt::Display for Error { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         match *self { | ||||||
|  |             Error::Http(ref e) => fmt::Display::fmt(e, f), | ||||||
|  |             Error::TooManyRedirects => { | ||||||
|  |                 f.pad("Too many redirects") | ||||||
|  |             }, | ||||||
|  |             Error::__DontMatchMe => unreachable!() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl StdError for Error { | ||||||
|  |     fn description(&self) -> &str { | ||||||
|  |         match *self { | ||||||
|  |             Error::Http(ref e) => e.description(), | ||||||
|  |             Error::TooManyRedirects => "Too many redirects", | ||||||
|  |             Error::__DontMatchMe => unreachable!() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn cause(&self) -> Option<&StdError> { | ||||||
|  |         match *self { | ||||||
|  |             Error::Http(ref e) => Some(e), | ||||||
|  |             Error::TooManyRedirects => None, | ||||||
|  |             Error::__DontMatchMe => unreachable!() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl From<::hyper::Error> for Error { | impl From<::hyper::Error> for Error { | ||||||
|     fn from(err: ::hyper::Error) -> Error { |     fn from(err: ::hyper::Error) -> Error { | ||||||
|         Error::Http(err) |         Error::Http(err) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl From<::url::ParseError> for Error { | ||||||
|  |     fn from(err: ::url::ParseError) -> Error { | ||||||
|  |         Error::Http(::hyper::Error::Uri(err)) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /// A `Result` alias where the `Err` case is `reqwest::Error`. | /// A `Result` alias where the `Err` case is `reqwest::Error`. | ||||||
| pub type Result<T> = ::std::result::Result<T, Error>; | pub type Result<T> = ::std::result::Result<T, Error>; | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/lib.rs
									
									
									
									
									
								
							| @@ -22,22 +22,30 @@ | |||||||
| //! | //! | ||||||
| //! ## Making a GET request | //! ## Making a GET request | ||||||
| //! | //! | ||||||
|  | //! For a single request, you can use the `get` shortcut method. | ||||||
|  | //! | ||||||
|  | //! | ||||||
| //! ```no_run | //! ```no_run | ||||||
| //! let resp = reqwest::get("https://www.rust-lang.org").unwrap(); | //! let resp = reqwest::get("https://www.rust-lang.org").unwrap(); | ||||||
| //! assert!(resp.status().is_success()); | //! assert!(resp.status().is_success()); | ||||||
| //! ``` | //! ``` | ||||||
|  | //! | ||||||
|  | //! If you plan to perform multiple requests, it is best to create a [`Client`][client] | ||||||
|  | //! and reuse it, taking advantage of keep-alive connection pooling. | ||||||
| extern crate hyper; | extern crate hyper; | ||||||
|  |  | ||||||
| #[macro_use] extern crate log; | #[macro_use] extern crate log; | ||||||
| #[cfg(feature = "tls")] extern crate native_tls; | #[cfg(feature = "tls")] extern crate native_tls; | ||||||
| extern crate serde; | extern crate serde; | ||||||
| extern crate serde_json; | extern crate serde_json; | ||||||
|  | extern crate url; | ||||||
|  |  | ||||||
| pub use hyper::header; | pub use hyper::header; | ||||||
| pub use hyper::method::Method; | pub use hyper::method::Method; | ||||||
| pub use hyper::status::StatusCode; | pub use hyper::status::StatusCode; | ||||||
| pub use hyper::version::HttpVersion; | pub use hyper::version::HttpVersion; | ||||||
| pub use hyper::Url; | pub use hyper::Url; | ||||||
|  | pub use url::ParseError as UrlError; | ||||||
|  |  | ||||||
| pub use self::client::{Client, Response}; | pub use self::client::{Client, Response}; | ||||||
| pub use self::error::{Error, Result}; | pub use self::error::{Error, Result}; | ||||||
| @@ -50,6 +58,6 @@ mod error; | |||||||
|  |  | ||||||
| /// Shortcut method to quickly make a `GET` request. | /// Shortcut method to quickly make a `GET` request. | ||||||
| pub fn get(url: &str) -> ::Result<Response> { | pub fn get(url: &str) -> ::Result<Response> { | ||||||
|     let client = Client::new(); |     let client = try!(Client::new()); | ||||||
|     client.get(url).send() |     client.get(url).send() | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user