use Body inside multipart Parts
This commit is contained in:
		
							
								
								
									
										83
									
								
								src/body.rs
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								src/body.rs
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| use std::io::{self, Read}; | ||||
| use std::fs::File; | ||||
| use std::fmt; | ||||
| use std::io::{self, Cursor, Read}; | ||||
|  | ||||
| use bytes::Bytes; | ||||
| use hyper::{self, Chunk}; | ||||
| @@ -16,7 +16,7 @@ use {async_impl, wait}; | ||||
| /// [builder]: ./struct.RequestBuilder.html#method.body | ||||
| #[derive(Debug)] | ||||
| pub struct Body { | ||||
|     reader: Kind, | ||||
|     kind: Kind, | ||||
| } | ||||
|  | ||||
| impl Body { | ||||
| @@ -54,7 +54,7 @@ impl Body { | ||||
|     /// ``` | ||||
|     pub fn new<R: Read + Send + 'static>(reader: R) -> Body { | ||||
|         Body { | ||||
|             reader: Kind::Reader(Box::new(reader), None), | ||||
|             kind: Kind::Reader(Box::from(reader), None), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -74,21 +74,11 @@ impl Body { | ||||
|     /// ``` | ||||
|     pub fn sized<R: Read + Send + 'static>(reader: R, len: u64) -> Body { | ||||
|         Body { | ||||
|             reader: Kind::Reader(Box::new(reader), Some(len)), | ||||
|             kind: Kind::Reader(Box::from(reader), Some(len)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // useful for tests, but not publicly exposed | ||||
| #[cfg(test)] | ||||
| pub fn read_to_string(mut body: Body) -> ::std::io::Result<String> { | ||||
|     let mut s = String::new(); | ||||
|     match body.reader { | ||||
|             Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s), | ||||
|             Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s), | ||||
|         } | ||||
|         .map(|_| s) | ||||
| } | ||||
|  | ||||
| enum Kind { | ||||
|     Reader(Box<Read + Send>, Option<u64>), | ||||
| @@ -99,7 +89,7 @@ impl From<Vec<u8>> for Body { | ||||
|     #[inline] | ||||
|     fn from(v: Vec<u8>) -> Body { | ||||
|         Body { | ||||
|             reader: Kind::Bytes(v.into()), | ||||
|             kind: Kind::Bytes(v.into()), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -116,7 +106,7 @@ impl From<&'static [u8]> for Body { | ||||
|     #[inline] | ||||
|     fn from(s: &'static [u8]) -> Body { | ||||
|         Body { | ||||
|             reader: Kind::Bytes(Bytes::from_static(s)), | ||||
|             kind: Kind::Bytes(Bytes::from_static(s)), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -133,7 +123,7 @@ impl From<File> for Body { | ||||
|     fn from(f: File) -> Body { | ||||
|         let len = f.metadata().map(|m| m.len()).ok(); | ||||
|         Body { | ||||
|             reader: Kind::Reader(Box::new(f), len), | ||||
|             kind: Kind::Reader(Box::new(f), len), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -141,8 +131,21 @@ impl From<File> for Body { | ||||
| impl fmt::Debug for Kind { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match *self { | ||||
|             Kind::Reader(_, ref v) => f.debug_tuple("Kind::Reader").field(&"_").field(v).finish(), | ||||
|             Kind::Bytes(ref v) => f.debug_tuple("Kind::Bytes").field(v).finish(), | ||||
|             Kind::Reader(_, ref v) => f.debug_struct("Reader") | ||||
|                 .field("length", &DebugLength(v)) | ||||
|                 .finish(), | ||||
|             Kind::Bytes(ref v) => fmt::Debug::fmt(v, f), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct DebugLength<'a>(&'a Option<u64>); | ||||
|  | ||||
| impl<'a> fmt::Debug for DebugLength<'a> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match *self.0 { | ||||
|             Some(ref len) => fmt::Debug::fmt(len, f), | ||||
|             None => f.write_str("Unknown"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -150,6 +153,35 @@ impl fmt::Debug for Kind { | ||||
|  | ||||
| // pub(crate) | ||||
|  | ||||
| pub fn len(body: &Body) -> Option<u64> { | ||||
|     match body.kind { | ||||
|         Kind::Reader(_, len) => len, | ||||
|         Kind::Bytes(ref bytes) => Some(bytes.len() as u64), | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub enum Reader { | ||||
|     Reader(Box<Read + Send>), | ||||
|     Bytes(Cursor<Bytes>), | ||||
| } | ||||
|  | ||||
| #[inline] | ||||
| pub fn reader(body: Body) -> Reader { | ||||
|     match body.kind { | ||||
|         Kind::Reader(r, _) => Reader::Reader(r), | ||||
|         Kind::Bytes(b) => Reader::Bytes(Cursor::new(b)), | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Read for Reader { | ||||
|     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { | ||||
|         match *self { | ||||
|             Reader::Reader(ref mut rdr) => rdr.read(buf), | ||||
|             Reader::Bytes(ref mut rdr) => rdr.read(buf), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Sender { | ||||
|     body: (Box<Read + Send>, Option<u64>), | ||||
|     tx: wait::WaitSink<::futures::sync::mpsc::Sender<hyper::Result<Chunk>>>, | ||||
| @@ -193,7 +225,7 @@ impl Sender { | ||||
|  | ||||
| #[inline] | ||||
| pub fn async(body: Body) -> (Option<Sender>, async_impl::Body, Option<u64>) { | ||||
|     match body.reader { | ||||
|     match body.kind { | ||||
|         Kind::Reader(read, len) => { | ||||
|             let (tx, rx) = hyper::Body::pair(); | ||||
|             let tx = Sender { | ||||
| @@ -208,3 +240,14 @@ pub fn async(body: Body) -> (Option<Sender>, async_impl::Body, Option<u64>) { | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // useful for tests, but not publicly exposed | ||||
| #[cfg(test)] | ||||
| pub fn read_to_string(mut body: Body) -> io::Result<String> { | ||||
|     let mut s = String::new(); | ||||
|     match body.kind { | ||||
|             Kind::Reader(ref mut reader, _) => reader.read_to_string(&mut s), | ||||
|             Kind::Bytes(ref mut bytes) => (&**bytes).read_to_string(&mut s), | ||||
|         } | ||||
|         .map(|_| s) | ||||
| } | ||||
|   | ||||
| @@ -9,8 +9,9 @@ use mime_guess; | ||||
| use url::percent_encoding; | ||||
| use uuid::Uuid; | ||||
|  | ||||
| use {Body}; | ||||
|  | ||||
| /// A multipart/form-data request. | ||||
| #[derive(Debug)] | ||||
| pub struct Form { | ||||
|     boundary: String, | ||||
|     fields: Vec<(Cow<'static, str>, Part)>, | ||||
| @@ -27,6 +28,12 @@ impl Form { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Get the boundary that this form will use. | ||||
|     #[inline] | ||||
|     pub fn boundary(&self) -> &str { | ||||
|         &self.boundary | ||||
|     } | ||||
|  | ||||
|     /// Add a data field with supplied name and value. | ||||
|     /// | ||||
|     /// # Examples | ||||
| @@ -38,7 +45,7 @@ impl Form { | ||||
|     /// ``` | ||||
|     pub fn text<T, U>(self, name: T, value: U) -> Form | ||||
|     where T: Into<Cow<'static, str>>, | ||||
|           U: AsRef<[u8]> + Send + 'static | ||||
|           U: Into<Cow<'static, str>>, | ||||
|     { | ||||
|         self.part(name, Part::text(value)) | ||||
|     } | ||||
| @@ -84,7 +91,7 @@ impl Form { | ||||
|     fn compute_length(&mut self) -> Option<u64> { | ||||
|         let mut length = 0u64; | ||||
|         for &(ref name, ref field) in self.fields.iter() { | ||||
|             match field.value_length { | ||||
|             match ::body::len(&field.value) { | ||||
|                 Some(value_length) => { | ||||
|                     // We are constructing the header just to get its length. To not have to | ||||
|                     // construct it again when the request is sent we cache these headers. | ||||
| @@ -108,38 +115,47 @@ impl Form { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for Form { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Form") | ||||
|             .field("boundary", &self.boundary) | ||||
|             .field("parts", &self.fields) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
| /// A field in a multipart form. | ||||
| pub struct Part { | ||||
|     value: Box<Read + Send>, | ||||
|     value_length: Option<u64>, | ||||
|     value: Body, | ||||
|     mime: Option<Mime>, | ||||
|     file_name: Option<Cow<'static, str>>, | ||||
| } | ||||
|  | ||||
| impl Part { | ||||
|     /// Makes a text parameter. | ||||
|     pub fn text<T: AsRef<[u8]> + Send + 'static>(value: T) -> Part { | ||||
|         let value_length = Some(value.as_ref().len() as u64); | ||||
|         let mut field = Part::new(Box::new(Cursor::new(value))); | ||||
|         field.value_length = value_length; | ||||
|         field | ||||
|     pub fn text<T>(value: T) -> Part | ||||
|     where T: Into<Cow<'static, str>>, | ||||
|     { | ||||
|         let body = match value.into() { | ||||
|             Cow::Borrowed(slice) => Body::from(slice), | ||||
|             Cow::Owned(string) => Body::from(string), | ||||
|         }; | ||||
|         Part::new(body) | ||||
|     } | ||||
|  | ||||
|     /// Adds a generic reader. | ||||
|     /// | ||||
|     /// Does not set filename or mime. | ||||
|     pub fn reader<T: Read + Send + 'static>(value: T) -> Part { | ||||
|         Part::new(Box::from(value)) | ||||
|         Part::new(Body::new(value)) | ||||
|     } | ||||
|  | ||||
|     /// Adds a generic reader with known length. | ||||
|     /// | ||||
|     /// Does not set filename or mime. | ||||
|     pub fn reader_with_length<T: Read + Send + 'static>(value: T, length: u64) -> Part { | ||||
|         let mut field = Part::new(Box::new(value)); | ||||
|         field.value_length = Some(length); | ||||
|         field | ||||
|         Part::new(Body::sized(value, length)) | ||||
|     } | ||||
|  | ||||
|     /// Makes a file parameter. | ||||
| @@ -157,18 +173,15 @@ impl Part { | ||||
|             .unwrap_or(""); | ||||
|         let mime = mime_guess::get_mime_type(ext); | ||||
|         let file = File::open(path)?; | ||||
|         let file_length = file.metadata().ok().map(|meta| meta.len()); | ||||
|         let mut field = Part::new(Box::new(file)); | ||||
|         field.value_length = file_length; | ||||
|         let mut field = Part::new(Body::from(file)); | ||||
|         field.mime = Some(mime); | ||||
|         field.file_name = file_name; | ||||
|         Ok(field) | ||||
|     } | ||||
|  | ||||
|     fn new(value: Box<Read + Send + 'static>) -> Part { | ||||
|     fn new(value: Body) -> Part { | ||||
|         Part { | ||||
|             value: value, | ||||
|             value_length: None, | ||||
|             mime: None, | ||||
|             file_name: None, | ||||
|         } | ||||
| @@ -190,7 +203,7 @@ impl Part { | ||||
| impl fmt::Debug for Part { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.debug_struct("Part") | ||||
|             .field("value_length", &self.value_length) | ||||
|             .field("value", &self.value) | ||||
|             .field("mime", &self.mime) | ||||
|             .field("file_name", &self.file_name) | ||||
|             .finish() | ||||
| @@ -251,7 +264,7 @@ impl Reader { | ||||
|                 } else { | ||||
|                     header(&name, &field) | ||||
|                 } | ||||
|             )).chain(field.value) | ||||
|             )).chain(::body::reader(field.value)) | ||||
|                 .chain(Cursor::new("\r\n")); | ||||
|             // According to https://tools.ietf.org/html/rfc2046#section-5.1.1 | ||||
|             // the very last field has a special boundary | ||||
| @@ -326,9 +339,9 @@ mod tests { | ||||
|     #[test] | ||||
|     fn form_empty() { | ||||
|         let mut output = Vec::new(); | ||||
|         let mut request = Form::new(); | ||||
|         let length = request.compute_length(); | ||||
|         request.reader().read_to_end(&mut output).unwrap(); | ||||
|         let mut form = Form::new(); | ||||
|         let length = form.compute_length(); | ||||
|         form.reader().read_to_end(&mut output).unwrap(); | ||||
|         assert_eq!(output, b""); | ||||
|         assert_eq!(length.unwrap(), 0); | ||||
|     } | ||||
| @@ -336,7 +349,7 @@ mod tests { | ||||
|     #[test] | ||||
|     fn read_to_end() { | ||||
|         let mut output = Vec::new(); | ||||
|         let mut request = Form::new() | ||||
|         let mut form = Form::new() | ||||
|             .part("reader1", Part::reader(::std::io::empty())) | ||||
|             .part("key1", Part::text("value1")) | ||||
|             .part( | ||||
| @@ -348,8 +361,8 @@ mod tests { | ||||
|                 "key3", | ||||
|                 Part::text("value3").file_name("filename"), | ||||
|             ); | ||||
|         request.boundary = "boundary".to_string(); | ||||
|         let length = request.compute_length(); | ||||
|         form.boundary = "boundary".to_string(); | ||||
|         let length = form.compute_length(); | ||||
|         let expected = "--boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ | ||||
|                         \r\n\ | ||||
| @@ -366,7 +379,7 @@ mod tests { | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ | ||||
|                         value3\r\n--boundary--"; | ||||
|         request.reader().read_to_end(&mut output).unwrap(); | ||||
|         form.reader().read_to_end(&mut output).unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
| @@ -380,7 +393,7 @@ mod tests { | ||||
|     #[test] | ||||
|     fn read_to_end_with_length() { | ||||
|         let mut output = Vec::new(); | ||||
|         let mut request = Form::new() | ||||
|         let mut form = Form::new() | ||||
|             .text("key1", "value1") | ||||
|             .part( | ||||
|                 "key2", | ||||
| @@ -390,8 +403,8 @@ mod tests { | ||||
|                 "key3", | ||||
|                 Part::text("value3").file_name("filename"), | ||||
|             ); | ||||
|         request.boundary = "boundary".to_string(); | ||||
|         let length = request.compute_length(); | ||||
|         form.boundary = "boundary".to_string(); | ||||
|         let length = form.compute_length(); | ||||
|         let expected = "--boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key1\"\r\n\r\n\ | ||||
|                         value1\r\n\ | ||||
| @@ -402,7 +415,7 @@ mod tests { | ||||
|                         --boundary\r\n\ | ||||
|                         Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\r\n\ | ||||
|                         value3\r\n--boundary--"; | ||||
|         request.reader().read_to_end(&mut output).unwrap(); | ||||
|         form.reader().read_to_end(&mut output).unwrap(); | ||||
|         // These prints are for debug purposes in case the test fails | ||||
|         println!( | ||||
|             "START REAL\n{}\nEND REAL", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user