feat(body): add body::aggregate and body::to_bytes functions
Adds utility functions to `hyper::body` to help asynchronously collecting all the buffers of some `HttpBody` into one. - `aggregate` will collect all into an `impl Buf` without copying the contents. This is ideal if you don't need a contiguous buffer. - `to_bytes` will copy all the data into a single contiguous `Bytes` buffer.
This commit is contained in:
25
src/body/aggregate.rs
Normal file
25
src/body/aggregate.rs
Normal file
@@ -0,0 +1,25 @@
|
||||
use bytes::Buf;
|
||||
|
||||
use super::HttpBody;
|
||||
use crate::common::buf::BufList;
|
||||
|
||||
/// Aggregate the data buffers from a body asynchronously.
|
||||
///
|
||||
/// The returned `impl Buf` groups the `Buf`s from the `HttpBody` without
|
||||
/// copying them. This is ideal if you don't require a contiguous buffer.
|
||||
pub async fn aggregate<T>(body: T) -> Result<impl Buf, T::Error>
|
||||
where
|
||||
T: HttpBody,
|
||||
{
|
||||
let mut bufs = BufList::new();
|
||||
|
||||
futures_util::pin_mut!(body);
|
||||
while let Some(buf) = body.data().await {
|
||||
let buf = buf?;
|
||||
if buf.has_remaining() {
|
||||
bufs.push(buf);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bufs)
|
||||
}
|
||||
@@ -18,11 +18,16 @@
|
||||
pub use bytes::{Buf, Bytes};
|
||||
pub use http_body::Body as HttpBody;
|
||||
|
||||
pub use self::aggregate::aggregate;
|
||||
pub use self::body::{Body, Sender};
|
||||
pub use self::to_bytes::to_bytes;
|
||||
|
||||
pub(crate) use self::payload::Payload;
|
||||
|
||||
mod aggregate;
|
||||
mod body;
|
||||
mod payload;
|
||||
mod to_bytes;
|
||||
|
||||
/// An optimization to try to take a full body if immediately available.
|
||||
///
|
||||
|
||||
36
src/body/to_bytes.rs
Normal file
36
src/body/to_bytes.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use bytes::{Buf, BufMut, Bytes};
|
||||
|
||||
use super::HttpBody;
|
||||
|
||||
/// dox
|
||||
pub async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
|
||||
where
|
||||
T: HttpBody,
|
||||
{
|
||||
futures_util::pin_mut!(body);
|
||||
|
||||
// If there's only 1 chunk, we can just return Buf::to_bytes()
|
||||
let mut first = if let Some(buf) = body.data().await {
|
||||
buf?
|
||||
} else {
|
||||
return Ok(Bytes::new());
|
||||
};
|
||||
|
||||
let second = if let Some(buf) = body.data().await {
|
||||
buf?
|
||||
} else {
|
||||
return Ok(first.to_bytes());
|
||||
};
|
||||
|
||||
// With more than 1 buf, we gotta flatten into a Vec first.
|
||||
let cap = first.remaining() + second.remaining() + body.size_hint().lower() as usize;
|
||||
let mut vec = Vec::with_capacity(cap);
|
||||
vec.put(first);
|
||||
vec.put(second);
|
||||
|
||||
while let Some(buf) = body.data().await {
|
||||
vec.put(buf?);
|
||||
}
|
||||
|
||||
Ok(vec.into())
|
||||
}
|
||||
75
src/common/buf.rs
Normal file
75
src/common/buf.rs
Normal file
@@ -0,0 +1,75 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::io::IoSlice;
|
||||
|
||||
use bytes::Buf;
|
||||
|
||||
pub(crate) struct BufList<T> {
|
||||
bufs: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T: Buf> BufList<T> {
|
||||
pub(crate) fn new() -> BufList<T> {
|
||||
BufList {
|
||||
bufs: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn push(&mut self, buf: T) {
|
||||
debug_assert!(buf.has_remaining());
|
||||
self.bufs.push_back(buf);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn bufs_cnt(&self) -> usize {
|
||||
self.bufs.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Buf> Buf for BufList<T> {
|
||||
#[inline]
|
||||
fn remaining(&self) -> usize {
|
||||
self.bufs.iter().map(|buf| buf.remaining()).sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes(&self) -> &[u8] {
|
||||
for buf in &self.bufs {
|
||||
return buf.bytes();
|
||||
}
|
||||
&[]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn advance(&mut self, mut cnt: usize) {
|
||||
while cnt > 0 {
|
||||
{
|
||||
let front = &mut self.bufs[0];
|
||||
let rem = front.remaining();
|
||||
if rem > cnt {
|
||||
front.advance(cnt);
|
||||
return;
|
||||
} else {
|
||||
front.advance(rem);
|
||||
cnt -= rem;
|
||||
}
|
||||
}
|
||||
self.bufs.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize {
|
||||
if dst.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let mut vecs = 0;
|
||||
for buf in &self.bufs {
|
||||
vecs += buf.bytes_vectored(&mut dst[vecs..]);
|
||||
if vecs == dst.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vecs
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ macro_rules! ready {
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) mod buf;
|
||||
pub(crate) mod drain;
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod io;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
use std::io::{self, IoSlice};
|
||||
|
||||
@@ -8,6 +7,7 @@ use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::{Http1Transaction, ParseContext, ParsedMessage};
|
||||
use crate::common::buf::BufList;
|
||||
use crate::common::{task, Pin, Poll, Unpin};
|
||||
|
||||
/// The initial buffer size allocated before trying to read from IO.
|
||||
@@ -90,7 +90,7 @@ where
|
||||
pub fn set_write_strategy_flatten(&mut self) {
|
||||
// this should always be called only at construction time,
|
||||
// so this assert is here to catch myself
|
||||
debug_assert!(self.write_buf.queue.bufs.is_empty());
|
||||
debug_assert!(self.write_buf.queue.bufs_cnt() == 0);
|
||||
self.write_buf.set_strategy(WriteStrategy::Flatten);
|
||||
}
|
||||
|
||||
@@ -431,16 +431,16 @@ pub(super) struct WriteBuf<B> {
|
||||
headers: Cursor<Vec<u8>>,
|
||||
max_buf_size: usize,
|
||||
/// Deque of user buffers if strategy is Queue
|
||||
queue: BufDeque<B>,
|
||||
queue: BufList<B>,
|
||||
strategy: WriteStrategy,
|
||||
}
|
||||
|
||||
impl<B> WriteBuf<B> {
|
||||
impl<B: Buf> WriteBuf<B> {
|
||||
fn new() -> WriteBuf<B> {
|
||||
WriteBuf {
|
||||
headers: Cursor::new(Vec::with_capacity(INIT_BUFFER_SIZE)),
|
||||
max_buf_size: DEFAULT_MAX_BUFFER_SIZE,
|
||||
queue: BufDeque::new(),
|
||||
queue: BufList::new(),
|
||||
strategy: WriteStrategy::Auto,
|
||||
}
|
||||
}
|
||||
@@ -479,7 +479,7 @@ where
|
||||
}
|
||||
}
|
||||
WriteStrategy::Auto | WriteStrategy::Queue => {
|
||||
self.queue.bufs.push_back(buf.into());
|
||||
self.queue.push(buf.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -488,7 +488,7 @@ where
|
||||
match self.strategy {
|
||||
WriteStrategy::Flatten => self.remaining() < self.max_buf_size,
|
||||
WriteStrategy::Auto | WriteStrategy::Queue => {
|
||||
self.queue.bufs.len() < MAX_BUF_LIST_BUFFERS && self.remaining() < self.max_buf_size
|
||||
self.queue.bufs_cnt() < MAX_BUF_LIST_BUFFERS && self.remaining() < self.max_buf_size
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -608,66 +608,6 @@ enum WriteStrategy {
|
||||
Queue,
|
||||
}
|
||||
|
||||
struct BufDeque<T> {
|
||||
bufs: VecDeque<T>,
|
||||
}
|
||||
|
||||
impl<T> BufDeque<T> {
|
||||
fn new() -> BufDeque<T> {
|
||||
BufDeque {
|
||||
bufs: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Buf> Buf for BufDeque<T> {
|
||||
#[inline]
|
||||
fn remaining(&self) -> usize {
|
||||
self.bufs.iter().map(|buf| buf.remaining()).sum()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes(&self) -> &[u8] {
|
||||
for buf in &self.bufs {
|
||||
return buf.bytes();
|
||||
}
|
||||
&[]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn advance(&mut self, mut cnt: usize) {
|
||||
while cnt > 0 {
|
||||
{
|
||||
let front = &mut self.bufs[0];
|
||||
let rem = front.remaining();
|
||||
if rem > cnt {
|
||||
front.advance(cnt);
|
||||
return;
|
||||
} else {
|
||||
front.advance(rem);
|
||||
cnt -= rem;
|
||||
}
|
||||
}
|
||||
self.bufs.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn bytes_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize {
|
||||
if dst.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
let mut vecs = 0;
|
||||
for buf in &self.bufs {
|
||||
vecs += buf.bytes_vectored(&mut dst[vecs..]);
|
||||
if vecs == dst.len() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
vecs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -871,12 +811,12 @@ mod tests {
|
||||
buffered.buffer(Cursor::new(b"world, ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"it's ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"hyper!".to_vec()));
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 3);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 3);
|
||||
buffered.flush().unwrap();
|
||||
|
||||
assert_eq!(buffered.io, b"hello world, it's hyper!");
|
||||
assert_eq!(buffered.io.num_writes(), 1);
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 0);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 0);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -896,7 +836,7 @@ mod tests {
|
||||
buffered.buffer(Cursor::new(b"world, ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"it's ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"hyper!".to_vec()));
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 0);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 0);
|
||||
|
||||
buffered.flush().await.expect("flush");
|
||||
}
|
||||
@@ -921,11 +861,11 @@ mod tests {
|
||||
buffered.buffer(Cursor::new(b"world, ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"it's ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"hyper!".to_vec()));
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 3);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 3);
|
||||
|
||||
buffered.flush().await.expect("flush");
|
||||
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 0);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -949,11 +889,11 @@ mod tests {
|
||||
buffered.buffer(Cursor::new(b"world, ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"it's ".to_vec()));
|
||||
buffered.buffer(Cursor::new(b"hyper!".to_vec()));
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 3);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 3);
|
||||
|
||||
buffered.flush().await.expect("flush");
|
||||
|
||||
assert_eq!(buffered.write_buf.queue.bufs.len(), 0);
|
||||
assert_eq!(buffered.write_buf.queue.bufs_cnt(), 0);
|
||||
}
|
||||
|
||||
#[cfg(feature = "nightly")]
|
||||
|
||||
Reference in New Issue
Block a user