Skip to content

Commit fc7e766

Browse files
committed
Support sending informational (1xx) responses
Support for `Expect: 100-continue` is mandated as MUST by https://datatracker.ietf.org/doc/html/rfc7231#section-5.1.1. Yet servers built on `h2` cannot currently support this requirement. One example of such usage, is [hyper #2743](hyperium/hyper#2743). This approach adds a `send_info` method to `SendResponse` that a server application can use to implement support itself. This PR does _not_ solve the feature itself, it merely provides sufficient support for a server application to implement the functionality as desired.
1 parent 77be664 commit fc7e766

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

src/proto/streams/state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ impl State {
112112
Open { local, remote }
113113
}
114114
}
115-
HalfClosedRemote(AwaitingHeaders) | ReservedLocal => {
115+
HalfClosedRemote(AwaitingHeaders | Streaming) | ReservedLocal => {
116116
if eos {
117117
Closed(Cause::EndStream)
118118
} else {

src/server.rs

+20
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,26 @@ impl<B: Buf> SendResponse<B> {
11241124
.map_err(Into::into)
11251125
}
11261126

1127+
/// Send a non-final 1xx response to a client request.
1128+
///
1129+
/// The [`SendResponse`] instance is already associated with a received
1130+
/// request. This function may only be called if [`send_reset`] or
1131+
/// [`send_response`] has not been previously called.
1132+
///
1133+
/// [`SendResponse`]: #
1134+
/// [`send_reset`]: #method.send_reset
1135+
/// [`send_response`]: #method.send_response
1136+
///
1137+
/// # Panics
1138+
///
1139+
/// If a "final" response has already been sent, or if the stream has been reset.
1140+
pub fn send_info(&mut self, response: Response<()>) -> Result<(), crate::Error> {
1141+
assert!(response.status().is_informational());
1142+
self.inner
1143+
.send_response(response, false)
1144+
.map_err(Into::into)
1145+
}
1146+
11271147
/// Push a request and response to the client
11281148
///
11291149
/// On success, a [`SendResponse`] instance is returned.

tests/h2-tests/tests/server.rs

+55
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,61 @@ async fn serve_request() {
104104
join(client, srv).await;
105105
}
106106

107+
#[tokio::test]
108+
async fn serve_request_expect_continue() {
109+
h2_support::trace_init!();
110+
let (io, mut client) = mock::new();
111+
112+
let client = async move {
113+
let settings = client.assert_server_handshake().await;
114+
assert_default_settings!(settings);
115+
client
116+
.send_frame(
117+
frames::headers(1)
118+
.field(http::header::EXPECT, "100-continue")
119+
.request("POST", "https://example.com/"),
120+
)
121+
.await;
122+
client.recv_frame(frames::headers(1).response(100)).await;
123+
client
124+
.send_frame(frames::data(1, "hello world").eos())
125+
.await;
126+
client
127+
.recv_frame(frames::headers(1).response(200).eos())
128+
.await;
129+
};
130+
131+
let srv = async move {
132+
let mut srv = server::handshake(io).await.expect("handshake");
133+
let (req, mut stream) = srv.next().await.unwrap().unwrap();
134+
135+
assert_eq!(req.method(), &http::Method::POST);
136+
assert_eq!(
137+
req.headers().get(http::header::EXPECT),
138+
Some(&http::HeaderValue::from_static("100-continue"))
139+
);
140+
141+
let connection_fut = poll_fn(|cx| srv.poll_closed(cx).map(Result::ok));
142+
let test_fut = async move {
143+
stream.send_continue().unwrap();
144+
145+
let mut body = req.into_body();
146+
assert_eq!(
147+
body.next().await.unwrap().unwrap(),
148+
Bytes::from_static(b"hello world")
149+
);
150+
assert!(body.next().await.is_none());
151+
152+
let rsp = http::Response::builder().status(200).body(()).unwrap();
153+
stream.send_response(rsp, true).unwrap();
154+
};
155+
156+
join(connection_fut, test_fut).await;
157+
};
158+
159+
join(client, srv).await;
160+
}
161+
107162
#[tokio::test]
108163
async fn serve_connect() {
109164
h2_support::trace_init!();

0 commit comments

Comments
 (0)