use Body inside multipart Parts

This commit is contained in:
Sean McArthur
2017-08-21 14:18:52 -07:00
parent 93c8321305
commit dcf70f3f4e
2 changed files with 108 additions and 52 deletions

View File

@@ -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)
}

View File

@@ -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",