use std::convert::Infallible;
use std::time::Duration;
use bytes::Bytes;
use http_body_util::{BodyExt, Empty, Full};
use hyper::body::Incoming;
use tokio::sync::OwnedSemaphorePermit;
use crate::{BoxError, ReqBody, ResponseBody};
pub(crate) const MAX_REQUEST_BODY_BUFFER: usize = 16 << 20;
pub(crate) const MAX_INFLIGHT_BODY_BUFFER_BYTES: usize = 1 << 30;
pub(crate) const INBOUND_BODY_READ_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) fn full(bytes: Vec<u8>) -> ResponseBody {
Full::new(Bytes::from(bytes))
.map_err(|e: Infallible| -> BoxError { match e {} })
.boxed_unsync()
}
pub(crate) fn stream(body: Incoming) -> ResponseBody {
body.map_err(|e| -> BoxError { Box::new(e) }).boxed_unsync()
}
pub(crate) fn box_incoming(body: Incoming) -> ReqBody {
body.map_err(|e| -> BoxError { Box::new(e) }).boxed()
}
pub(crate) fn empty_req() -> ReqBody {
Empty::<Bytes>::new()
.map_err(|e: Infallible| -> BoxError { match e {} })
.boxed()
}
pub(crate) enum BufferOutcome {
Buffered(Vec<u8>),
TooLarge,
ReadError,
}
pub(crate) async fn buffer_request_body(mut body: ReqBody, max: usize) -> BufferOutcome {
use hyper::body::Body;
let hint = usize::try_from(body.size_hint().lower()).unwrap_or(usize::MAX);
let mut buf = Vec::with_capacity(hint.min(max));
while let Some(frame) = body.frame().await {
let Ok(frame) = frame else {
return BufferOutcome::ReadError;
};
if let Ok(data) = frame.into_data() {
if buf.len() + data.len() > max {
return BufferOutcome::TooLarge;
}
buf.extend_from_slice(&data);
}
}
BufferOutcome::Buffered(buf)
}
pub(crate) fn req_full(bytes: Bytes) -> ReqBody {
Full::new(bytes)
.map_err(|e: Infallible| -> BoxError { match e {} })
.boxed()
}
pub(crate) const UPSTREAM_BODY_READ_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) enum ResponseBufferOutcome {
Buffered(Vec<u8>),
OverCap {
head: Vec<u8>,
rest: ResponseBody,
},
ReadError,
}
pub(crate) async fn buffer_response_body(
mut body: ResponseBody,
max: usize,
) -> ResponseBufferOutcome {
use hyper::body::Body;
let hint = usize::try_from(body.size_hint().lower()).unwrap_or(usize::MAX);
let mut buf = Vec::with_capacity(hint.min(max));
while let Some(frame) = body.frame().await {
let Ok(frame) = frame else {
return ResponseBufferOutcome::ReadError;
};
if let Ok(data) = frame.into_data() {
let room = max.saturating_sub(buf.len());
if data.len() > room {
let mut data = data;
let head_tail = data.split_to(room);
buf.extend_from_slice(&head_tail);
return ResponseBufferOutcome::OverCap {
head: buf,
rest: prefixed(data, body),
};
}
buf.extend_from_slice(&data);
}
}
ResponseBufferOutcome::Buffered(buf)
}
pub(crate) fn prefixed(prefix: Bytes, rest: ResponseBody) -> ResponseBody {
PrefixedBody {
prefix: Some(prefix),
rest,
}
.boxed_unsync()
}
pub(crate) fn hold_budget(body: ResponseBody, permit: OwnedSemaphorePermit) -> ResponseBody {
BudgetedBody {
inner: body,
_permit: permit,
}
.boxed_unsync()
}
struct BudgetedBody {
inner: ResponseBody,
_permit: OwnedSemaphorePermit,
}
impl hyper::body::Body for BudgetedBody {
type Data = Bytes;
type Error = BoxError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Bytes>, BoxError>>> {
std::pin::Pin::new(&mut self.get_mut().inner).poll_frame(cx)
}
fn size_hint(&self) -> hyper::body::SizeHint {
self.inner.size_hint()
}
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
struct PrefixedBody {
prefix: Option<Bytes>,
rest: ResponseBody,
}
impl hyper::body::Body for PrefixedBody {
type Data = Bytes;
type Error = BoxError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Bytes>, BoxError>>> {
let this = self.get_mut();
if let Some(prefix) = this.prefix.take() {
return std::task::Poll::Ready(Some(Ok(hyper::body::Frame::data(prefix))));
}
std::pin::Pin::new(&mut this.rest).poll_frame(cx)
}
fn size_hint(&self) -> hyper::body::SizeHint {
let mut hint = self.rest.size_hint();
if let Some(prefix) = &self.prefix {
let extra = prefix.len() as u64;
let upper = hint.upper().map(|u| u.saturating_add(extra));
hint = match upper {
Some(upper) => {
let mut h = hyper::body::SizeHint::new();
h.set_lower(hint.lower().saturating_add(extra));
h.set_upper(upper);
h
}
None => {
let mut h = hyper::body::SizeHint::new();
h.set_lower(hint.lower().saturating_add(extra));
h
}
};
}
hint
}
fn is_end_stream(&self) -> bool {
self.prefix.is_none() && self.rest.is_end_stream()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Frames(std::collections::VecDeque<Bytes>);
impl hyper::body::Body for Frames {
type Data = Bytes;
type Error = BoxError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Bytes>, BoxError>>> {
std::task::Poll::Ready(
self.get_mut()
.0
.pop_front()
.map(|b| Ok(hyper::body::Frame::data(b))),
)
}
}
fn framed(chunks: Vec<&'static [u8]>) -> ResponseBody {
Frames(chunks.into_iter().map(Bytes::from_static).collect()).boxed_unsync()
}
async fn collect(body: ResponseBody) -> Vec<u8> {
BodyExt::collect(body).await.unwrap().to_bytes().to_vec()
}
#[tokio::test]
async fn a_body_within_the_cap_is_buffered_whole() {
match buffer_response_body(framed(vec![b"hello ", b"world"]), 64).await {
ResponseBufferOutcome::Buffered(body) => assert_eq!(body, b"hello world".to_vec()),
_ => panic!("expected a whole buffer"),
}
}
#[tokio::test]
async fn an_over_cap_body_splits_at_the_cap_and_keeps_the_remainder_streamable() {
match buffer_response_body(framed(vec![b"0123456789", b"abcdefghij"]), 12).await {
ResponseBufferOutcome::OverCap { head, rest } => {
assert_eq!(head, b"0123456789ab".to_vec());
assert_eq!(collect(rest).await, b"cdefghij".to_vec());
}
_ => panic!("expected an over-cap split"),
}
}
#[tokio::test]
async fn re_attached_bytes_lead_the_stream_they_came_from() {
let body = prefixed(Bytes::from_static(b"head"), framed(vec![b"-tail"]));
assert_eq!(collect(body).await, b"head-tail".to_vec());
}
#[tokio::test]
async fn the_byte_budget_admits_concurrent_buffers_up_to_its_total_and_no_further() {
let budget = std::sync::Arc::new(tokio::sync::Semaphore::new(1024));
let first = budget.clone().acquire_many_owned(768).await.unwrap();
let second = budget.clone().acquire_many_owned(256).await.unwrap();
assert!(
budget.clone().try_acquire_many_owned(1).is_err(),
"the budget is spent"
);
drop(second);
assert!(
budget.clone().try_acquire_many_owned(256).is_ok(),
"releasing one buffer's bytes frees exactly those bytes for the other direction"
);
drop(first);
}
#[tokio::test]
async fn a_held_budget_outlives_the_buffering_call_and_frees_with_the_bytes() {
let budget = std::sync::Arc::new(tokio::sync::Semaphore::new(16));
let permit = budget.clone().acquire_many_owned(16).await.unwrap();
let body = hold_budget(framed(vec![b"bytes"]), permit);
assert!(budget.clone().try_acquire_many_owned(1).is_err());
assert_eq!(collect(body).await, b"bytes".to_vec());
assert!(
budget.try_acquire_many_owned(16).is_ok(),
"dropping the body releases its reservation"
);
}
}