use super::disconnect::ResponseGuard;
#[derive(Debug, thiserror::Error)]
pub(super) enum BodyError {
#[error("upstream proxy body read failed: {0}")]
UpstreamProxy(std::sync::Arc<str>),
}
pub(super) struct GuardedBody {
inner: HyperResponseBody,
guard: ResponseGuard,
remaining: Option<u64>,
}
impl GuardedBody {
pub(super) fn attach(
response: hyper::Response<HyperResponseBody>,
guard: ResponseGuard,
bodyless_request: bool,
) -> hyper::Response<Self> {
use hyper::body::Body;
let (parts, inner) = response.into_parts();
let remaining = inner
.size_hint()
.exact()
.or_else(|| declared_content_length(&parts.headers));
let body = Self {
inner,
guard,
remaining,
};
let nothing_to_produce = body.is_end_stream() || body.remaining == Some(0);
match completes_at_construction(parts.status, nothing_to_produce, bodyless_request) {
true => body.guard.complete(),
false => {}
}
hyper::Response::from_parts(parts, body)
}
fn produced(&mut self, frame: &hyper::body::Frame<bytes::Bytes>) {
let bytes = frame.data_ref().map_or(0, |data| data.len() as u64);
self.remaining = self
.remaining
.map(|remaining| remaining.saturating_sub(bytes));
match self.remaining {
Some(0) => self.guard.complete(),
_ => {}
}
}
fn finished(&self) {
match self.remaining {
None | Some(0) => self.guard.complete(),
Some(_) => {}
}
}
}
impl hyper::body::Body for GuardedBody {
type Data = bytes::Bytes;
type Error = BodyError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let this = self.get_mut();
let outcome = std::pin::Pin::new(&mut this.inner).poll_frame(cx);
match &outcome {
std::task::Poll::Ready(None) => this.finished(),
std::task::Poll::Ready(Some(Ok(frame))) => this.produced(frame),
_ => {}
}
outcome
}
fn size_hint(&self) -> hyper::body::SizeHint {
self.inner.size_hint()
}
fn is_end_stream(&self) -> bool {
self.inner.is_end_stream()
}
}
fn declared_content_length(headers: &hyper::HeaderMap) -> Option<u64> {
headers
.get(hyper::header::CONTENT_LENGTH)
.and_then(|value| value.to_str().ok())
.and_then(|value| value.parse().ok())
}
fn completes_at_construction(
status: hyper::StatusCode,
nothing_to_produce: bool,
bodyless_request: bool,
) -> bool {
match status {
hyper::StatusCode::SWITCHING_PROTOCOLS => false,
hyper::StatusCode::NO_CONTENT | hyper::StatusCode::NOT_MODIFIED => true,
_ => nothing_to_produce || bodyless_request,
}
}
pub(super) enum StreamBody {
Channel(tokio::sync::mpsc::Receiver<bytes::Bytes>),
Proxy(tokio::sync::mpsc::Receiver<Result<bytes::Bytes, BodyError>>),
Drained,
}
type BodyFramePoll = std::task::Poll<Option<Result<hyper::body::Frame<bytes::Bytes>, BodyError>>>;
fn impossible_body_error(never: std::convert::Infallible) -> BodyError {
match never {}
}
fn poll_byte_receiver(
receiver: &mut tokio::sync::mpsc::Receiver<bytes::Bytes>,
cx: &mut std::task::Context<'_>,
) -> BodyFramePoll {
match receiver.poll_recv(cx) {
std::task::Poll::Ready(Some(data)) => {
std::task::Poll::Ready(Some(Ok(hyper::body::Frame::data(data))))
}
std::task::Poll::Ready(None) => std::task::Poll::Ready(None),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
fn poll_proxy_receiver(
receiver: &mut tokio::sync::mpsc::Receiver<Result<bytes::Bytes, BodyError>>,
cx: &mut std::task::Context<'_>,
) -> BodyFramePoll {
match receiver.poll_recv(cx) {
std::task::Poll::Ready(Some(Ok(data))) => {
std::task::Poll::Ready(Some(Ok(hyper::body::Frame::data(data))))
}
std::task::Poll::Ready(Some(Err(error))) => std::task::Poll::Ready(Some(Err(error))),
std::task::Poll::Ready(None) => std::task::Poll::Ready(None),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
fn map_infallible_frame(
poll: std::task::Poll<
Option<Result<hyper::body::Frame<bytes::Bytes>, std::convert::Infallible>>,
>,
) -> BodyFramePoll {
match poll {
std::task::Poll::Ready(Some(Ok(frame))) => std::task::Poll::Ready(Some(Ok(frame))),
std::task::Poll::Ready(Some(Err(never))) => {
std::task::Poll::Ready(Some(Err(impossible_body_error(never))))
}
std::task::Poll::Ready(None) => std::task::Poll::Ready(None),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
impl hyper::body::Body for StreamBody {
type Data = bytes::Bytes;
type Error = BodyError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match self.get_mut() {
StreamBody::Channel(receiver) => poll_byte_receiver(receiver, cx),
StreamBody::Proxy(receiver) => poll_proxy_receiver(receiver, cx),
StreamBody::Drained => std::task::Poll::Ready(None),
}
}
}
pub(super) enum HyperResponseBody {
Full(http_body_util::Full<bytes::Bytes>),
Streaming(StreamBody),
#[cfg(feature = "grpc")]
Grpc(GrpcBody),
}
impl hyper::body::Body for HyperResponseBody {
type Data = bytes::Bytes;
type Error = BodyError;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
match self.get_mut() {
HyperResponseBody::Full(body) => {
map_infallible_frame(std::pin::Pin::new(body).poll_frame(cx))
}
HyperResponseBody::Streaming(body) => std::pin::Pin::new(body).poll_frame(cx),
#[cfg(feature = "grpc")]
HyperResponseBody::Grpc(body) => {
map_infallible_frame(std::pin::Pin::new(body).poll_frame(cx))
}
}
}
fn size_hint(&self) -> hyper::body::SizeHint {
match self {
HyperResponseBody::Full(body) => body.size_hint(),
HyperResponseBody::Streaming(body) => body.size_hint(),
#[cfg(feature = "grpc")]
HyperResponseBody::Grpc(body) => body.size_hint(),
}
}
fn is_end_stream(&self) -> bool {
match self {
HyperResponseBody::Full(body) => body.is_end_stream(),
HyperResponseBody::Streaming(body) => body.is_end_stream(),
#[cfg(feature = "grpc")]
HyperResponseBody::Grpc(body) => body.is_end_stream(),
}
}
}
#[cfg(feature = "grpc")]
pub(super) struct GrpcBody {
pub(super) inner: tonic::body::Body,
pub(super) finished: bool,
}
#[cfg(feature = "grpc")]
impl hyper::body::Body for GrpcBody {
type Data = bytes::Bytes;
type Error = std::convert::Infallible;
fn poll_frame(
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
let this = self.get_mut();
if this.finished {
return std::task::Poll::Ready(None);
}
match std::pin::Pin::new(&mut this.inner).poll_frame(cx) {
std::task::Poll::Ready(Some(Ok(frame))) => std::task::Poll::Ready(Some(Ok(frame))),
std::task::Poll::Ready(Some(Err(status))) => {
this.finished = true;
std::task::Poll::Ready(Some(Ok(grpc_error_frame(&status))))
}
std::task::Poll::Ready(None) => {
this.finished = true;
std::task::Poll::Ready(None)
}
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
fn size_hint(&self) -> hyper::body::SizeHint {
self.inner.size_hint()
}
fn is_end_stream(&self) -> bool {
match self.finished {
true => true,
false => self.inner.is_end_stream(),
}
}
}
#[cfg(feature = "grpc")]
fn grpc_error_frame(status: &tonic::Status) -> hyper::body::Frame<bytes::Bytes> {
let mut trailers = hyper::HeaderMap::with_capacity(2);
let code = status.code() as i32;
trailers.insert("grpc-status", hyper::header::HeaderValue::from(code));
if let Ok(message) = hyper::header::HeaderValue::from_str(status.message()) {
trailers.insert("grpc-message", message);
}
hyper::body::Frame::trailers(trailers)
}