Skip to content

Commit

Permalink
feat(ext): add ext::on_informational() callback extension
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanmonstar committed Dec 24, 2024
1 parent 30f2961 commit 79e6d50
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 36 deletions.
86 changes: 86 additions & 0 deletions src/ext/informational.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use std::sync::Arc;

#[derive(Clone)]
pub(crate) struct OnInformational(Arc<dyn OnInformationalCallback + Send + Sync>);

/// Add a callback for 1xx informational responses.
///
/// # Example
///
/// ```
/// # let some_body = ();
/// let mut req = hyper::Request::new(some_body);
///
/// hyper::ext::on_informational(&mut req, |res| {
/// println!("informational: {:?}", res.status());
/// });
///
/// // send request on a client connection...
/// ```
pub fn on_informational<B, F>(req: &mut http::Request<B>, callback: F)
where
F: Fn(Response<'_>) + Send + Sync + 'static,
{
on_informational_raw(req, OnInformationalClosure(callback));
}

pub(crate) fn on_informational_raw<B, C>(req: &mut http::Request<B>, callback: C)
where
C: OnInformationalCallback + Send + Sync + 'static,
{
req.extensions_mut()
.insert(OnInformational(Arc::new(callback)));
}

// Sealed, not actually nameable bounds
pub(crate) trait OnInformationalCallback {
fn on_informational(&self, res: http::Response<()>);
}

impl OnInformational {
pub(crate) fn call(&self, res: http::Response<()>) {
self.0.on_informational(res);
}
}

struct OnInformationalClosure<F>(F);

impl<F> OnInformationalCallback for OnInformationalClosure<F>
where
F: Fn(Response<'_>) + Send + Sync + 'static,
{
fn on_informational(&self, res: http::Response<()>) {
let res = Response(&res);
(self.0)(res);
}
}

// A facade over http::Response.
//
// It purposefully hides being able to move the response out of the closure,
// while also not being able to expect it to be a reference `&Response`.
// (Otherwise, a closure can be written as `|res: &_|`, and then be broken if
// we make the closure take ownership.)
//
// With the type not being nameable, we could change from being a facade to
// being either a real reference, or moving the http::Response into the closure,
// in a backwards-compatible change in the future.
#[derive(Debug)]
pub struct Response<'a>(&'a http::Response<()>);

impl Response<'_> {
#[inline]
pub fn status(&self) -> http::StatusCode {
self.0.status()
}

#[inline]
pub fn version(&self) -> http::Version {
self.0.version()
}

#[inline]
pub fn headers(&self) -> &http::HeaderMap {
self.0.headers()
}
}
9 changes: 9 additions & 0 deletions src/ext/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ mod h1_reason_phrase;
#[cfg(any(feature = "http1", feature = "ffi"))]
pub use h1_reason_phrase::ReasonPhrase;

#[cfg(any(feature = "http1", feature = "client"))]
mod informational;
#[cfg(any(feature = "http1", feature = "client"))]
pub use informational::on_informational;
#[cfg(any(feature = "http1", feature = "client"))]
pub(crate) use informational::OnInformational;
#[cfg(all(any(feature = "http1", feature = "client"), feature = "ffi"))]
pub(crate) use informational::{on_informational_raw, OnInformationalCallback};

#[cfg(feature = "http2")]
/// Represents the `:protocol` pseudo-header used by
/// the [Extended CONNECT Protocol].
Expand Down
22 changes: 16 additions & 6 deletions src/ffi/http_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ pub struct hyper_headers {
}

#[derive(Clone)]
pub(crate) struct OnInformational {
struct OnInformational {
func: hyper_request_on_informational_callback,
data: UserDataPointer,
}
Expand Down Expand Up @@ -268,13 +268,21 @@ ffi_fn! {
/// be valid after the callback finishes. You must copy any data you wish
/// to persist.
fn hyper_request_on_informational(req: *mut hyper_request, callback: hyper_request_on_informational_callback, data: *mut c_void) -> hyper_code {
#[cfg(feature = "client")]
{
let ext = OnInformational {
func: callback,
data: UserDataPointer(data),
};
let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG);
req.0.extensions_mut().insert(ext);
crate::ext::on_informational_raw(&mut req.0, ext);
hyper_code::HYPERE_OK
}
#[cfg(not(feature = "client"))]
{
drop((req, callback, data));
hyper_code::HYPERE_FEATURE_NOT_ENABLED
}
}
}

Expand Down Expand Up @@ -567,10 +575,12 @@ unsafe fn raw_name_value(

// ===== impl OnInformational =====

impl OnInformational {
pub(crate) fn call(&mut self, resp: Response<IncomingBody>) {
let mut resp = hyper_response::wrap(resp);
(self.func)(self.data.0, &mut resp);
#[cfg(feature = "client")]
impl crate::ext::OnInformationalCallback for OnInformational {
fn on_informational(&self, res: http::Response<()>) {
let res = res.map(|()| IncomingBody::empty());
let mut res = hyper_response::wrap(res);
(self.func)(self.data.0, &mut res);
}
}

Expand Down
14 changes: 7 additions & 7 deletions src/proto/h1/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ where
preserve_header_order: false,
title_case_headers: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: None,
notify_read: false,
reading: Reading::Init,
Expand Down Expand Up @@ -246,7 +246,7 @@ where
#[cfg(feature = "ffi")]
preserve_header_order: self.state.preserve_header_order,
h09_responses: self.state.h09_responses,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut self.state.on_informational,
},
) {
Expand Down Expand Up @@ -286,7 +286,7 @@ where
self.state.h09_responses = false;

// Drop any OnInformational callbacks, we're done there!
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
{
self.state.on_informational = None;
}
Expand Down Expand Up @@ -636,10 +636,10 @@ where
debug_assert!(head.headers.is_empty());
self.state.cached_headers = Some(head.headers);

#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
{
self.state.on_informational =
head.extensions.remove::<crate::ffi::OnInformational>();
head.extensions.remove::<crate::ext::OnInformational>();
}

Some(encoder)
Expand Down Expand Up @@ -943,8 +943,8 @@ struct State {
/// If set, called with each 1xx informational response received for
/// the current request. MUST be unset after a non-1xx response is
/// received.
#[cfg(feature = "ffi")]
on_informational: Option<crate::ffi::OnInformational>,
#[cfg(feature = "client")]
on_informational: Option<crate::ext::OnInformational>,
/// Set to true when the Dispatcher should poll read operations
/// again. See the `maybe_notify` method for more.
notify_read: bool,
Expand Down
4 changes: 2 additions & 2 deletions src/proto/h1/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ where
#[cfg(feature = "ffi")]
preserve_header_order: parse_ctx.preserve_header_order,
h09_responses: parse_ctx.h09_responses,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: parse_ctx.on_informational,
},
)? {
Expand Down Expand Up @@ -710,7 +710,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
assert!(buffered
Expand Down
4 changes: 2 additions & 2 deletions src/proto/h1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ pub(crate) struct ParseContext<'a> {
#[cfg(feature = "ffi")]
preserve_header_order: bool,
h09_responses: bool,
#[cfg(feature = "ffi")]
on_informational: &'a mut Option<crate::ffi::OnInformational>,
#[cfg(feature = "client")]
on_informational: &'a mut Option<crate::ext::OnInformational>,
}

/// Passed to Http1Transaction::encode
Expand Down
37 changes: 18 additions & 19 deletions src/proto/h1/role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1153,10 +1153,9 @@ impl Http1Transaction for Client {
}));
}

#[cfg(feature = "ffi")]
if head.subject.is_informational() {
if let Some(callback) = ctx.on_informational {
callback.call(head.into_response(crate::body::Incoming::empty()));
callback.call(head.into_response(()));
}
}

Expand Down Expand Up @@ -1661,7 +1660,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down Expand Up @@ -1689,7 +1688,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
Expand Down Expand Up @@ -1734,7 +1733,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: true,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
Expand All @@ -1757,7 +1756,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
Client::parse(&mut raw, ctx).unwrap_err();
Expand All @@ -1784,7 +1783,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
Expand All @@ -1808,7 +1807,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
Client::parse(&mut raw, ctx).unwrap_err();
Expand All @@ -1828,7 +1827,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
};
let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap();
Expand Down Expand Up @@ -1867,7 +1866,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand All @@ -1888,7 +1887,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down Expand Up @@ -2118,7 +2117,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
}
)
Expand All @@ -2139,7 +2138,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand All @@ -2160,7 +2159,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down Expand Up @@ -2730,7 +2729,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down Expand Up @@ -2774,7 +2773,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
);
Expand All @@ -2798,7 +2797,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
);
Expand Down Expand Up @@ -2967,7 +2966,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down Expand Up @@ -3012,7 +3011,7 @@ mod tests {
#[cfg(feature = "ffi")]
preserve_header_order: false,
h09_responses: false,
#[cfg(feature = "ffi")]
#[cfg(feature = "client")]
on_informational: &mut None,
},
)
Expand Down
Loading

0 comments on commit 79e6d50

Please sign in to comment.