Merge pull request #21 from seanmonstar/aidanhs-aphs-307-308
Add handling of 307 and 308 redirects
This commit is contained in:
18
src/body.rs
18
src/body.rs
@@ -10,6 +10,17 @@ pub struct Body {
|
|||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
/// Instantiate a `Body` from a reader.
|
/// Instantiate a `Body` from a reader.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// While allowing for many types to be used, these bodies do not have
|
||||||
|
/// a way to reset to the beginning and be reused. This means that when
|
||||||
|
/// encountering a 307 or 308 status code, instead of repeating the
|
||||||
|
/// request at the new location, the `Response` will be returned with
|
||||||
|
/// the redirect status code set.
|
||||||
|
///
|
||||||
|
/// A `Body` constructed from a set of bytes, like `String` or `Vec<u8>`,
|
||||||
|
/// are stored differently and can be reused.
|
||||||
pub fn new<R: Read + 'static>(reader: R) -> Body {
|
pub fn new<R: Read + 'static>(reader: R) -> Body {
|
||||||
Body {
|
Body {
|
||||||
reader: Kind::Reader(Box::new(reader), None),
|
reader: Kind::Reader(Box::new(reader), None),
|
||||||
@@ -115,3 +126,10 @@ pub fn as_hyper_body<'a>(body: &'a mut Body) -> ::hyper::client::Body<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn can_reset(body: &Body) -> bool {
|
||||||
|
match body.reader {
|
||||||
|
Kind::Bytes(_) => true,
|
||||||
|
Kind::Reader(..) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -198,57 +198,68 @@ impl<'a> RequestBuilder<'a> {
|
|||||||
|
|
||||||
try!(req.send())
|
try!(req.send())
|
||||||
};
|
};
|
||||||
body.take();
|
|
||||||
|
|
||||||
match res.status {
|
let should_redirect = match res.status {
|
||||||
StatusCode::MovedPermanently |
|
StatusCode::MovedPermanently |
|
||||||
StatusCode::Found |
|
StatusCode::Found |
|
||||||
StatusCode::SeeOther => {
|
StatusCode::SeeOther => {
|
||||||
|
body = None;
|
||||||
//TODO: turn this into self.redirect_policy.check()
|
match method {
|
||||||
if redirect_count > 10 {
|
Method::Get | Method::Head => {},
|
||||||
return Err(::Error::TooManyRedirects);
|
_ => {
|
||||||
|
method = Method::Get;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
redirect_count += 1;
|
true
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
},
|
},
|
||||||
_ => {
|
StatusCode::TemporaryRedirect |
|
||||||
return Ok(Response {
|
StatusCode::PermanentRedirect => {
|
||||||
inner: res
|
if let Some(ref body) = body {
|
||||||
});
|
body::can_reset(body)
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_redirect {
|
||||||
|
//TODO: turn this into self.redirect_policy.check()
|
||||||
|
if redirect_count > 10 {
|
||||||
|
return Err(::Error::TooManyRedirects);
|
||||||
}
|
}
|
||||||
|
redirect_count += 1;
|
||||||
|
|
||||||
|
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 {:?} '{}'", method, url);
|
||||||
|
|
||||||
|
//TODO: removeSensitiveHeaders(&mut headers, &url);
|
||||||
|
} else {
|
||||||
|
return Ok(Response {
|
||||||
|
inner: res
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
163
tests/client.rs
163
tests/client.rs
@@ -33,45 +33,130 @@ fn test_get() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_redirect_302_changes_post_to_get() {
|
fn test_redirect_301_and_302_and_303_changes_post_to_get() {
|
||||||
|
|
||||||
let redirect = server! {
|
|
||||||
request: b"\
|
|
||||||
POST /302 HTTP/1.1\r\n\
|
|
||||||
Host: $HOST\r\n\
|
|
||||||
User-Agent: $USERAGENT\r\n\
|
|
||||||
Content-Length: 0\r\n\
|
|
||||||
\r\n\
|
|
||||||
",
|
|
||||||
response: b"\
|
|
||||||
HTTP/1.1 302 Found\r\n\
|
|
||||||
Server: test-redirect\r\n\
|
|
||||||
Content-Length: 0\r\n\
|
|
||||||
Location: /dst\r\n\
|
|
||||||
Connection: close\r\n\
|
|
||||||
\r\n\
|
|
||||||
",
|
|
||||||
|
|
||||||
request: b"\
|
|
||||||
GET /dst HTTP/1.1\r\n\
|
|
||||||
Host: $HOST\r\n\
|
|
||||||
User-Agent: $USERAGENT\r\n\
|
|
||||||
Referer: http://$HOST/302\r\n\
|
|
||||||
\r\n\
|
|
||||||
",
|
|
||||||
response: b"\
|
|
||||||
HTTP/1.1 200 OK\r\n\
|
|
||||||
Server: test-dst\r\n\
|
|
||||||
Content-Length: 0\r\n\
|
|
||||||
\r\n\
|
|
||||||
"
|
|
||||||
};
|
|
||||||
|
|
||||||
let client = reqwest::Client::new().unwrap();
|
let client = reqwest::Client::new().unwrap();
|
||||||
let res = client.post(&format!("http://{}/302", redirect.addr()))
|
let codes = [301, 302, 303];
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(res.status(), &reqwest::StatusCode::Ok);
|
|
||||||
assert_eq!(res.headers().get(), Some(&reqwest::header::Server("test-dst".to_string())));
|
|
||||||
|
|
||||||
|
for code in codes.iter() {
|
||||||
|
let redirect = server! {
|
||||||
|
request: format!("\
|
||||||
|
POST /{} HTTP/1.1\r\n\
|
||||||
|
Host: $HOST\r\n\
|
||||||
|
User-Agent: $USERAGENT\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
\r\n\
|
||||||
|
", code),
|
||||||
|
response: format!("\
|
||||||
|
HTTP/1.1 {} reason\r\n\
|
||||||
|
Server: test-redirect\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
Location: /dst\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
", code),
|
||||||
|
|
||||||
|
request: format!("\
|
||||||
|
GET /dst HTTP/1.1\r\n\
|
||||||
|
Host: $HOST\r\n\
|
||||||
|
User-Agent: $USERAGENT\r\n\
|
||||||
|
Referer: http://$HOST/{}\r\n\
|
||||||
|
\r\n\
|
||||||
|
", code),
|
||||||
|
response: b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
Server: test-dst\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = client.post(&format!("http://{}/{}", redirect.addr(), code))
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(res.status(), &reqwest::StatusCode::Ok);
|
||||||
|
assert_eq!(res.headers().get(), Some(&reqwest::header::Server("test-dst".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_307_and_308_tries_to_post_again() {
|
||||||
|
let client = reqwest::Client::new().unwrap();
|
||||||
|
let codes = [307, 308];
|
||||||
|
for code in codes.iter() {
|
||||||
|
let redirect = server! {
|
||||||
|
request: format!("\
|
||||||
|
POST /{} HTTP/1.1\r\n\
|
||||||
|
Host: $HOST\r\n\
|
||||||
|
User-Agent: $USERAGENT\r\n\
|
||||||
|
Content-Length: 5\r\n\
|
||||||
|
\r\n\
|
||||||
|
Hello\
|
||||||
|
", code),
|
||||||
|
response: format!("\
|
||||||
|
HTTP/1.1 {} reason\r\n\
|
||||||
|
Server: test-redirect\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
Location: /dst\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
", code),
|
||||||
|
|
||||||
|
request: format!("\
|
||||||
|
POST /dst HTTP/1.1\r\n\
|
||||||
|
Host: $HOST\r\n\
|
||||||
|
User-Agent: $USERAGENT\r\n\
|
||||||
|
Referer: http://$HOST/{}\r\n\
|
||||||
|
Content-Length: 5\r\n\
|
||||||
|
\r\n\
|
||||||
|
Hello\
|
||||||
|
", code),
|
||||||
|
response: b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
Server: test-dst\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
\r\n\
|
||||||
|
"
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = client.post(&format!("http://{}/{}", redirect.addr(), code))
|
||||||
|
.body("Hello")
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(res.status(), &reqwest::StatusCode::Ok);
|
||||||
|
assert_eq!(res.headers().get(), Some(&reqwest::header::Server("test-dst".to_string())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redirect_307_does_not_try_if_reader_cannot_reset() {
|
||||||
|
let client = reqwest::Client::new().unwrap();
|
||||||
|
let codes = [307, 308];
|
||||||
|
for &code in codes.iter() {
|
||||||
|
let redirect = server! {
|
||||||
|
request: format!("\
|
||||||
|
POST /{} HTTP/1.1\r\n\
|
||||||
|
Host: $HOST\r\n\
|
||||||
|
User-Agent: $USERAGENT\r\n\
|
||||||
|
Transfer-Encoding: chunked\r\n\
|
||||||
|
\r\n\
|
||||||
|
5\r\n\
|
||||||
|
Hello\r\n\
|
||||||
|
0\r\n\r\n\
|
||||||
|
", code),
|
||||||
|
response: format!("\
|
||||||
|
HTTP/1.1 {} reason\r\n\
|
||||||
|
Server: test-redirect\r\n\
|
||||||
|
Content-Length: 0\r\n\
|
||||||
|
Location: /dst\r\n\
|
||||||
|
Connection: close\r\n\
|
||||||
|
\r\n\
|
||||||
|
", code)
|
||||||
|
};
|
||||||
|
|
||||||
|
let res = client.post(&format!("http://{}/{}", redirect.addr(), code))
|
||||||
|
.body(reqwest::Body::new(&b"Hello"[..]))
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(res.status(), &reqwest::StatusCode::from_u16(code));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user