From dcf70f3f4e47f659fb643efe18536f22dae09cea Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Mon, 21 Aug 2017 14:18:52 -0700 Subject: [PATCH] use Body inside multipart Parts --- src/body.rs | 83 +++++++++++++++++++++++++++++++++++------------ src/multipart_.rs | 77 +++++++++++++++++++++++++------------------ 2 files changed, 108 insertions(+), 52 deletions(-) diff --git a/src/body.rs b/src/body.rs index 1b55f49..c673f62 100644 --- a/src/body.rs +++ b/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(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(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 { - 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, Option), @@ -99,7 +89,7 @@ impl From> for Body { #[inline] fn from(v: Vec) -> 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 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 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); + +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 { + match body.kind { + Kind::Reader(_, len) => len, + Kind::Bytes(ref bytes) => Some(bytes.len() as u64), + } +} + +pub enum Reader { + Reader(Box), + Bytes(Cursor), +} + +#[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 { + match *self { + Reader::Reader(ref mut rdr) => rdr.read(buf), + Reader::Bytes(ref mut rdr) => rdr.read(buf), + } + } +} + pub struct Sender { body: (Box, Option), tx: wait::WaitSink<::futures::sync::mpsc::Sender>>, @@ -193,7 +225,7 @@ impl Sender { #[inline] pub fn async(body: Body) -> (Option, async_impl::Body, Option) { - 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, async_impl::Body, Option) { } } } + +// useful for tests, but not publicly exposed +#[cfg(test)] +pub fn read_to_string(mut body: Body) -> io::Result { + 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) +} diff --git a/src/multipart_.rs b/src/multipart_.rs index d49bd84..9ec7711 100644 --- a/src/multipart_.rs +++ b/src/multipart_.rs @@ -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(self, name: T, value: U) -> Form where T: Into>, - U: AsRef<[u8]> + Send + 'static + U: Into>, { self.part(name, Part::text(value)) } @@ -84,7 +91,7 @@ impl Form { fn compute_length(&mut self) -> Option { 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, - value_length: Option, + value: Body, mime: Option, file_name: Option>, } impl Part { /// Makes a text parameter. - pub fn text + 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(value: T) -> Part + where T: Into>, + { + 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(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(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) -> 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",