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.");
|
|
||||||
}
|
|
||||||
140
src/client.rs
140
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,19 +153,80 @@ 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);
|
|
||||||
|
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);
|
req = req.body(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = try!(req.send());
|
try!(req.send())
|
||||||
Ok(Response {
|
};
|
||||||
|
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
|
inner: res
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("redirecting to '{}'", url);
|
||||||
|
|
||||||
|
//TODO: removeSensitiveHeaders(&mut headers, &url);
|
||||||
|
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Ok(Response {
|
||||||
|
inner: res
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A Response to a submitted `Request`.
|
/// A Response to a submitted `Request`.
|
||||||
|
|||||||
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