Add a Request type and refactor execution.
A Request can be obtained with RequestBuilder::build(), and executed with Client::execute(). The RequestBuilder now also builds a Request and forwards it to the inner client. The execution logic was moved from Requestbuilder::send() to ClientRef::execute_request().
This commit is contained in:
318
src/client.rs
318
src/client.rs
@@ -297,6 +297,17 @@ impl Client {
|
|||||||
body: None,
|
body: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a `Request`.
|
||||||
|
///
|
||||||
|
/// A `Request` can be built manually with `Request::new()` or obtained
|
||||||
|
/// from a RequestBuilder with `RequestBuilder::build()`.
|
||||||
|
///
|
||||||
|
/// You should prefer to use the `RequestBuilder` and
|
||||||
|
/// `RequestBuilder::send()`.
|
||||||
|
pub fn execute(&self, request: Request) -> ::Result<Response> {
|
||||||
|
self.inner.execute_request(request)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Client {
|
impl fmt::Debug for Client {
|
||||||
@@ -316,6 +327,188 @@ struct ClientRef {
|
|||||||
auto_ungzip: AtomicBool,
|
auto_ungzip: AtomicBool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ClientRef {
|
||||||
|
fn execute_request(&self, request: Request) -> ::Result<Response> {
|
||||||
|
let mut headers = request.headers;
|
||||||
|
if !headers.has::<UserAgent>() {
|
||||||
|
headers.set(UserAgent(DEFAULT_USER_AGENT.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !headers.has::<Accept>() {
|
||||||
|
headers.set(Accept::star());
|
||||||
|
}
|
||||||
|
if self.auto_ungzip.load(Ordering::Relaxed) &&
|
||||||
|
!headers.has::<AcceptEncoding>() &&
|
||||||
|
!headers.has::<Range>() {
|
||||||
|
headers.set(AcceptEncoding(vec![qitem(Encoding::Gzip)]));
|
||||||
|
}
|
||||||
|
let mut method = request.method;
|
||||||
|
let mut url = request.url;
|
||||||
|
let mut body = request.body;
|
||||||
|
|
||||||
|
let mut urls = Vec::new();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let res = {
|
||||||
|
info!("Request: {:?} {}", method, url);
|
||||||
|
let c = self.hyper.read().unwrap();
|
||||||
|
let mut req = c.request(method.clone(), url.clone())
|
||||||
|
.headers(headers.clone());
|
||||||
|
|
||||||
|
if let Some(ref mut b) = body {
|
||||||
|
let body = body::as_hyper_body(b);
|
||||||
|
req = req.body(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
try_!(req.send(), &url)
|
||||||
|
};
|
||||||
|
|
||||||
|
let should_redirect = match res.status {
|
||||||
|
StatusCode::MovedPermanently |
|
||||||
|
StatusCode::Found |
|
||||||
|
StatusCode::SeeOther => {
|
||||||
|
body = None;
|
||||||
|
match method {
|
||||||
|
Method::Get | Method::Head => {},
|
||||||
|
_ => {
|
||||||
|
method = Method::Get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
},
|
||||||
|
StatusCode::TemporaryRedirect |
|
||||||
|
StatusCode::PermanentRedirect => {
|
||||||
|
if let Some(ref body) = body {
|
||||||
|
body::can_reset(body)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_redirect {
|
||||||
|
let loc = {
|
||||||
|
let loc = res.headers.get::<Location>().map(|loc| url.join(loc));
|
||||||
|
if let Some(loc) = loc {
|
||||||
|
loc
|
||||||
|
} else {
|
||||||
|
return Ok(::response::new(res, self.auto_ungzip.load(Ordering::Relaxed)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
url = match loc {
|
||||||
|
Ok(loc) => {
|
||||||
|
if self.auto_referer.load(Ordering::Relaxed) {
|
||||||
|
if let Some(referer) = make_referer(&loc, &url) {
|
||||||
|
headers.set(referer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
urls.push(url);
|
||||||
|
let action = check_redirect(&self.redirect_policy.lock().unwrap(), &loc, &urls);
|
||||||
|
|
||||||
|
match action {
|
||||||
|
redirect::Action::Follow => loc,
|
||||||
|
redirect::Action::Stop => {
|
||||||
|
debug!("redirect_policy disallowed redirection to '{}'", loc);
|
||||||
|
return Ok(::response::new(res, self.auto_ungzip.load(Ordering::Relaxed)));
|
||||||
|
},
|
||||||
|
redirect::Action::LoopDetected => {
|
||||||
|
return Err(::error::loop_detected(res.url.clone()));
|
||||||
|
},
|
||||||
|
redirect::Action::TooManyRedirects => {
|
||||||
|
return Err(::error::too_many_redirects(res.url.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
debug!("Location header had invalid URI: {:?}", e);
|
||||||
|
|
||||||
|
return Ok(::response::new(res, self.auto_ungzip.load(Ordering::Relaxed)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
remove_sensitive_headers(&mut headers, &url, &urls);
|
||||||
|
debug!("redirecting to {:?} '{}'", method, url);
|
||||||
|
} else {
|
||||||
|
return Ok(::response::new(res, self.auto_ungzip.load(Ordering::Relaxed)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A request which can be executed with `Client::execute()`.
|
||||||
|
pub struct Request {
|
||||||
|
_version: HttpVersion,
|
||||||
|
method: Method,
|
||||||
|
url: Url,
|
||||||
|
headers: Headers,
|
||||||
|
body: Option<Body>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
/// Constructs a new request.
|
||||||
|
pub fn new(method: Method, url: Url) -> Self {
|
||||||
|
Request {
|
||||||
|
_version: HttpVersion::Http11,
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
headers: Headers::new(),
|
||||||
|
body: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the method.
|
||||||
|
pub fn method(&self) -> &Method {
|
||||||
|
&self.method
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the method.
|
||||||
|
pub fn set_method(&mut self, method: Method) {
|
||||||
|
self.method = method;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the url.
|
||||||
|
pub fn url(&self) -> &Url {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the url.
|
||||||
|
pub fn url_mut(&mut self) -> &mut Url {
|
||||||
|
&mut self.url
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the url.
|
||||||
|
pub fn set_url(&mut self, url: Url) {
|
||||||
|
self.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the headers.
|
||||||
|
pub fn headers(&self) -> &Headers {
|
||||||
|
&self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the headers.
|
||||||
|
pub fn headers_mut(&mut self) -> &mut Headers {
|
||||||
|
&mut self.headers
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the body.
|
||||||
|
pub fn body(&self) -> Option<&Body> {
|
||||||
|
self.body.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get a mutable reference to the body.
|
||||||
|
pub fn body_mut(&mut self) -> &mut Option<Body> {
|
||||||
|
&mut self.body
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the body.
|
||||||
|
pub fn set_body(&mut self, body: Option<Body>) {
|
||||||
|
self.body = body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A builder to construct the properties of a `Request`.
|
/// A builder to construct the properties of a `Request`.
|
||||||
pub struct RequestBuilder {
|
pub struct RequestBuilder {
|
||||||
client: Arc<ClientRef>,
|
client: Arc<ClientRef>,
|
||||||
@@ -432,118 +625,29 @@ impl RequestBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs the Request and sends it the target URL, returning a Response.
|
/// Build a `Request`, which can be inspected, modified and executed with
|
||||||
pub fn send(mut self) -> ::Result<Response> {
|
/// `Client::execute()`.
|
||||||
if !self.headers.has::<UserAgent>() {
|
pub fn build(self) -> ::Result<Request> {
|
||||||
self.headers.set(UserAgent(DEFAULT_USER_AGENT.to_owned()));
|
let url = try_!(self.url);
|
||||||
}
|
let body = match self.body {
|
||||||
|
|
||||||
if !self.headers.has::<Accept>() {
|
|
||||||
self.headers.set(Accept::star());
|
|
||||||
}
|
|
||||||
if self.client.auto_ungzip.load(Ordering::Relaxed) &&
|
|
||||||
!self.headers.has::<AcceptEncoding>() &&
|
|
||||||
!self.headers.has::<Range>() {
|
|
||||||
self.headers.set(AcceptEncoding(vec![qitem(Encoding::Gzip)]));
|
|
||||||
}
|
|
||||||
let client = self.client;
|
|
||||||
let mut method = self.method;
|
|
||||||
let mut url = try_!(self.url);
|
|
||||||
let mut headers = self.headers;
|
|
||||||
let mut body = match self.body {
|
|
||||||
Some(b) => Some(try_!(b)),
|
Some(b) => Some(try_!(b)),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
let req = Request {
|
||||||
let mut urls = Vec::new();
|
_version: self._version,
|
||||||
|
method: self.method,
|
||||||
loop {
|
url: url,
|
||||||
let res = {
|
headers: self.headers,
|
||||||
info!("Request: {:?} {}", method, url);
|
body: body,
|
||||||
let c = client.hyper.read().unwrap();
|
|
||||||
let mut req = c.request(method.clone(), url.clone())
|
|
||||||
.headers(headers.clone());
|
|
||||||
|
|
||||||
if let Some(ref mut b) = body {
|
|
||||||
let body = body::as_hyper_body(b);
|
|
||||||
req = req.body(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
try_!(req.send(), &url)
|
|
||||||
};
|
};
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
let should_redirect = match res.status {
|
/// Constructs the Request and sends it the target URL, returning a Response.
|
||||||
StatusCode::MovedPermanently |
|
pub fn send(self) -> ::Result<Response> {
|
||||||
StatusCode::Found |
|
let client = self.client.clone();
|
||||||
StatusCode::SeeOther => {
|
let request = self.build()?;
|
||||||
body = None;
|
client.execute_request(request)
|
||||||
match method {
|
|
||||||
Method::Get | Method::Head => {}
|
|
||||||
_ => {
|
|
||||||
method = Method::Get;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
StatusCode::TemporaryRedirect |
|
|
||||||
StatusCode::PermanentRedirect => {
|
|
||||||
if let Some(ref body) = body {
|
|
||||||
body::can_reset(body)
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if should_redirect {
|
|
||||||
let loc = {
|
|
||||||
let loc = res.headers.get::<Location>().map(|loc| url.join(loc));
|
|
||||||
if let Some(loc) = loc {
|
|
||||||
loc
|
|
||||||
} else {
|
|
||||||
return Ok(::response::new(res, client.auto_ungzip.load(Ordering::Relaxed)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
url = match loc {
|
|
||||||
Ok(loc) => {
|
|
||||||
if client.auto_referer.load(Ordering::Relaxed) {
|
|
||||||
if let Some(referer) = make_referer(&loc, &url) {
|
|
||||||
headers.set(referer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
urls.push(url);
|
|
||||||
let action =
|
|
||||||
check_redirect(&client.redirect_policy.lock().unwrap(), &loc, &urls);
|
|
||||||
|
|
||||||
match action {
|
|
||||||
redirect::Action::Follow => loc,
|
|
||||||
redirect::Action::Stop => {
|
|
||||||
debug!("redirect_policy disallowed redirection to '{}'", loc);
|
|
||||||
return Ok(::response::new(res, client.auto_ungzip.load(Ordering::Relaxed)));
|
|
||||||
}
|
|
||||||
redirect::Action::LoopDetected => {
|
|
||||||
return Err(::error::loop_detected(res.url.clone()));
|
|
||||||
}
|
|
||||||
redirect::Action::TooManyRedirects => {
|
|
||||||
return Err(::error::too_many_redirects(res.url.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("Location header had invalid URI: {:?}", e);
|
|
||||||
|
|
||||||
return Ok(::response::new(res, client.auto_ungzip.load(Ordering::Relaxed)));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
remove_sensitive_headers(&mut headers, &url, &urls);
|
|
||||||
debug!("redirecting to {:?} '{}'", method, url);
|
|
||||||
} else {
|
|
||||||
return Ok(::response::new(res, client.auto_ungzip.load(Ordering::Relaxed)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user