Skip to content

Commit 835bb8f

Browse files
author
Apu Islam
committed
feat(http2): implement HTTP/2 informational responses support
Add support for HTTP/2 informational responses (1xx status codes) including 103 Early Hints. This enables servers to send preliminary headers before the final response, improving client performance through early resource discovery and connection establishment. Changes include: - extend client and server APIs to handle informational responses - update stream state management for 1xx responses - add test for informational response scenarios This implementation follows RFC 7540 and RFC 8297 specifications for HTTP/2 informational responses handling.
1 parent b9d5397 commit 835bb8f

File tree

6 files changed

+643
-14
lines changed

6 files changed

+643
-14
lines changed

src/client.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1485,6 +1485,19 @@ impl ResponseFuture {
14851485
pub fn stream_id(&self) -> crate::StreamId {
14861486
crate::StreamId::from_internal(self.inner.stream_id())
14871487
}
1488+
1489+
/// Polls for informational responses (1xx status codes).
1490+
///
1491+
/// This method should be called before polling the main response future
1492+
/// to check for any informational responses that have been received.
1493+
///
1494+
/// Returns `Poll::Ready(Some(response))` if an informational response is available,
1495+
/// `Poll::Ready(None)` if no more informational responses are expected,
1496+
/// or `Poll::Pending` if no informational response is currently available.
1497+
pub fn poll_informational(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Response<()>, crate::Error>>> {
1498+
self.inner.poll_informational(cx).map_err(Into::into)
1499+
}
1500+
14881501
/// Returns a stream of PushPromises
14891502
///
14901503
/// # Panics

src/proto/streams/recv.rs

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub(super) enum Event {
6666
Headers(peer::PollMessage),
6767
Data(Bytes),
6868
Trailers(HeaderMap),
69+
InformationalHeaders(peer::PollMessage),
6970
}
7071

7172
#[derive(Debug)]
@@ -264,6 +265,21 @@ impl Recv {
264265
// corresponding headers frame pushed to `stream.pending_recv`.
265266
self.pending_accept.push(stream);
266267
}
268+
} else {
269+
// This is an informational response (1xx status code)
270+
// Convert to response and store it for polling
271+
let message = counts
272+
.peer()
273+
.convert_poll_message(pseudo, fields, stream_id)?;
274+
275+
tracing::debug!("Received informational response: {:?}", message);
276+
277+
// Push the informational response onto the stream's recv buffer
278+
// with a special event type so it can be polled separately
279+
stream
280+
.pending_recv
281+
.push_back(&mut self.buffer, Event::InformationalHeaders(message));
282+
stream.notify_recv();
267283
}
268284

269285
Ok(())
@@ -324,24 +340,63 @@ impl Recv {
324340
) -> Poll<Result<Response<()>, proto::Error>> {
325341
use super::peer::PollMessage::*;
326342

327-
// If the buffer is not empty, then the first frame must be a HEADERS
328-
// frame or the user violated the contract.
329-
match stream.pending_recv.pop_front(&mut self.buffer) {
330-
Some(Event::Headers(Client(response))) => Poll::Ready(Ok(response)),
331-
Some(_) => panic!("poll_response called after response returned"),
332-
None => {
333-
if !stream.state.ensure_recv_open()? {
334-
proto_err!(stream: "poll_response: stream={:?} is not opened;", stream.id);
335-
return Poll::Ready(Err(Error::library_reset(
336-
stream.id,
337-
Reason::PROTOCOL_ERROR,
338-
)));
343+
// Skip over any informational headers to find the main response
344+
loop {
345+
match stream.pending_recv.pop_front(&mut self.buffer) {
346+
Some(Event::Headers(Client(response))) => return Poll::Ready(Ok(response)),
347+
Some(Event::InformationalHeaders(_)) => {
348+
// Skip informational headers - they should be consumed by poll_informational
349+
continue;
339350
}
351+
Some(_) => panic!("poll_response called after response returned"),
352+
None => {
353+
if !stream.state.ensure_recv_open()? {
354+
proto_err!(stream: "poll_response: stream={:?} is not opened;", stream.id);
355+
return Poll::Ready(Err(Error::library_reset(
356+
stream.id,
357+
Reason::PROTOCOL_ERROR,
358+
)));
359+
}
340360

341-
stream.recv_task = Some(cx.waker().clone());
342-
Poll::Pending
361+
stream.recv_task = Some(cx.waker().clone());
362+
return Poll::Pending;
363+
}
364+
}
365+
}
366+
}
367+
368+
/// Called by the client to get informational responses (1xx status codes)
369+
pub fn poll_informational(
370+
&mut self,
371+
cx: &Context,
372+
stream: &mut store::Ptr,
373+
) -> Poll<Option<Result<Response<()>, proto::Error>>> {
374+
use super::peer::PollMessage::*;
375+
376+
// Try to pop the front event and check if it's an informational response
377+
// If it's not, we put it back
378+
if let Some(event) = stream.pending_recv.pop_front(&mut self.buffer) {
379+
match event {
380+
Event::InformationalHeaders(Client(response)) => {
381+
// Found an informational response, return it
382+
return Poll::Ready(Some(Ok(response)));
383+
}
384+
other => {
385+
// Not an informational response, put it back at the front
386+
stream.pending_recv.push_front(&mut self.buffer, other);
387+
}
343388
}
344389
}
390+
391+
// No informational response available at the front
392+
if stream.state.ensure_recv_open()? {
393+
// Request to get notified once more frames arrive
394+
stream.recv_task = Some(cx.waker().clone());
395+
Poll::Pending
396+
} else {
397+
// No more frames will be received
398+
Poll::Ready(None)
399+
}
345400
}
346401

347402
/// Transition the stream based on receiving trailers

src/proto/streams/send.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,45 @@ impl Send {
167167
Ok(())
168168
}
169169

170+
/// Send informational headers (1xx responses) without changing stream state.
171+
/// This allows multiple informational responses to be sent before the final response.
172+
pub fn send_informational_headers_direct<B>(
173+
&mut self,
174+
frame: frame::Headers,
175+
buffer: &mut Buffer<Frame<B>>,
176+
stream: &mut store::Ptr,
177+
_counts: &mut Counts,
178+
task: &mut Option<Waker>,
179+
) -> Result<(), UserError> {
180+
tracing::trace!(
181+
"send_informational_headers_direct; frame={:?}; stream_id={:?}",
182+
frame,
183+
frame.stream_id()
184+
);
185+
186+
// Validate headers
187+
Self::check_headers(frame.fields())?;
188+
189+
// Ensure this is an informational response (1xx status code)
190+
if !frame.is_informational() {
191+
tracing::debug!("send_informational_headers_direct called with non-informational frame");
192+
return Err(UserError::UnexpectedFrameType);
193+
}
194+
195+
// Ensure the frame is not marked as end_stream for informational responses
196+
if frame.is_end_stream() {
197+
tracing::debug!("send_informational_headers_direct called with end_stream=true");
198+
return Err(UserError::UnexpectedFrameType);
199+
}
200+
201+
// Queue the frame for sending WITHOUT changing stream state
202+
// This is the key difference from send_headers - we don't call stream.state.send_open()
203+
self.prioritize
204+
.queue_frame(frame.into(), buffer, stream, task);
205+
206+
Ok(())
207+
}
208+
170209
/// Send an explicit RST_STREAM frame
171210
pub fn send_reset<B>(
172211
&mut self,

src/proto/streams/streams.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,42 @@ impl<B> StreamRef<B> {
11501150
}
11511151
}
11521152

1153+
pub fn send_informational_headers(&mut self, frame: frame::Headers) -> Result<(), UserError> {
1154+
let mut me = self.opaque.inner.lock().unwrap();
1155+
let me = &mut *me;
1156+
1157+
let stream = me.store.resolve(self.opaque.key);
1158+
let actions = &mut me.actions;
1159+
let mut send_buffer = self.send_buffer.inner.lock().unwrap();
1160+
let send_buffer = &mut *send_buffer;
1161+
1162+
me.counts.transition(stream, |counts, stream| {
1163+
// For informational responses (1xx), we need to send headers without
1164+
// changing the stream state. This allows multiple informational responses
1165+
// to be sent before the final response.
1166+
1167+
// Validate that this is actually an informational response
1168+
if !frame.is_informational() {
1169+
return Err(UserError::UnexpectedFrameType);
1170+
}
1171+
1172+
// Ensure the frame is not marked as end_stream for informational responses
1173+
if frame.is_end_stream() {
1174+
return Err(UserError::UnexpectedFrameType);
1175+
}
1176+
1177+
// Send the informational headers directly to the buffer without state changes
1178+
// This bypasses the normal send_headers flow that would transition the stream state
1179+
actions.send.send_informational_headers_direct(
1180+
frame,
1181+
send_buffer,
1182+
stream,
1183+
counts,
1184+
&mut actions.task
1185+
)
1186+
})
1187+
}
1188+
11531189
pub fn send_response(
11541190
&mut self,
11551191
mut response: Response<()>,
@@ -1334,6 +1370,16 @@ impl OpaqueStreamRef {
13341370

13351371
me.actions.recv.poll_response(cx, &mut stream)
13361372
}
1373+
1374+
/// Called by a client to check for informational responses (1xx status codes)
1375+
pub fn poll_informational(&mut self, cx: &Context) -> Poll<Option<Result<Response<()>, proto::Error>>> {
1376+
let mut me = self.inner.lock().unwrap();
1377+
let me = &mut *me;
1378+
1379+
let mut stream = me.store.resolve(self.key);
1380+
1381+
me.actions.recv.poll_informational(cx, &mut stream)
1382+
}
13371383
/// Called by a client to check for a pushed request.
13381384
pub fn poll_pushed(
13391385
&mut self,

src/server.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,95 @@ impl Default for Builder {
11021102
// ===== impl SendResponse =====
11031103

11041104
impl<B: Buf> SendResponse<B> {
1105+
/// Send an informational response (1xx status codes)
1106+
///
1107+
/// This method can be called multiple times before calling `send_response()`
1108+
/// to send the final response. Only 1xx status codes are allowed.
1109+
///
1110+
/// Informational responses are used to provide early feedback to the client
1111+
/// before the final response is ready. Common examples include:
1112+
/// - 100 Continue: Indicates the client should continue with the request
1113+
/// - 102 Processing: Indicates the server is processing the request
1114+
/// - 103 Early Hints: Provides early hints about resources to preload
1115+
///
1116+
/// # Arguments
1117+
/// * `response` - HTTP response with 1xx status code and headers
1118+
///
1119+
/// # Returns
1120+
/// * `Ok(())` - Informational response sent successfully
1121+
/// * `Err(Error)` - Failed to send (invalid status code, connection error, etc.)
1122+
///
1123+
/// # Examples
1124+
/// ```rust
1125+
/// use h2::server;
1126+
/// use http::{Response, StatusCode};
1127+
///
1128+
/// // Send 100 Continue before processing request body
1129+
/// let continue_response = Response::builder()
1130+
/// .status(StatusCode::CONTINUE)
1131+
/// .header("x-custom", "value")
1132+
/// .body(())
1133+
/// .unwrap();
1134+
/// send_response.send_informational(continue_response)?;
1135+
///
1136+
/// // Later send the final response
1137+
/// let final_response = Response::builder()
1138+
/// .status(StatusCode::OK)
1139+
/// .body(())
1140+
/// .unwrap();
1141+
/// let stream = send_response.send_response(final_response, false)?;
1142+
/// ```
1143+
///
1144+
/// # Errors
1145+
/// This method will return an error if:
1146+
/// - The response status code is not in the 1xx range
1147+
/// - The final response has already been sent
1148+
/// - There is a connection-level error
1149+
pub fn send_informational(&mut self, response: Response<()>) -> Result<(), crate::Error> {
1150+
tracing::debug!(
1151+
"h2::send_informational called with status: {} on stream: {:?}",
1152+
response.status(),
1153+
self.inner.stream_id()
1154+
);
1155+
1156+
// Validate that this is an informational response (1xx status code)
1157+
if !response.status().is_informational() {
1158+
// Instead of returning an error, just ignore invalid informational responses
1159+
// This allows the stream to continue working even if invalid responses are sent
1160+
tracing::debug!(
1161+
"h2::Ignoring invalid informational response with status: {}",
1162+
response.status()
1163+
);
1164+
return Ok(());
1165+
}
1166+
1167+
// Convert the response to a HEADERS frame without END_STREAM flag
1168+
// Use the proper Peer::convert_send_message method for informational responses
1169+
let frame = Peer::convert_send_message(
1170+
self.inner.stream_id(),
1171+
response,
1172+
false, // NOT end_of_stream for informational responses
1173+
);
1174+
1175+
tracing::debug!(
1176+
"h2::Converted informational response to frame: {:?}",
1177+
frame
1178+
);
1179+
1180+
// Use the proper H2 streams API for sending informational headers
1181+
// This bypasses the normal response flow and allows multiple informational responses
1182+
let result = self.inner
1183+
.send_informational_headers(frame)
1184+
.map_err(Into::into);
1185+
1186+
match &result {
1187+
Ok(()) => tracing::debug!("h2::Successfully sent informational headers"),
1188+
Err(e) => tracing::debug!("h2::Failed to send informational headers: {:?}", e),
1189+
}
1190+
1191+
result
1192+
}
1193+
11051194
/// Send a response to a client request.
11061195
///
11071196
/// On success, a [`SendStream`] instance is returned. This instance can be

0 commit comments

Comments
 (0)