Skip to content

Commit 15227a3

Browse files
committed
feat(ext): add ext::on_informational() callback extension
This new function allows attaching a callback to a request, such that when it is sent through a hyper client connection, and any 1xx informational responses are received, they are passed to the callback.
1 parent 30f2961 commit 15227a3

File tree

8 files changed

+181
-37
lines changed

8 files changed

+181
-37
lines changed

src/ext/informational.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use std::sync::Arc;
2+
3+
#[derive(Clone)]
4+
pub(crate) struct OnInformational(Arc<dyn OnInformationalCallback + Send + Sync>);
5+
6+
/// Add a callback for 1xx informational responses.
7+
///
8+
/// # Example
9+
///
10+
/// ```
11+
/// # let some_body = ();
12+
/// let mut req = hyper::Request::new(some_body);
13+
///
14+
/// hyper::ext::on_informational(&mut req, |res| {
15+
/// println!("informational: {:?}", res.status());
16+
/// });
17+
///
18+
/// // send request on a client connection...
19+
/// ```
20+
pub fn on_informational<B, F>(req: &mut http::Request<B>, callback: F)
21+
where
22+
F: Fn(Response<'_>) + Send + Sync + 'static,
23+
{
24+
on_informational_raw(req, OnInformationalClosure(callback));
25+
}
26+
27+
pub(crate) fn on_informational_raw<B, C>(req: &mut http::Request<B>, callback: C)
28+
where
29+
C: OnInformationalCallback + Send + Sync + 'static,
30+
{
31+
req.extensions_mut()
32+
.insert(OnInformational(Arc::new(callback)));
33+
}
34+
35+
// Sealed, not actually nameable bounds
36+
pub(crate) trait OnInformationalCallback {
37+
fn on_informational(&self, res: http::Response<()>);
38+
}
39+
40+
impl OnInformational {
41+
pub(crate) fn call(&self, res: http::Response<()>) {
42+
self.0.on_informational(res);
43+
}
44+
}
45+
46+
struct OnInformationalClosure<F>(F);
47+
48+
impl<F> OnInformationalCallback for OnInformationalClosure<F>
49+
where
50+
F: Fn(Response<'_>) + Send + Sync + 'static,
51+
{
52+
fn on_informational(&self, res: http::Response<()>) {
53+
let res = Response(&res);
54+
(self.0)(res);
55+
}
56+
}
57+
58+
// A facade over http::Response.
59+
//
60+
// It purposefully hides being able to move the response out of the closure,
61+
// while also not being able to expect it to be a reference `&Response`.
62+
// (Otherwise, a closure can be written as `|res: &_|`, and then be broken if
63+
// we make the closure take ownership.)
64+
//
65+
// With the type not being nameable, we could change from being a facade to
66+
// being either a real reference, or moving the http::Response into the closure,
67+
// in a backwards-compatible change in the future.
68+
#[derive(Debug)]
69+
pub struct Response<'a>(&'a http::Response<()>);
70+
71+
impl Response<'_> {
72+
#[inline]
73+
pub fn status(&self) -> http::StatusCode {
74+
self.0.status()
75+
}
76+
77+
#[inline]
78+
pub fn version(&self) -> http::Version {
79+
self.0.version()
80+
}
81+
82+
#[inline]
83+
pub fn headers(&self) -> &http::HeaderMap {
84+
self.0.headers()
85+
}
86+
}

src/ext/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ mod h1_reason_phrase;
1919
#[cfg(any(feature = "http1", feature = "ffi"))]
2020
pub use h1_reason_phrase::ReasonPhrase;
2121

22+
#[cfg(all(feature = "http1", feature = "client"))]
23+
mod informational;
24+
#[cfg(all(feature = "http1", feature = "client"))]
25+
pub use informational::on_informational;
26+
#[cfg(all(feature = "http1", feature = "client"))]
27+
pub(crate) use informational::OnInformational;
28+
#[cfg(all(feature = "http1", feature = "client", feature = "ffi"))]
29+
pub(crate) use informational::{on_informational_raw, OnInformationalCallback};
30+
2231
#[cfg(feature = "http2")]
2332
/// Represents the `:protocol` pseudo-header used by
2433
/// the [Extended CONNECT Protocol].

src/ffi/http_types.rs

+16-6
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ pub struct hyper_headers {
7070
}
7171

7272
#[derive(Clone)]
73-
pub(crate) struct OnInformational {
73+
struct OnInformational {
7474
func: hyper_request_on_informational_callback,
7575
data: UserDataPointer,
7676
}
@@ -268,13 +268,21 @@ ffi_fn! {
268268
/// be valid after the callback finishes. You must copy any data you wish
269269
/// to persist.
270270
fn hyper_request_on_informational(req: *mut hyper_request, callback: hyper_request_on_informational_callback, data: *mut c_void) -> hyper_code {
271+
#[cfg(feature = "client")]
272+
{
271273
let ext = OnInformational {
272274
func: callback,
273275
data: UserDataPointer(data),
274276
};
275277
let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG);
276-
req.0.extensions_mut().insert(ext);
278+
crate::ext::on_informational_raw(&mut req.0, ext);
277279
hyper_code::HYPERE_OK
280+
}
281+
#[cfg(not(feature = "client"))]
282+
{
283+
drop((req, callback, data));
284+
hyper_code::HYPERE_FEATURE_NOT_ENABLED
285+
}
278286
}
279287
}
280288

@@ -567,10 +575,12 @@ unsafe fn raw_name_value(
567575

568576
// ===== impl OnInformational =====
569577

570-
impl OnInformational {
571-
pub(crate) fn call(&mut self, resp: Response<IncomingBody>) {
572-
let mut resp = hyper_response::wrap(resp);
573-
(self.func)(self.data.0, &mut resp);
578+
#[cfg(feature = "client")]
579+
impl crate::ext::OnInformationalCallback for OnInformational {
580+
fn on_informational(&self, res: http::Response<()>) {
581+
let res = res.map(|()| IncomingBody::empty());
582+
let mut res = hyper_response::wrap(res);
583+
(self.func)(self.data.0, &mut res);
574584
}
575585
}
576586

src/proto/h1/conn.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ where
7373
preserve_header_order: false,
7474
title_case_headers: false,
7575
h09_responses: false,
76-
#[cfg(feature = "ffi")]
76+
#[cfg(feature = "client")]
7777
on_informational: None,
7878
notify_read: false,
7979
reading: Reading::Init,
@@ -246,7 +246,7 @@ where
246246
#[cfg(feature = "ffi")]
247247
preserve_header_order: self.state.preserve_header_order,
248248
h09_responses: self.state.h09_responses,
249-
#[cfg(feature = "ffi")]
249+
#[cfg(feature = "client")]
250250
on_informational: &mut self.state.on_informational,
251251
},
252252
) {
@@ -286,7 +286,7 @@ where
286286
self.state.h09_responses = false;
287287

288288
// Drop any OnInformational callbacks, we're done there!
289-
#[cfg(feature = "ffi")]
289+
#[cfg(feature = "client")]
290290
{
291291
self.state.on_informational = None;
292292
}
@@ -636,10 +636,10 @@ where
636636
debug_assert!(head.headers.is_empty());
637637
self.state.cached_headers = Some(head.headers);
638638

639-
#[cfg(feature = "ffi")]
639+
#[cfg(feature = "client")]
640640
{
641641
self.state.on_informational =
642-
head.extensions.remove::<crate::ffi::OnInformational>();
642+
head.extensions.remove::<crate::ext::OnInformational>();
643643
}
644644

645645
Some(encoder)
@@ -943,8 +943,8 @@ struct State {
943943
/// If set, called with each 1xx informational response received for
944944
/// the current request. MUST be unset after a non-1xx response is
945945
/// received.
946-
#[cfg(feature = "ffi")]
947-
on_informational: Option<crate::ffi::OnInformational>,
946+
#[cfg(feature = "client")]
947+
on_informational: Option<crate::ext::OnInformational>,
948948
/// Set to true when the Dispatcher should poll read operations
949949
/// again. See the `maybe_notify` method for more.
950950
notify_read: bool,

src/proto/h1/io.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ where
188188
#[cfg(feature = "ffi")]
189189
preserve_header_order: parse_ctx.preserve_header_order,
190190
h09_responses: parse_ctx.h09_responses,
191-
#[cfg(feature = "ffi")]
191+
#[cfg(feature = "client")]
192192
on_informational: parse_ctx.on_informational,
193193
},
194194
)? {
@@ -710,7 +710,7 @@ mod tests {
710710
#[cfg(feature = "ffi")]
711711
preserve_header_order: false,
712712
h09_responses: false,
713-
#[cfg(feature = "ffi")]
713+
#[cfg(feature = "client")]
714714
on_informational: &mut None,
715715
};
716716
assert!(buffered

src/proto/h1/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ pub(crate) struct ParseContext<'a> {
7777
#[cfg(feature = "ffi")]
7878
preserve_header_order: bool,
7979
h09_responses: bool,
80-
#[cfg(feature = "ffi")]
81-
on_informational: &'a mut Option<crate::ffi::OnInformational>,
80+
#[cfg(feature = "client")]
81+
on_informational: &'a mut Option<crate::ext::OnInformational>,
8282
}
8383

8484
/// Passed to Http1Transaction::encode

0 commit comments

Comments
 (0)