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
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,7 +266,11 @@ 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> {
|
||||||
self.value.content_length()
|
if self.body_length.is_some() {
|
||||||
|
self.body_length
|
||||||
|
} else {
|
||||||
|
self.value.content_length()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn metadata(&self) -> &PartMetadata {
|
fn metadata(&self) -> &PartMetadata {
|
||||||
@@ -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