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::pin::Pin;
|
||||
|
||||
use bytes::{Bytes};
|
||||
use bytes::Bytes;
|
||||
use http::HeaderMap;
|
||||
use mime_guess::Mime;
|
||||
use percent_encoding::{self, AsciiSet, NON_ALPHANUMERIC};
|
||||
@@ -22,6 +22,7 @@ pub struct Form {
|
||||
pub struct Part {
|
||||
meta: PartMetadata,
|
||||
value: Body,
|
||||
body_length: Option<u64>,
|
||||
}
|
||||
|
||||
pub(crate) struct FormParts<P> {
|
||||
@@ -190,7 +191,7 @@ impl Part {
|
||||
Cow::Borrowed(slice) => Body::from(slice),
|
||||
Cow::Owned(string) => Body::from(string),
|
||||
};
|
||||
Part::new(body)
|
||||
Part::new(body, None)
|
||||
}
|
||||
|
||||
/// Makes a new parameter from arbitrary bytes.
|
||||
@@ -202,18 +203,26 @@ impl Part {
|
||||
Cow::Borrowed(slice) => Body::from(slice),
|
||||
Cow::Owned(vec) => Body::from(vec),
|
||||
};
|
||||
Part::new(body)
|
||||
Part::new(body, None)
|
||||
}
|
||||
|
||||
/// Makes a new parameter from an arbitrary stream.
|
||||
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 {
|
||||
meta: PartMetadata::new(),
|
||||
value,
|
||||
body_length,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +250,7 @@ impl Part {
|
||||
{
|
||||
Part {
|
||||
meta: func(self.meta),
|
||||
value: self.value,
|
||||
..self
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -257,7 +266,11 @@ impl fmt::Debug for Part {
|
||||
|
||||
impl PartProps for Part {
|
||||
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 {
|
||||
@@ -508,7 +521,11 @@ mod tests {
|
||||
fn form_empty() {
|
||||
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 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::<
|
||||
Result<String, crate::Error>,
|
||||
>(Ok(
|
||||
"part1".to_owned(),
|
||||
"part1".to_owned()
|
||||
))))),
|
||||
)
|
||||
.part("key1", Part::text("value1"))
|
||||
@@ -534,13 +551,12 @@ mod tests {
|
||||
Part::stream(Body::stream(stream::once(future::ready::<
|
||||
Result<String, crate::Error>,
|
||||
>(Ok(
|
||||
"part2".to_owned(),
|
||||
"part2".to_owned()
|
||||
))))),
|
||||
)
|
||||
.part("key3", Part::text("value3").file_name("filename"));
|
||||
form.inner.boundary = "boundary".to_string();
|
||||
let expected =
|
||||
"--boundary\r\n\
|
||||
let expected = "--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"reader1\"\r\n\r\n\
|
||||
part1\r\n\
|
||||
--boundary\r\n\
|
||||
@@ -556,7 +572,11 @@ mod tests {
|
||||
--boundary\r\n\
|
||||
Content-Disposition: form-data; name=\"key3\"; filename=\"filename\"\r\n\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 s = body.map(|try_c| try_c.map(|r| r.to_vec())).try_concat();
|
||||
|
||||
@@ -583,7 +603,11 @@ mod tests {
|
||||
\r\n\
|
||||
value2\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 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);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn header_percent_encoding() {
|
||||
let name = "start%'\"\r\nßend";
|
||||
|
||||
Reference in New Issue
Block a user