use crate::chunked::{ChunkEvent, ChunkedDecoder, ChunkedError};
use crate::header::{HeaderId, HeaderVec};
use crate::{BodyKind, Head};
use bytes::Bytes;
use std::cell::{Cell, RefCell};
use std::future::Future;
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite};
pub trait Transport: AsyncRead + AsyncWrite + Unpin {}
impl<T: AsyncRead + AsyncWrite + Unpin> Transport for T {}
pub struct Upgraded {
pub peer: Option<SocketAddr>,
pub io: Box<dyn Transport>,
pub buffered: Bytes,
}
impl std::fmt::Debug for Upgraded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Upgraded")
.field("peer", &self.peer)
.field("buffered", &self.buffered.len())
.finish_non_exhaustive()
}
}
#[derive(Debug, thiserror::Error)]
pub enum BodyError {
#[error("chunked body error: {0}")]
Chunked(#[from] ChunkedError),
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("connection closed before the declared body arrived")]
Incomplete,
#[error("body exceeded the requested cap")]
TooLarge,
}
impl BodyError {
pub fn status(&self) -> u16 {
match self {
BodyError::Chunked(e) => e.status(),
BodyError::TooLarge => 413,
BodyError::Incomplete => 400,
BodyError::Io(_) => 400,
}
}
}
pub trait BodyIo {
fn poll_fill(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Bytes>>;
fn poll_send_continue(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<()>>;
fn take_buffered(&mut self, max: usize) -> Bytes;
fn push_back(&mut self, bytes: Bytes);
}
struct BodyState {
kind: BodyKind,
buffered: Bytes,
remaining: u64,
decoder: Option<ChunkedDecoder>,
trailers: Option<HeaderVec>,
io: Option<Rc<RefCell<dyn BodyIo>>>,
continue_sent: bool,
needs_continue: bool,
done: bool,
fully_read: Rc<Cell<bool>>,
#[cfg(feature = "hyper-backend")]
raw: bool,
#[cfg(feature = "hyper-backend")]
consumed: u64,
#[cfg(feature = "hyper-backend")]
cap: u64,
#[cfg(feature = "hyper-backend")]
trailers_slot: Option<Rc<RefCell<Option<HeaderVec>>>>,
}
impl BodyState {
#[inline]
fn finish(&mut self) {
self.done = true;
self.fully_read.set(true);
}
#[inline]
fn fail(&mut self) {
self.done = true;
self.fully_read.set(false);
}
}
pub struct Body {
state: BodyState,
}
impl std::fmt::Debug for Body {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Body")
.field("kind", &self.state.kind)
.field("done", &self.state.done)
.finish_non_exhaustive()
}
}
impl Body {
pub fn empty() -> Self {
Self {
state: BodyState {
kind: BodyKind::None,
buffered: Bytes::new(),
remaining: 0,
decoder: None,
trailers: Some(HeaderVec::new()),
io: None,
continue_sent: true,
needs_continue: false,
done: true,
fully_read: Rc::new(Cell::new(true)),
#[cfg(feature = "hyper-backend")]
raw: false,
#[cfg(feature = "hyper-backend")]
consumed: 0,
#[cfg(feature = "hyper-backend")]
cap: u64::MAX,
#[cfg(feature = "hyper-backend")]
trailers_slot: None,
},
}
}
pub fn new(
kind: BodyKind,
io: Rc<RefCell<dyn BodyIo>>,
needs_continue: bool,
limits: &crate::Limits,
fully_read: Rc<Cell<bool>>,
) -> Self {
let (remaining, decoder, done) = match kind {
BodyKind::None => (0, None, true),
BodyKind::Length(n) => (n, None, n == 0),
BodyKind::Chunked => (0, Some(ChunkedDecoder::new(limits)), false),
};
fully_read.set(done);
Self {
state: BodyState {
kind,
buffered: Bytes::new(),
remaining,
decoder,
trailers: if done { Some(HeaderVec::new()) } else { None },
io: Some(io),
continue_sent: !needs_continue,
needs_continue,
done,
fully_read,
#[cfg(feature = "hyper-backend")]
raw: false,
#[cfg(feature = "hyper-backend")]
consumed: 0,
#[cfg(feature = "hyper-backend")]
cap: u64::MAX,
#[cfg(feature = "hyper-backend")]
trailers_slot: None,
},
}
}
#[cfg(feature = "hyper-backend")]
pub(crate) fn from_backend(
kind: BodyKind,
io: Rc<RefCell<dyn BodyIo>>,
needs_continue: bool,
max_body_bytes: u64,
fully_read: Rc<Cell<bool>>,
trailers_slot: Rc<RefCell<Option<HeaderVec>>>,
) -> Self {
let done = matches!(kind, BodyKind::None | BodyKind::Length(0));
fully_read.set(done);
Self {
state: BodyState {
kind,
buffered: Bytes::new(),
remaining: 0,
decoder: None,
trailers: if done { Some(HeaderVec::new()) } else { None },
io: Some(io),
continue_sent: !needs_continue,
needs_continue,
done,
fully_read,
raw: true,
consumed: 0,
cap: max_body_bytes,
trailers_slot: Some(trailers_slot),
},
}
}
pub fn from_bytes(data: Bytes) -> Self {
let len = data.len() as u64;
Self {
state: BodyState {
kind: BodyKind::Length(len),
buffered: data,
remaining: len,
decoder: None,
trailers: Some(HeaderVec::new()),
io: None,
continue_sent: true,
needs_continue: false,
done: len == 0,
fully_read: Rc::new(Cell::new(true)),
#[cfg(feature = "hyper-backend")]
raw: false,
#[cfg(feature = "hyper-backend")]
consumed: 0,
#[cfg(feature = "hyper-backend")]
cap: u64::MAX,
#[cfg(feature = "hyper-backend")]
trailers_slot: None,
},
}
}
#[inline]
pub fn kind(&self) -> BodyKind {
self.state.kind
}
#[inline]
pub fn is_end(&self) -> bool {
self.state.done
}
#[inline]
pub fn continue_pending(&self) -> bool {
self.state.needs_continue && !self.state.continue_sent
}
#[inline]
pub fn was_fully_read(&self) -> bool {
self.state.fully_read.get()
}
#[inline]
pub fn trailers(&self) -> Option<&HeaderVec> {
self.state.trailers.as_ref()
}
pub fn poll_chunk(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, BodyError>>> {
let s = &mut self.state;
if s.done {
return Poll::Ready(None);
}
if !s.continue_sent {
let sent = match &s.io {
Some(io) => io.borrow_mut().poll_send_continue(cx),
None => Poll::Ready(Ok(())),
};
match sent {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => {
s.fail();
return Poll::Ready(Some(Err(BodyError::Io(e))));
}
Poll::Ready(Ok(())) => {}
}
s.continue_sent = true;
}
#[cfg(feature = "hyper-backend")]
if s.raw {
return Self::poll_raw(s, cx);
}
loop {
match s.kind {
BodyKind::None => {
s.finish();
return Poll::Ready(None);
}
BodyKind::Length(_) => {
if s.buffered.is_empty() && s.remaining > 0 {
let want = usize::try_from(s.remaining).unwrap_or(usize::MAX);
if let Some(io) = &s.io {
let got = io.borrow_mut().take_buffered(want);
if !got.is_empty() {
s.buffered = got;
}
}
}
if !s.buffered.is_empty() {
let take = std::cmp::min(s.buffered.len() as u64, s.remaining) as usize;
let chunk = s.buffered.split_to(take);
s.remaining -= take as u64;
if s.remaining == 0 {
s.finish();
s.trailers = Some(HeaderVec::new());
if !s.buffered.is_empty()
&& let Some(io) = &s.io
{
let leftover = std::mem::take(&mut s.buffered);
io.borrow_mut().push_back(leftover);
}
}
return Poll::Ready(Some(Ok(chunk)));
}
if s.remaining == 0 {
s.finish();
s.trailers = Some(HeaderVec::new());
return Poll::Ready(None);
}
match Self::fill(s, cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => {
s.fail();
return Poll::Ready(Some(Err(e)));
}
Poll::Ready(Ok(false)) => {
s.fail();
return Poll::Ready(Some(Err(BodyError::Incomplete)));
}
Poll::Ready(Ok(true)) => continue,
}
}
BodyKind::Chunked => {
if s.buffered.is_empty()
&& let Some(io) = &s.io
{
let got = io.borrow_mut().take_buffered(usize::MAX);
if !got.is_empty() {
s.buffered = got;
}
}
let decoder = s.decoder.as_mut().expect("chunked body has a decoder");
match decoder.poll(&mut s.buffered) {
Err(e) => {
s.fail();
return Poll::Ready(Some(Err(BodyError::Chunked(e))));
}
Ok(Some(ChunkEvent::Data(d))) => return Poll::Ready(Some(Ok(d))),
Ok(Some(ChunkEvent::Trailers(t))) => {
s.trailers = Some(*t);
continue;
}
Ok(Some(ChunkEvent::End)) => {
s.finish();
if s.trailers.is_none() {
s.trailers = Some(HeaderVec::new());
}
if !s.buffered.is_empty() {
let leftover = std::mem::take(&mut s.buffered);
if let Some(io) = &s.io {
io.borrow_mut().push_back(leftover);
}
}
return Poll::Ready(None);
}
Ok(None) => match Self::fill(s, cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => {
s.fail();
return Poll::Ready(Some(Err(e)));
}
Poll::Ready(Ok(false)) => {
s.fail();
return Poll::Ready(Some(Err(BodyError::Incomplete)));
}
Poll::Ready(Ok(true)) => continue,
},
}
}
}
}
}
#[cfg(feature = "hyper-backend")]
fn poll_raw(s: &mut BodyState, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, BodyError>>> {
loop {
if s.buffered.is_empty()
&& let Some(io) = &s.io
{
let got = io.borrow_mut().take_buffered(usize::MAX);
if !got.is_empty() {
s.buffered = got;
}
}
if !s.buffered.is_empty() {
let chunk = std::mem::take(&mut s.buffered);
s.consumed += chunk.len() as u64;
if s.consumed > s.cap {
s.fail();
return Poll::Ready(Some(Err(BodyError::TooLarge)));
}
return Poll::Ready(Some(Ok(chunk)));
}
match Self::fill(s, cx) {
Poll::Pending => return Poll::Pending,
Poll::Ready(Err(e)) => {
s.fail();
return Poll::Ready(Some(Err(e)));
}
Poll::Ready(Ok(false)) => {
s.finish();
s.trailers = Some(
s.trailers_slot
.as_ref()
.and_then(|slot| slot.borrow_mut().take())
.unwrap_or_default(),
);
return Poll::Ready(None);
}
Poll::Ready(Ok(true)) => continue,
}
}
}
fn fill(s: &mut BodyState, cx: &mut Context<'_>) -> Poll<Result<bool, BodyError>> {
let Some(io) = &s.io else {
return Poll::Ready(Ok(false));
};
match io.borrow_mut().poll_fill(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => Poll::Ready(Err(BodyError::Io(e))),
Poll::Ready(Ok(more)) => {
if more.is_empty() {
return Poll::Ready(Ok(false));
}
if s.buffered.is_empty() {
s.buffered = more;
} else {
let mut joined = Vec::with_capacity(s.buffered.len() + more.len());
joined.extend_from_slice(&s.buffered);
joined.extend_from_slice(&more);
s.buffered = Bytes::from(joined);
}
Poll::Ready(Ok(true))
}
}
}
pub async fn chunk(&mut self) -> Option<Result<Bytes, BodyError>> {
std::future::poll_fn(|cx| self.poll_chunk(cx)).await
}
pub async fn collect(&mut self, cap: u64) -> Result<Bytes, BodyError> {
let mut acc: Option<Vec<u8>> = None;
let mut first: Option<Bytes> = None;
let mut total: u64 = 0;
while let Some(chunk) = self.chunk().await {
let chunk = chunk?;
total += chunk.len() as u64;
if total > cap {
return Err(BodyError::TooLarge);
}
match (&mut acc, &first) {
(None, None) => first = Some(chunk),
(None, Some(_)) => {
let f = first.take().expect("checked");
let mut v = Vec::with_capacity(f.len() + chunk.len());
v.extend_from_slice(&f);
v.extend_from_slice(&chunk);
acc = Some(v);
}
(Some(v), _) => v.extend_from_slice(&chunk),
}
}
Ok(match acc {
Some(v) => Bytes::from(v),
None => first.unwrap_or_default(),
})
}
}
pub struct Request {
pub head: Head,
pub body: Body,
pub peer: Option<SocketAddr>,
}
impl std::fmt::Debug for Request {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Request")
.field("method", &self.head.method)
.field("target", self.head.target())
.field("peer", &self.peer)
.finish_non_exhaustive()
}
}
pub enum ResponseBody {
Empty,
Full(Bytes),
Stream(Pin<Box<dyn futures_stream::Stream>>),
}
pub mod futures_stream {
use super::{BodyError, Bytes, Context, Poll};
pub trait Stream {
fn poll_next(
self: std::pin::Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, BodyError>>>;
}
}
impl std::fmt::Debug for ResponseBody {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ResponseBody::Empty => f.write_str("Empty"),
ResponseBody::Full(b) => write!(f, "Full({} bytes)", b.len()),
ResponseBody::Stream(_) => f.write_str("Stream"),
}
}
}
pub struct Response {
pub status: u16,
pub headers: HeaderVec,
pub body: ResponseBody,
}
impl std::fmt::Debug for Response {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Response")
.field("status", &self.status)
.field("body", &self.body)
.finish()
}
}
impl Response {
pub fn new(status: u16) -> Self {
Self {
status,
headers: HeaderVec::new(),
body: ResponseBody::Empty,
}
}
pub fn ok() -> Self {
Self::new(200)
}
pub fn status_only(status: u16) -> Self {
Self::new(status)
}
pub fn text(body: &'static str) -> Self {
let mut r = Self::new(200);
r.headers.push((
HeaderId::ContentType,
Bytes::from_static(b"text/plain; charset=utf-8"),
));
r.body = ResponseBody::Full(Bytes::from_static(body.as_bytes()));
r
}
pub fn json(body: Bytes) -> Self {
let mut r = Self::new(200);
r.headers.push((
HeaderId::ContentType,
Bytes::from_static(b"application/json"),
));
r.body = ResponseBody::Full(body);
r
}
pub fn header(mut self, id: HeaderId, value: Bytes) -> Self {
self.headers.push((id, value));
self
}
pub fn with_body(mut self, body: ResponseBody) -> Self {
self.body = body;
self
}
}
pub trait H1Service {
type Future: Future<Output = Response>;
fn call(&self, req: Request) -> Self::Future;
}
impl<F, Fut> H1Service for F
where
F: Fn(Request) -> Fut,
Fut: Future<Output = Response>,
{
type Future = Fut;
#[inline]
fn call(&self, req: Request) -> Fut {
(self)(req)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Limits;
struct MockIo {
reads: Vec<Bytes>,
buffered: Bytes,
continues: usize,
}
impl MockIo {
fn with_buffered(buffered: &'static [u8], reads: Vec<&'static [u8]>) -> Rc<RefCell<Self>> {
Rc::new(RefCell::new(Self {
reads: reads.into_iter().rev().map(Bytes::from_static).collect(),
buffered: Bytes::from_static(buffered),
continues: 0,
}))
}
}
impl BodyIo for MockIo {
fn poll_fill(&mut self, _cx: &mut Context<'_>) -> Poll<io::Result<Bytes>> {
Poll::Ready(Ok(self.reads.pop().unwrap_or_default()))
}
fn poll_send_continue(&mut self, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.continues += 1;
Poll::Ready(Ok(()))
}
fn take_buffered(&mut self, max: usize) -> Bytes {
let n = self.buffered.len().min(max);
self.buffered.split_to(n)
}
fn push_back(&mut self, bytes: Bytes) {
let mut joined = Vec::with_capacity(bytes.len() + self.buffered.len());
joined.extend_from_slice(&bytes);
joined.extend_from_slice(&self.buffered);
self.buffered = Bytes::from(joined);
}
}
fn body(kind: BodyKind, buffered: &'static [u8], reads: Vec<&'static [u8]>) -> Body {
Body::new(
kind,
MockIo::with_buffered(buffered, reads),
false,
&Limits::default(),
Rc::new(Cell::new(false)),
)
}
#[tokio::test]
async fn empty_body_ends_immediately() {
let mut b = Body::empty();
assert!(b.is_end());
assert!(b.chunk().await.is_none());
assert_eq!(b.collect(1024).await.unwrap().len(), 0);
}
#[tokio::test]
async fn fixed_body_yields_declared_bytes() {
let mut b = body(BodyKind::Length(5), b"hello", vec![]);
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello");
assert!(b.is_end());
}
#[tokio::test]
async fn fixed_body_reads_across_fills() {
let mut b = body(BodyKind::Length(10), b"hel", vec![b"lo wor", b"ld"]);
assert_eq!(&b.collect(1024).await.unwrap()[..], &b"hello world"[..10]);
}
#[tokio::test]
async fn fixed_body_returns_over_read_bytes_to_the_connection() {
const NEXT: &[u8] = b"GET /admin HTTP/1.1\r\nHost: a\r\n\r\n";
let io = MockIo::with_buffered(b"hel", vec![b"loGET /admin HTTP/1.1\r\nHost: a\r\n\r\n"]);
let mut b = Body::new(
BodyKind::Length(5),
io.clone(),
false,
&Limits::default(),
Rc::new(Cell::new(false)),
);
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello");
assert!(b.is_end());
assert_eq!(
&io.borrow().buffered[..],
NEXT,
"the next request must be readable by the connection, not dropped"
);
}
#[tokio::test]
async fn chunked_body_yields_chunks_and_trailers() {
let mut b = body(
BodyKind::Chunked,
b"5\r\nhello\r\n0\r\nEtag: x\r\n\r\n",
vec![],
);
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello");
let t = b.trailers().expect("trailers after end");
assert_eq!(crate::header::get_str(t, &HeaderId::Etag), Some("x"));
}
#[tokio::test]
async fn chunked_body_reads_across_fills() {
let mut b = body(BodyKind::Chunked, b"5\r\nhel", vec![b"lo\r\n0\r\n\r\n"]);
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello");
}
#[tokio::test]
async fn chunked_error_surfaces() {
let mut b = body(BodyKind::Chunked, b"zz\r\n", vec![]);
let err = b.collect(1024).await.unwrap_err();
assert!(matches!(err, BodyError::Chunked(_)), "got {err:?}");
assert_eq!(err.status(), 400);
}
#[tokio::test]
async fn collect_enforces_its_cap() {
let mut b = body(BodyKind::Length(5), b"hello", vec![]);
let err = b.collect(4).await.unwrap_err();
assert!(matches!(err, BodyError::TooLarge));
assert_eq!(err.status(), 413);
}
#[tokio::test]
async fn collect_ignores_a_lying_content_length() {
let mut b = body(BodyKind::Length(5), b"hello world, much longer", vec![]);
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello");
}
#[tokio::test]
async fn continue_is_sent_lazily_on_first_read() {
let io = MockIo::with_buffered(b"hello", vec![]);
let mut b = Body::new(
BodyKind::Length(5),
io.clone(),
true,
&Limits::default(),
Rc::new(Cell::new(false)),
);
assert!(b.continue_pending());
assert_eq!(io.borrow().continues, 0, "nothing sent before a read");
b.collect(1024).await.unwrap();
assert_eq!(io.borrow().continues, 1, "sent exactly once");
assert!(!b.continue_pending());
}
#[tokio::test]
async fn continue_is_not_sent_when_body_is_ignored() {
let io = MockIo::with_buffered(b"hello", vec![]);
let b = Body::new(
BodyKind::Length(5),
io.clone(),
true,
&Limits::default(),
Rc::new(Cell::new(false)),
);
drop(b);
assert_eq!(io.borrow().continues, 0);
}
#[test]
fn response_builders_set_expected_fields() {
let r = Response::text("hi");
assert_eq!(r.status, 200);
assert!(matches!(r.body, ResponseBody::Full(_)));
assert_eq!(
crate::header::get_str(&r.headers, &HeaderId::ContentType),
Some("text/plain; charset=utf-8")
);
let r = Response::json(Bytes::from_static(b"{}"));
assert_eq!(
crate::header::get_str(&r.headers, &HeaderId::ContentType),
Some("application/json")
);
let r = Response::status_only(404);
assert_eq!(r.status, 404);
assert!(matches!(r.body, ResponseBody::Empty));
let r = Response::ok().header(HeaderId::Etag, Bytes::from_static(b"v1"));
assert_eq!(
crate::header::get_str(&r.headers, &HeaderId::Etag),
Some("v1")
);
}
#[tokio::test]
async fn service_future_need_not_be_send() {
use std::cell::Cell;
let counter = Rc::new(Cell::new(0u32));
let svc = {
let counter = counter.clone();
move |_req: Request| {
let counter = counter.clone();
async move {
counter.set(counter.get() + 1);
tokio::task::yield_now().await;
counter.set(counter.get() + 1);
Response::ok()
}
}
};
let req = Request {
head: crate::parse_head(
&Bytes::from_static(b"GET / HTTP/1.1\r\nHost: a\r\n\r\n"),
&Limits::default(),
)
.unwrap()
.unwrap()
.0,
body: Body::empty(),
peer: None,
};
let resp = H1Service::call(&svc, req).await;
assert_eq!(resp.status, 200);
assert_eq!(counter.get(), 2);
}
#[tokio::test]
async fn from_bytes_body_round_trips() {
let mut b = Body::from_bytes(Bytes::from_static(b"payload"));
assert_eq!(&b.collect(1024).await.unwrap()[..], b"payload");
}
#[cfg(feature = "hyper-backend")]
mod backend_body {
use super::*;
fn raw_body(buffered: &'static [u8], reads: Vec<&'static [u8]>, cap: u64) -> Body {
Body::from_backend(
BodyKind::Chunked,
MockIo::with_buffered(buffered, reads),
false,
cap,
Rc::new(Cell::new(false)),
Rc::new(RefCell::new(None)),
)
}
#[tokio::test]
async fn streams_frames_until_eof_without_reframing() {
let mut b = raw_body(b"hel", vec![b"lo world", b""], 1024);
assert_eq!(b.kind(), BodyKind::Chunked, "kind reports the wire framing");
assert_eq!(&b.collect(1024).await.unwrap()[..], b"hello world");
assert!(b.was_fully_read());
assert!(b.trailers().is_some(), "empty trailers after clean EOF");
}
#[tokio::test]
async fn enforces_the_cumulative_body_cap() {
let mut b = raw_body(b"hello", vec![b" world", b""], 8);
let err = b.collect(1024).await.unwrap_err();
assert!(matches!(err, BodyError::TooLarge), "got {err:?}");
assert_eq!(err.status(), 413);
assert!(!b.was_fully_read());
}
#[tokio::test]
async fn surfaces_trailers_from_the_slot() {
let slot = Rc::new(RefCell::new(None));
let io = MockIo::with_buffered(b"hi", vec![b""]);
let mut b = Body::from_backend(
BodyKind::Chunked,
io,
false,
1024,
Rc::new(Cell::new(false)),
slot.clone(),
);
let mut t = HeaderVec::new();
t.push((HeaderId::Etag, Bytes::from_static(b"x")));
*slot.borrow_mut() = Some(t);
b.collect(1024).await.unwrap();
let t = b.trailers().expect("trailers after end");
assert_eq!(crate::header::get_str(t, &HeaderId::Etag), Some("x"));
}
#[tokio::test]
async fn sends_continue_before_first_read() {
let io = MockIo::with_buffered(b"hi", vec![b""]);
let mut b = Body::from_backend(
BodyKind::Length(2),
io.clone(),
true,
1024,
Rc::new(Cell::new(false)),
Rc::new(RefCell::new(None)),
);
b.collect(1024).await.unwrap();
assert_eq!(io.borrow().continues, 1);
}
}
}