Skip to content

Commit 657bcb4

Browse files
committed
Support SendResponse::send_continue
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_continue` 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 4dc2b4a commit 657bcb4

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed

src/proto/streams/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ impl State {
111111
Open { local, remote }
112112
}
113113
}
114-
HalfClosedRemote(AwaitingHeaders) | ReservedLocal => {
114+
HalfClosedRemote(AwaitingHeaders | Streaming) | ReservedLocal => {
115115
if eos {
116116
Closed(Cause::EndStream)
117117
} else {

src/server.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,6 +1049,21 @@ impl<B: Buf> SendResponse<B> {
10491049
.map_err(Into::into)
10501050
}
10511051

1052+
/// Send a 100-continue response to a client request.
1053+
///
1054+
/// The [`SendResponse`] instance is already associated with a received
1055+
/// request. This function may only be called once per instance and only if
1056+
/// [`send_reset`] or [`send_response`] has not been previously called.
1057+
///
1058+
/// [`SendResponse`]: #
1059+
/// [`send_reset`]: #method.send_reset
1060+
/// [`send_response`]: #method.send_response
1061+
pub fn send_continue(&mut self) -> Result<(), crate::Error> {
1062+
self.inner
1063+
.send_response(Response::builder().status(100).body(()).unwrap(), false)
1064+
.map_err(Into::into)
1065+
}
1066+
10521067
/// Push a request and response to the client
10531068
///
10541069
/// On success, a [`SendResponse`] instance is returned.

tests/h2-tests/tests/server.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,61 @@ async fn serve_request() {
105105
join(client, srv).await;
106106
}
107107

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

0 commit comments

Comments
 (0)