feat(multipart): Adds support for manually setting size
I also added a simple sanity test to make sure it doesn't override a `Body` with an actual size. I also double checked that this works with a project where we are using streams. Closes #1090
This commit is contained in:
		
				
					committed by
					
						 Sean McArthur
						Sean McArthur
					
				
			
			
				
	
			
			
			
						parent
						
							b1d498ffa8
						
					
				
				
					commit
					2ca0e26cfa
				
			| @@ -3,7 +3,7 @@ use std::borrow::Cow; | |||||||
| use std::fmt; | use std::fmt; | ||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
|  |  | ||||||
| use bytes::{Bytes}; | use bytes::Bytes; | ||||||
| use http::HeaderMap; | use http::HeaderMap; | ||||||
| use mime_guess::Mime; | use mime_guess::Mime; | ||||||
| use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; | use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC}; | ||||||
| @@ -22,6 +22,7 @@ pub struct Form { | |||||||
| pub struct Part { | pub struct Part { | ||||||
|     meta: PartMetadata, |     meta: PartMetadata, | ||||||
|     value: Body, |     value: Body, | ||||||
|  |     body_length: Option<u64>, | ||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) struct FormParts<P> { | pub(crate) struct FormParts<P> { | ||||||
| @@ -190,7 +191,7 @@ impl Part { | |||||||
|             Cow::Borrowed(slice) => Body::from(slice), |             Cow::Borrowed(slice) => Body::from(slice), | ||||||
|             Cow::Owned(string) => Body::from(string), |             Cow::Owned(string) => Body::from(string), | ||||||
|         }; |         }; | ||||||
|         Part::new(body) |         Part::new(body, None) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Makes a new parameter from arbitrary bytes. |     /// Makes a new parameter from arbitrary bytes. | ||||||
| @@ -202,18 +203,26 @@ impl Part { | |||||||
|             Cow::Borrowed(slice) => Body::from(slice), |             Cow::Borrowed(slice) => Body::from(slice), | ||||||
|             Cow::Owned(vec) => Body::from(vec), |             Cow::Owned(vec) => Body::from(vec), | ||||||
|         }; |         }; | ||||||
|         Part::new(body) |         Part::new(body, None) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Makes a new parameter from an arbitrary stream. |     /// Makes a new parameter from an arbitrary stream. | ||||||
|     pub fn stream<T: Into<Body>>(value: T) -> Part { |     pub fn stream<T: Into<Body>>(value: T) -> Part { | ||||||
|         Part::new(value.into()) |         Part::new(value.into(), None) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn new(value: Body) -> Part { |     /// Makes a new parameter from an arbitrary stream with a known length. This is particularly | ||||||
|  |     /// useful when adding something like file contents as a stream, where you can know the content | ||||||
|  |     /// length beforehand. | ||||||
|  |     pub fn stream_with_length<T: Into<Body>>(value: T, length: u64) -> Part { | ||||||
|  |         Part::new(value.into(), Some(length)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn new(value: Body, body_length: Option<u64>) -> Part { | ||||||
|         Part { |         Part { | ||||||
|             meta: PartMetadata::new(), |             meta: PartMetadata::new(), | ||||||
|             value, |             value, | ||||||
|  |             body_length, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -241,7 +250,7 @@ impl Part { | |||||||
|     { |     { | ||||||
|         Part { |         Part { | ||||||
|             meta: func(self.meta), |             meta: func(self.meta), | ||||||
|             value: self.value, |             ..self | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -257,8 +266,12 @@ impl fmt::Debug for Part { | |||||||
|  |  | ||||||
| impl PartProps for Part { | impl PartProps for Part { | ||||||
|     fn value_len(&self) -> Option<u64> { |     fn value_len(&self) -> Option<u64> { | ||||||
|  |         if self.body_length.is_some() { | ||||||
|  |             self.body_length | ||||||
|  |         } else { | ||||||
|             self.value.content_length() |             self.value.content_length() | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     fn metadata(&self) -> &PartMetadata { |     fn metadata(&self) -> &PartMetadata { | ||||||
|         &self.meta |         &self.meta | ||||||
| @@ -508,7 +521,11 @@ mod tests { | |||||||
|     fn form_empty() { |     fn form_empty() { | ||||||
|         let form = Form::new(); |         let form = Form::new(); | ||||||
|  |  | ||||||
|         let mut rt = runtime::Builder::new().basic_scheduler().enable_all().build().expect("new rt"); |         let mut rt = runtime::Builder::new() | ||||||
|  |             .basic_scheduler() | ||||||
|  |             .enable_all() | ||||||
|  |             .build() | ||||||
|  |             .expect("new rt"); | ||||||
|         let body = form.stream().into_stream(); |         let body = form.stream().into_stream(); | ||||||
|         let s = body.map_ok(|try_c| try_c.to_vec()).try_concat(); |         let s = body.map_ok(|try_c| try_c.to_vec()).try_concat(); | ||||||
|  |  | ||||||
| @@ -524,7 +541,7 @@ mod tests { | |||||||
|                 Part::stream(Body::stream(stream::once(future::ready::< |                 Part::stream(Body::stream(stream::once(future::ready::< | ||||||
|                     Result<String, crate::Error>, |                     Result<String, crate::Error>, | ||||||
|                 >(Ok( |                 >(Ok( | ||||||
|                     "part1".to_owned(), |                     "part1".to_owned() | ||||||
|                 ))))), |                 ))))), | ||||||
|             ) |             ) | ||||||
|             .part("key1", Part::text("value1")) |             .part("key1", Part::text("value1")) | ||||||
| @@ -534,13 +551,12 @@ mod tests { | |||||||
|                 Part::stream(Body::stream(stream::once(future::ready::< |                 Part::stream(Body::stream(stream::once(future::ready::< | ||||||
|                     Result<String, crate::Error>, |                     Result<String, crate::Error>, | ||||||
|                 >(Ok( |                 >(Ok( | ||||||
|                     "part2".to_owned(), |                     "part2".to_owned() | ||||||
|                 ))))), |                 ))))), | ||||||
|             ) |             ) | ||||||
|             .part("key3", Part::text("value3").file_name("filename")); |             .part("key3", Part::text("value3").file_name("filename")); | ||||||
|         form.inner.boundary = "boundary".to_string(); |         form.inner.boundary = "boundary".to_string(); | ||||||
|         let expected = |         let expected = "--boundary\r\n\ | ||||||
|             "--boundary\r\n\ |  | ||||||
|              Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ |              Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\ | ||||||
|              part1\r\n\ |              part1\r\n\ | ||||||
|              --boundary\r\n\ |              --boundary\r\n\ | ||||||
| @@ -556,7 +572,11 @@ 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--\r\n"; |              value3\r\n--boundary--\r\n"; | ||||||
|         let mut rt = runtime::Builder::new().basic_scheduler().enable_all().build().expect("new rt"); |         let mut rt = runtime::Builder::new() | ||||||
|  |             .basic_scheduler() | ||||||
|  |             .enable_all() | ||||||
|  |             .build() | ||||||
|  |             .expect("new rt"); | ||||||
|         let body = form.stream().into_stream(); |         let body = form.stream().into_stream(); | ||||||
|         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); |         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); | ||||||
|  |  | ||||||
| @@ -583,7 +603,11 @@ mod tests { | |||||||
|                         \r\n\ |                         \r\n\ | ||||||
|                         value2\r\n\ |                         value2\r\n\ | ||||||
|                         --boundary--\r\n"; |                         --boundary--\r\n"; | ||||||
|         let mut rt = runtime::Builder::new().basic_scheduler().enable_all().build().expect("new rt"); |         let mut rt = runtime::Builder::new() | ||||||
|  |             .basic_scheduler() | ||||||
|  |             .enable_all() | ||||||
|  |             .build() | ||||||
|  |             .expect("new rt"); | ||||||
|         let body = form.stream().into_stream(); |         let body = form.stream().into_stream(); | ||||||
|         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); |         let s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat(); | ||||||
|  |  | ||||||
| @@ -597,6 +621,29 @@ mod tests { | |||||||
|         assert_eq!(std::str::from_utf8(&out).unwrap(), expected); |         assert_eq!(std::str::from_utf8(&out).unwrap(), expected); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     #[test] | ||||||
|  |     fn correct_content_length() { | ||||||
|  |         // Setup an arbitrary data stream | ||||||
|  |         let stream_data = b"just some stream data"; | ||||||
|  |         let stream_len = stream_data.len(); | ||||||
|  |         let stream_data = stream_data | ||||||
|  |             .chunks(3) | ||||||
|  |             .map(|c| Ok::<_, std::io::Error>(Bytes::from(c))); | ||||||
|  |         let the_stream = futures_util::stream::iter(stream_data); | ||||||
|  |  | ||||||
|  |         let bytes_data = b"some bytes data".to_vec(); | ||||||
|  |         let bytes_len = bytes_data.len(); | ||||||
|  |  | ||||||
|  |         let stream_part = Part::stream_with_length(Body::stream(the_stream), stream_len as u64); | ||||||
|  |         let body_part = Part::bytes(bytes_data); | ||||||
|  |  | ||||||
|  |         // A simple check to make sure we get the configured body length | ||||||
|  |         assert_eq!(stream_part.value_len().unwrap(), stream_len as u64); | ||||||
|  |  | ||||||
|  |         // Make sure it delegates to the underlying body if length is not specified | ||||||
|  |         assert_eq!(body_part.value_len().unwrap(), bytes_len as u64); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     #[test] |     #[test] | ||||||
|     fn header_percent_encoding() { |     fn header_percent_encoding() { | ||||||
|         let name = "start%'\"\r\nßend"; |         let name = "start%'\"\r\nßend"; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user