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