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 { | ||||
|     /// 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 { | ||||
|         Body { | ||||
|             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()) | ||||
|             }; | ||||
|             body.take(); | ||||
|  | ||||
|             match res.status { | ||||
|             let should_redirect = match res.status { | ||||
|                 StatusCode::MovedPermanently | | ||||
|                 StatusCode::Found | | ||||
|                 StatusCode::SeeOther => { | ||||
|  | ||||
|                     //TODO: turn this into self.redirect_policy.check() | ||||
|                     if redirect_count > 10 { | ||||
|                         return Err(::Error::TooManyRedirects); | ||||
|                     body = None; | ||||
|                     match method { | ||||
|                         Method::Get | Method::Head => {}, | ||||
|                         _ => { | ||||
|                             method = Method::Get; | ||||
|                         } | ||||
|                     } | ||||
|                     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); | ||||
|  | ||||
|                     true | ||||
|                 }, | ||||
|                 _ => { | ||||
|                     return Ok(Response { | ||||
|                         inner: res | ||||
|                     }); | ||||
|                 StatusCode::TemporaryRedirect | | ||||
|                 StatusCode::PermanentRedirect => { | ||||
|                     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] | ||||
| fn test_redirect_302_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\ | ||||
|             " | ||||
|     }; | ||||
|  | ||||
| fn test_redirect_301_and_302_and_303_changes_post_to_get() { | ||||
|     let client = reqwest::Client::new().unwrap(); | ||||
|     let res = client.post(&format!("http://{}/302", redirect.addr())) | ||||
|         .send() | ||||
|         .unwrap(); | ||||
|     assert_eq!(res.status(), &reqwest::StatusCode::Ok); | ||||
|     assert_eq!(res.headers().get(), Some(&reqwest::header::Server("test-dst".to_string()))); | ||||
|     let codes = [301, 302, 303]; | ||||
|  | ||||
|     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