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