fix(client): GET and HEAD shouldn't add Transfer-Encoding
Also adds an EmptyWriter, used for GET and HEAD requests, which will return an io::ShortWrite error if the user ever tries to write to a GET or HEAD request. Closes #77
This commit is contained in:
		| @@ -7,7 +7,7 @@ use method::{mod, Get, Post, Delete, Put, Patch, Head, Options}; | |||||||
| use header::Headers; | use header::Headers; | ||||||
| use header::common::{mod, Host}; | use header::common::{mod, Host}; | ||||||
| use net::{NetworkStream, NetworkConnector, HttpStream, Fresh, Streaming}; | use net::{NetworkStream, NetworkConnector, HttpStream, Fresh, Streaming}; | ||||||
| use http::{HttpWriter, ThroughWriter, ChunkedWriter, SizedWriter, LINE_ENDING}; | use http::{HttpWriter, ThroughWriter, ChunkedWriter, SizedWriter, EmptyWriter, LINE_ENDING}; | ||||||
| use version; | use version; | ||||||
| use {HttpResult, HttpUriError}; | use {HttpResult, HttpUriError}; | ||||||
| use client::Response; | use client::Response; | ||||||
| @@ -117,6 +117,11 @@ impl Request<Fresh> { | |||||||
|         try_io!(self.body.write(LINE_ENDING)); |         try_io!(self.body.write(LINE_ENDING)); | ||||||
|  |  | ||||||
|  |  | ||||||
|  |         let stream = match self.method { | ||||||
|  |             Get | Head => { | ||||||
|  |                 EmptyWriter(self.body.unwrap()) | ||||||
|  |             }, | ||||||
|  |             _ => { | ||||||
|                 let mut chunked = true; |                 let mut chunked = true; | ||||||
|                 let mut len = 0; |                 let mut len = 0; | ||||||
|  |  | ||||||
| @@ -150,10 +155,12 @@ impl Request<Fresh> { | |||||||
|  |  | ||||||
|                 try_io!(self.body.write(LINE_ENDING)); |                 try_io!(self.body.write(LINE_ENDING)); | ||||||
|  |  | ||||||
|         let stream = if chunked { |                 if chunked { | ||||||
|                     ChunkedWriter(self.body.unwrap()) |                     ChunkedWriter(self.body.unwrap()) | ||||||
|                 } else { |                 } else { | ||||||
|                     SizedWriter(self.body.unwrap(), len) |                     SizedWriter(self.body.unwrap(), len) | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         Ok(Request { |         Ok(Request { | ||||||
| @@ -192,3 +199,38 @@ impl Writer for Request<Streaming> { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(test)] | ||||||
|  | mod tests { | ||||||
|  |     use std::boxed::BoxAny; | ||||||
|  |     use std::str::from_utf8; | ||||||
|  |     use url::Url; | ||||||
|  |     use method::{Get, Head}; | ||||||
|  |     use mock::MockStream; | ||||||
|  |     use super::Request; | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_get_empty_body() { | ||||||
|  |         let req = Request::with_stream::<MockStream>( | ||||||
|  |             Get, Url::parse("http://example.dom").unwrap() | ||||||
|  |         ).unwrap(); | ||||||
|  |         let req = req.start().unwrap(); | ||||||
|  |         let stream = *req.body.end().unwrap().unwrap().downcast::<MockStream>().unwrap(); | ||||||
|  |         let bytes = stream.write.unwrap(); | ||||||
|  |         let s = from_utf8(bytes[]).unwrap(); | ||||||
|  |         assert!(!s.contains("Content-Length:")); | ||||||
|  |         assert!(!s.contains("Transfer-Encoding:")); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn test_head_empty_body() { | ||||||
|  |         let req = Request::with_stream::<MockStream>( | ||||||
|  |             Head, Url::parse("http://example.dom").unwrap() | ||||||
|  |         ).unwrap(); | ||||||
|  |         let req = req.start().unwrap(); | ||||||
|  |         let stream = *req.body.end().unwrap().unwrap().downcast::<MockStream>().unwrap(); | ||||||
|  |         let bytes = stream.write.unwrap(); | ||||||
|  |         let s = from_utf8(bytes[]).unwrap(); | ||||||
|  |         assert!(!s.contains("Content-Length:")); | ||||||
|  |         assert!(!s.contains("Transfer-Encoding:")); | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -100,11 +100,11 @@ mod tests { | |||||||
|             status: status::Ok, |             status: status::Ok, | ||||||
|             headers: Headers::new(), |             headers: Headers::new(), | ||||||
|             version: version::Http11, |             version: version::Http11, | ||||||
|             body: EofReader(BufferedReader::new(box MockStream as Box<NetworkStream + Send>)) |             body: EofReader(BufferedReader::new(box MockStream::new() as Box<NetworkStream + Send>)) | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         let b = res.unwrap().downcast::<MockStream>().unwrap(); |         let b = res.unwrap().downcast::<MockStream>().unwrap(); | ||||||
|         assert_eq!(b, box MockStream); |         assert_eq!(b, box MockStream::new()); | ||||||
|  |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										18
									
								
								src/http.rs
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								src/http.rs
									
									
									
									
									
								
							| @@ -157,6 +157,8 @@ pub enum HttpWriter<W: Writer> { | |||||||
|     /// |     /// | ||||||
|     /// Enforces that the body is not longer than the Content-Length header. |     /// Enforces that the body is not longer than the Content-Length header. | ||||||
|     SizedWriter(W, uint), |     SizedWriter(W, uint), | ||||||
|  |     /// A writer that should not write any body. | ||||||
|  |     EmptyWriter(W), | ||||||
| } | } | ||||||
|  |  | ||||||
| impl<W: Writer> HttpWriter<W> { | impl<W: Writer> HttpWriter<W> { | ||||||
| @@ -166,7 +168,8 @@ impl<W: Writer> HttpWriter<W> { | |||||||
|         match self { |         match self { | ||||||
|             ThroughWriter(w) => w, |             ThroughWriter(w) => w, | ||||||
|             ChunkedWriter(w) => w, |             ChunkedWriter(w) => w, | ||||||
|             SizedWriter(w, _) => w |             SizedWriter(w, _) => w, | ||||||
|  |             EmptyWriter(w) => w, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -204,6 +207,18 @@ impl<W: Writer> Writer for HttpWriter<W> { | |||||||
|                     *remaining -= len; |                     *remaining -= len; | ||||||
|                     w.write(msg) |                     w.write(msg) | ||||||
|                 } |                 } | ||||||
|  |             }, | ||||||
|  |             EmptyWriter(..) => { | ||||||
|  |                 let bytes = msg.len(); | ||||||
|  |                 if bytes == 0 { | ||||||
|  |                     Ok(()) | ||||||
|  |                 } else { | ||||||
|  |                     Err(io::IoError { | ||||||
|  |                         kind: io::ShortWrite(bytes), | ||||||
|  |                         desc: "EmptyWriter cannot write any bytes", | ||||||
|  |                         detail: Some("Cannot include a body with this kind of message".into_string()) | ||||||
|  |                     }) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -214,6 +229,7 @@ impl<W: Writer> Writer for HttpWriter<W> { | |||||||
|             ThroughWriter(ref mut w) => w.flush(), |             ThroughWriter(ref mut w) => w.flush(), | ||||||
|             ChunkedWriter(ref mut w) => w.flush(), |             ChunkedWriter(ref mut w) => w.flush(), | ||||||
|             SizedWriter(ref mut w, _) => w.flush(), |             SizedWriter(ref mut w, _) => w.flush(), | ||||||
|  |             EmptyWriter(ref mut w) => w.flush(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								src/mock.rs
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								src/mock.rs
									
									
									
									
									
								
							| @@ -1,20 +1,55 @@ | |||||||
| use std::io::IoResult; | use std::fmt; | ||||||
|  | use std::io::{IoResult, MemReader, MemWriter}; | ||||||
| use std::io::net::ip::{SocketAddr, ToSocketAddr}; | use std::io::net::ip::{SocketAddr, ToSocketAddr}; | ||||||
|  |  | ||||||
| use net::{NetworkStream, NetworkConnector}; | use net::{NetworkStream, NetworkConnector}; | ||||||
|  |  | ||||||
| #[deriving(Clone, PartialEq, Show)] | pub struct MockStream { | ||||||
| pub struct MockStream; |     pub read: MemReader, | ||||||
|  |     pub write: MemWriter, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl Clone for MockStream { | ||||||
|  |     fn clone(&self) -> MockStream { | ||||||
|  |         MockStream { | ||||||
|  |             read: MemReader::new(self.read.get_ref().to_vec()), | ||||||
|  |             write: MemWriter::from_vec(self.write.get_ref().to_vec()), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl PartialEq for MockStream { | ||||||
|  |     fn eq(&self, other: &MockStream) -> bool { | ||||||
|  |         self.read.get_ref() == other.read.get_ref() && | ||||||
|  |             self.write.get_ref() == other.write.get_ref() | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl fmt::Show for MockStream { | ||||||
|  |     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||||
|  |         write!(f, "MockStream {{ read: {}, write: {} }}", | ||||||
|  |                self.read.get_ref(), self.write.get_ref()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl MockStream { | ||||||
|  |     pub fn new() -> MockStream { | ||||||
|  |         MockStream { | ||||||
|  |             read: MemReader::new(vec![]), | ||||||
|  |             write: MemWriter::new(), | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| impl Reader for MockStream { | impl Reader for MockStream { | ||||||
|     fn read(&mut self, _buf: &mut [u8]) -> IoResult<uint> { |     fn read(&mut self, buf: &mut [u8]) -> IoResult<uint> { | ||||||
|         unimplemented!() |         self.read.read(buf) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Writer for MockStream { | impl Writer for MockStream { | ||||||
|     fn write(&mut self, _msg: &[u8]) -> IoResult<()> { |     fn write(&mut self, msg: &[u8]) -> IoResult<()> { | ||||||
|         unimplemented!() |         self.write.write(msg) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -27,6 +62,6 @@ impl NetworkStream for MockStream { | |||||||
|  |  | ||||||
| impl NetworkConnector for MockStream { | impl NetworkConnector for MockStream { | ||||||
|     fn connect<To: ToSocketAddr>(_addr: To, _scheme: &str) -> IoResult<MockStream> { |     fn connect<To: ToSocketAddr>(_addr: To, _scheme: &str) -> IoResult<MockStream> { | ||||||
|         Ok(MockStream) |         Ok(MockStream::new()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -274,19 +274,19 @@ mod tests { | |||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_downcast_box_stream() { |     fn test_downcast_box_stream() { | ||||||
|         let stream = box MockStream as Box<NetworkStream + Send>; |         let stream = box MockStream::new() as Box<NetworkStream + Send>; | ||||||
|  |  | ||||||
|         let mock = stream.downcast::<MockStream>().unwrap(); |         let mock = stream.downcast::<MockStream>().unwrap(); | ||||||
|         assert_eq!(mock, box MockStream); |         assert_eq!(mock, box MockStream::new()); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn test_downcast_unchecked_box_stream() { |     fn test_downcast_unchecked_box_stream() { | ||||||
|         let stream = box MockStream as Box<NetworkStream + Send>; |         let stream = box MockStream::new() as Box<NetworkStream + Send>; | ||||||
|  |  | ||||||
|         let mock = unsafe { stream.downcast_unchecked::<MockStream>() }; |         let mock = unsafe { stream.downcast_unchecked::<MockStream>() }; | ||||||
|         assert_eq!(mock, box MockStream); |         assert_eq!(mock, box MockStream::new()); | ||||||
|  |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user