Skip to content

Commit 6928536

Browse files
committed
refactor(http/retry): prepare PeekTrailersBody<B> for hyper upgrade
this is a refactor commit, outlined from #3504. this commit is particularly interested in preparing the `PeekTrailersBody<B>` middleware for our upgrade to hyper/http-body 1.0. more specifically: this commit clears up the boundary concerning when it will or will not become inert and delegate to its inner body `B`. currently, a `PeekTrailersBody<B>` is not fully consistent about the conditions in which it will peek the trailers of a response body: the inner body is allowed to yield _either_ (a) **zero** DATA frames, in which case the body will be `.await`'ed and polled until the trailers are obtained, or (b) **one** DATA frame, so long as the inner body immediately yields a trailer. see linkerd/linkerd2#8733. Signed-off-by: katelyn martin <[email protected]>
1 parent b07b0d8 commit 6928536

File tree

1 file changed

+89
-76
lines changed

1 file changed

+89
-76
lines changed

linkerd/http/retry/src/peek_trailers.rs

Lines changed: 89 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -11,32 +11,31 @@ use std::{
1111
task::{Context, Poll},
1212
};
1313

14-
/// An HTTP body that allows inspecting the body's trailers, if a `TRAILERS`
15-
/// frame was the first frame after the initial headers frame.
14+
/// An HTTP body that allows inspecting the body's trailers.
1615
///
17-
/// If the first frame of the body stream was *not* a `TRAILERS` frame, this
18-
/// behaves identically to a normal body.
19-
pub struct PeekTrailersBody<B: Body = BoxBody> {
20-
inner: B,
21-
22-
/// The first DATA frame received from the inner body, or an error that
23-
/// occurred while polling for data.
24-
///
25-
/// If this is `None`, then the body has completed without any DATA frames.
26-
first_data: Option<Result<B::Data, B::Error>>,
27-
28-
/// The inner body's trailers, if it was terminated by a `TRAILERS` frame
29-
/// after 0-1 DATA frames, or an error if polling for trailers failed.
16+
/// The body's trailers may be peeked with [`PeekTrailersBody::peek_trailers()`].
17+
///
18+
/// Trailers may only be peeked if the inner body immediately yields a TRAILERS frame. If the first
19+
/// frame of the body stream was *not* a `TRAILERS` frame, this behaves identically to a normal
20+
/// body.
21+
pub enum PeekTrailersBody<B: Body = BoxBody> {
22+
/// The trailers are not available to be inspected.
23+
Passthru {
24+
/// The inner body.
25+
inner: B,
26+
/// The first DATA frame received from the inner body, or an error that
27+
/// occurred while polling for data.
28+
///
29+
/// If this is `None`, then the body has completed without any DATA frames.
30+
first_data: Option<Result<B::Data, B::Error>>,
31+
},
32+
/// The trailers have been peeked.
3033
///
31-
/// Yes, this is a bit of a complex type, so let's break it down:
32-
/// - the outer `Option` indicates whether any trailers were received by
33-
/// `WithTrailers`; if it's `None`, then we don't *know* if the response
34-
/// had trailers, as it is not yet complete.
35-
/// - the inner `Result` and `Option` are the `Result` and `Option` returned
36-
/// by `HttpBody::trailers` on the inner body. If this is `Ok(None)`, then
37-
/// the body has terminated without trailers --- it is *known* to not have
38-
/// trailers.
39-
trailers: Option<Result<Option<http::HeaderMap>, B::Error>>,
34+
/// This variant applies if the inner body's first frame was a `TRAILERS` frame.
35+
Peek {
36+
/// The inner body's trailers.
37+
trailers: Option<Result<Option<http::HeaderMap>, B::Error>>,
38+
},
4039
}
4140

4241
pub type WithPeekTrailersBody<B> = Either<
@@ -50,10 +49,16 @@ pub struct ResponseWithPeekTrailers<S>(pub(crate) S);
5049
// === impl WithTrailers ===
5150

5251
impl<B: Body> PeekTrailersBody<B> {
52+
/// Returns a reference to the trailers, if applicable.
53+
///
54+
/// See [`PeekTrailersBody<B>`] for more information on when this returns `None`.
5355
pub fn peek_trailers(&self) -> Option<&http::HeaderMap> {
54-
self.trailers
55-
.as_ref()
56-
.and_then(|trls| trls.as_ref().ok()?.as_ref())
56+
match self {
57+
Self::Peek {
58+
trailers: Some(Ok(Some(ref t))),
59+
} => Some(t),
60+
Self::Passthru { .. } | Self::Peek { .. } => None,
61+
}
5762
}
5863

5964
pub fn map_response(rsp: http::Response<B>) -> WithPeekTrailersBody<B>
@@ -87,44 +92,32 @@ impl<B: Body> PeekTrailersBody<B> {
8792
B::Data: Send + Unpin,
8893
B::Error: Send,
8994
{
90-
let (parts, body) = rsp.into_parts();
91-
let mut body = Self {
92-
inner: body,
93-
first_data: None,
94-
trailers: None,
95-
};
95+
let (parts, mut body) = rsp.into_parts();
9696

97-
tracing::debug!("Buffering first data frame");
98-
if let Some(data) = body.inner.data().await {
97+
tracing::debug!("Buffering first body frame");
98+
if let first_data @ Some(_) = body.data().await {
9999
// The body has data; stop waiting for trailers.
100-
body.first_data = Some(data);
101-
102-
// Peek to see if there's immediately a trailers frame, and grab
103-
// it if so. Otherwise, bail.
104-
// XXX(eliza): the documentation for the `http::Body` trait says
105-
// that `poll_trailers` should only be called after `poll_data`
106-
// returns `None`...but, in practice, I'm fairly sure that this just
107-
// means that it *will not return `Ready`* until there are no data
108-
// frames left, which is fine for us here, because we `now_or_never`
109-
// it.
110-
body.trailers = body.inner.trailers().now_or_never();
111-
} else {
112-
// Okay, `poll_data` has returned `None`, so there are no data
113-
// frames left. Let's see if there's trailers...
114-
body.trailers = Some(body.inner.trailers().await);
115-
}
116-
if body.trailers.is_some() {
117-
tracing::debug!("Buffered trailers frame");
100+
let body = Self::Passthru {
101+
inner: body,
102+
first_data,
103+
};
104+
return http::Response::from_parts(parts, body);
118105
}
119106

107+
// We have confirmed that there are no data frames. Now, await the trailers.
108+
let trailers = body.trailers().await;
109+
tracing::debug!("Buffered trailers frame");
110+
let body = Self::Peek {
111+
trailers: Some(trailers),
112+
};
120113
http::Response::from_parts(parts, body)
121114
}
122115

116+
/// Returns a response with an inert [`PeekTrailersBody<B>`].
123117
fn no_trailers(rsp: http::Response<B>) -> http::Response<Self> {
124-
rsp.map(|inner| Self {
118+
rsp.map(|inner| Self::Passthru {
125119
inner,
126120
first_data: None,
127-
trailers: None,
128121
})
129122
}
130123
}
@@ -142,47 +135,67 @@ where
142135
self: Pin<&mut Self>,
143136
cx: &mut Context<'_>,
144137
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
145-
let this = self.get_mut();
146-
if let Some(first_data) = this.first_data.take() {
147-
return Poll::Ready(Some(first_data));
138+
match self.get_mut() {
139+
Self::Passthru {
140+
first_data,
141+
ref mut inner,
142+
} => {
143+
// Return the first chunk that was buffered originally.
144+
if let data @ Some(_) = first_data.take() {
145+
return Poll::Ready(data);
146+
}
147+
// ...and then, poll the inner body.
148+
Pin::new(inner).poll_data(cx)
149+
}
150+
// If we have peeked the trailers, we've already polled an empty body.
151+
Self::Peek { .. } => Poll::Ready(None),
148152
}
149-
150-
Pin::new(&mut this.inner).poll_data(cx)
151153
}
152154

153155
fn poll_trailers(
154156
self: Pin<&mut Self>,
155157
cx: &mut Context<'_>,
156158
) -> Poll<Result<Option<http::HeaderMap>, Self::Error>> {
157-
let this = self.get_mut();
158-
if let Some(trailers) = this.trailers.take() {
159-
return Poll::Ready(trailers);
159+
match self.get_mut() {
160+
Self::Passthru { ref mut inner, .. } => Pin::new(inner).poll_trailers(cx),
161+
Self::Peek { trailers } => {
162+
let trailers = trailers
163+
.take()
164+
.expect("poll_trailers should not be called more than once");
165+
Poll::Ready(trailers)
166+
}
160167
}
161-
162-
Pin::new(&mut this.inner).poll_trailers(cx)
163168
}
164169

165170
#[inline]
166171
fn is_end_stream(&self) -> bool {
167-
self.first_data.is_none() && self.trailers.is_none() && self.inner.is_end_stream()
172+
match self {
173+
Self::Passthru { inner, first_data } => first_data.is_none() && inner.is_end_stream(),
174+
Self::Peek { trailers: Some(_) } => false,
175+
Self::Peek { trailers: None } => true,
176+
}
168177
}
169178

170179
#[inline]
171180
fn size_hint(&self) -> http_body::SizeHint {
172181
use bytes::Buf;
173182

174-
let mut hint = self.inner.size_hint();
175-
// If we're holding onto a chunk of data, add its length to the inner
176-
// `Body`'s size hint.
177-
if let Some(Ok(chunk)) = self.first_data.as_ref() {
178-
let buffered = chunk.remaining() as u64;
179-
if let Some(upper) = hint.upper() {
180-
hint.set_upper(upper + buffered);
183+
match self {
184+
Self::Passthru { inner, first_data } => {
185+
let mut hint = inner.size_hint();
186+
// If we're holding onto a chunk of data, add its length to the inner
187+
// `Body`'s size hint.
188+
if let Some(Ok(chunk)) = first_data.as_ref() {
189+
let buffered = chunk.remaining() as u64;
190+
if let Some(upper) = hint.upper() {
191+
hint.set_upper(upper + buffered);
192+
}
193+
hint.set_lower(hint.lower() + buffered);
194+
}
195+
hint
181196
}
182-
hint.set_lower(hint.lower() + buffered);
197+
Self::Peek { .. } => http_body::SizeHint::default(),
183198
}
184-
185-
hint
186199
}
187200
}
188201

0 commit comments

Comments
 (0)