feat(ffi): add hyper_request_on_informational

This defines an extension type used in requests for the client that is
used to setup a callback for receipt of informational (1xx) responses.
The type isn't currently public, and is only usable in the C API.
This commit is contained in:
Sean McArthur
2021-07-08 11:32:39 -07:00
parent 1cd40b7e26
commit 25d18c0b74
10 changed files with 162 additions and 8 deletions

View File

@@ -148,6 +148,16 @@ static int print_each_header(void *userdata,
return HYPER_ITER_CONTINUE; return HYPER_ITER_CONTINUE;
} }
static void print_informational(void *userdata, hyper_response *resp) {
uint16_t http_status = hyper_response_status(resp);
printf("\nInformational (1xx): %d\n", http_status);
hyper_headers *headers = hyper_response_headers(resp);
hyper_headers_foreach(headers, print_each_header, NULL);
printf("\n");
}
typedef enum { typedef enum {
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
EXAMPLE_HANDSHAKE, EXAMPLE_HANDSHAKE,
@@ -172,7 +182,7 @@ int main(int argc, char *argv[]) {
upload.fd = open(file, O_RDONLY); upload.fd = open(file, O_RDONLY);
if (upload.fd < 0) { if (upload.fd < 0) {
printf("error opening file to upload: %d", errno); printf("error opening file to upload: %s\n", strerror(errno));
return 1; return 1;
} }
printf("connecting to port %s on %s...\n", port, host); printf("connecting to port %s on %s...\n", port, host);
@@ -262,7 +272,10 @@ int main(int argc, char *argv[]) {
} }
hyper_headers *req_headers = hyper_request_headers(req); hyper_headers *req_headers = hyper_request_headers(req);
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host)); hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
hyper_headers_set(req_headers, STR_ARG("expect"), STR_ARG("100-continue"));
hyper_request_on_informational(req, print_informational, NULL);
// Prepare the req body // Prepare the req body
hyper_body *body = hyper_body_new(); hyper_body *body = hyper_body_new();

View File

@@ -207,6 +207,8 @@ typedef int (*hyper_body_foreach_callback)(void*, const struct hyper_buf*);
typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**); typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**);
typedef void (*hyper_request_on_informational_callback)(void*, const struct hyper_response*);
typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t); typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t);
typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t);
@@ -454,6 +456,27 @@ struct hyper_headers *hyper_request_headers(struct hyper_request *req);
*/ */
enum hyper_code hyper_request_set_body(struct hyper_request *req, struct hyper_body *body); enum hyper_code hyper_request_set_body(struct hyper_request *req, struct hyper_body *body);
/*
Set an informational (1xx) response callback.
The callback is called each time hyper receives an informational (1xx)
response for this request.
The third argument is an opaque user data pointer, which is passed to
the callback each time.
The callback is passed the `void *` data pointer, and a
`hyper_response *` which can be inspected as any other response. The
body of the response will always be empty.
NOTE: The `const hyper_response *` is just borrowed data, and will not
be valid after the callback finishes. You must copy any data you wish
to persist.
*/
enum hyper_code hyper_request_on_informational(struct hyper_request *req,
hyper_request_on_informational_callback callback,
void *data);
/* /*
Free an HTTP response after using it. Free an HTTP response after using it.
*/ */

View File

@@ -5,7 +5,7 @@ use std::ffi::c_void;
use super::body::{hyper_body, hyper_buf}; use super::body::{hyper_body, hyper_buf};
use super::error::hyper_code; use super::error::hyper_code;
use super::task::{hyper_task_return_type, AsTaskType}; use super::task::{hyper_task_return_type, AsTaskType};
use super::HYPER_ITER_CONTINUE; use super::{UserDataPointer, HYPER_ITER_CONTINUE};
use crate::ext::HeaderCaseMap; use crate::ext::HeaderCaseMap;
use crate::header::{HeaderName, HeaderValue}; use crate::header::{HeaderName, HeaderValue};
use crate::{Body, HeaderMap, Method, Request, Response, Uri}; use crate::{Body, HeaderMap, Method, Request, Response, Uri};
@@ -29,6 +29,13 @@ pub(crate) struct ReasonPhrase(pub(crate) Bytes);
pub(crate) struct RawHeaders(pub(crate) hyper_buf); pub(crate) struct RawHeaders(pub(crate) hyper_buf);
pub(crate) struct OnInformational {
func: hyper_request_on_informational_callback,
data: UserDataPointer,
}
type hyper_request_on_informational_callback = extern "C" fn(*mut c_void, *const hyper_response);
// ===== impl hyper_request ===== // ===== impl hyper_request =====
ffi_fn! { ffi_fn! {
@@ -129,6 +136,32 @@ ffi_fn! {
} }
} }
ffi_fn! {
/// Set an informational (1xx) response callback.
///
/// The callback is called each time hyper receives an informational (1xx)
/// response for this request.
///
/// The third argument is an opaque user data pointer, which is passed to
/// the callback each time.
///
/// The callback is passed the `void *` data pointer, and a
/// `hyper_response *` which can be inspected as any other response. The
/// body of the response will always be empty.
///
/// NOTE: The `const hyper_response *` is just borrowed data, and will not
/// 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 {
let ext = OnInformational {
func: callback,
data: UserDataPointer(data),
};
unsafe { &mut *req }.0.extensions_mut().insert(ext);
hyper_code::HYPERE_OK
}
}
impl hyper_request { impl hyper_request {
pub(super) fn finalize_request(&mut self) { pub(super) fn finalize_request(&mut self) {
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() { if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
@@ -394,6 +427,15 @@ unsafe fn raw_name_value(
Ok((name, value, orig_name)) Ok((name, value, orig_name))
} }
// ===== impl OnInformational =====
impl OnInformational {
pub(crate) fn call(&mut self, resp: Response<Body>) {
let mut resp = hyper_response(resp);
(self.func)(self.data.0, &mut resp);
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@@ -81,6 +81,7 @@ struct UserDataPointer(*mut std::ffi::c_void);
// We don't actually know anything about this pointer, it's up to the user // We don't actually know anything about this pointer, it's up to the user
// to do the right thing. // to do the right thing.
unsafe impl Send for UserDataPointer {} unsafe impl Send for UserDataPointer {}
unsafe impl Sync for UserDataPointer {}
/// cbindgen:ignore /// cbindgen:ignore
static VERSION_CSTR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0"); static VERSION_CSTR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");

View File

@@ -50,6 +50,8 @@ where
title_case_headers: false, title_case_headers: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
notify_read: false, notify_read: false,
reading: Reading::Init, reading: Reading::Init,
@@ -170,6 +172,8 @@ where
preserve_header_case: self.state.preserve_header_case, preserve_header_case: self.state.preserve_header_case,
h09_responses: self.state.h09_responses, h09_responses: self.state.h09_responses,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut self.state.on_informational,
#[cfg(feature = "ffi")]
raw_headers: self.state.raw_headers, raw_headers: self.state.raw_headers,
} }
)) { )) {
@@ -185,6 +189,12 @@ where
// Prevent accepting HTTP/0.9 responses after the initial one, if any. // Prevent accepting HTTP/0.9 responses after the initial one, if any.
self.state.h09_responses = false; self.state.h09_responses = false;
// Drop any OnInformational callbacks, we're done there!
#[cfg(feature = "ffi")]
{
self.state.on_informational = None;
}
self.state.busy(); self.state.busy();
self.state.keep_alive &= msg.keep_alive; self.state.keep_alive &= msg.keep_alive;
self.state.version = msg.head.version; self.state.version = msg.head.version;
@@ -525,6 +535,14 @@ where
debug_assert!(self.state.cached_headers.is_none()); debug_assert!(self.state.cached_headers.is_none());
debug_assert!(head.headers.is_empty()); debug_assert!(head.headers.is_empty());
self.state.cached_headers = Some(head.headers); self.state.cached_headers = Some(head.headers);
#[cfg(feature = "ffi")]
{
self.state.on_informational = head
.extensions
.remove::<crate::ffi::OnInformational>();
}
Some(encoder) Some(encoder)
} }
Err(err) => { Err(err) => {
@@ -775,6 +793,11 @@ struct State {
preserve_header_case: bool, preserve_header_case: bool,
title_case_headers: bool, title_case_headers: bool,
h09_responses: bool, h09_responses: bool,
/// 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 = "ffi")] #[cfg(feature = "ffi")]
raw_headers: bool, raw_headers: bool,
/// Set to true when the Dispatcher should poll read operations /// Set to true when the Dispatcher should poll read operations

View File

@@ -598,11 +598,7 @@ cfg_client! {
match msg { match msg {
Ok((msg, body)) => { Ok((msg, body)) => {
if let Some(cb) = self.callback.take() { if let Some(cb) = self.callback.take() {
let mut res = http::Response::new(body); let res = msg.into_response(body);
*res.status_mut() = msg.subject;
*res.headers_mut() = msg.headers;
*res.version_mut() = msg.version;
*res.extensions_mut() = msg.extensions;
cb.send(Ok(res)); cb.send(Ok(res));
Ok(()) Ok(())
} else { } else {

View File

@@ -168,6 +168,8 @@ where
preserve_header_case: parse_ctx.preserve_header_case, preserve_header_case: parse_ctx.preserve_header_case,
h09_responses: parse_ctx.h09_responses, h09_responses: parse_ctx.h09_responses,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: parse_ctx.on_informational,
#[cfg(feature = "ffi")]
raw_headers: parse_ctx.raw_headers, raw_headers: parse_ctx.raw_headers,
}, },
)? { )? {
@@ -678,6 +680,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
assert!(buffered assert!(buffered

View File

@@ -75,6 +75,8 @@ pub(crate) struct ParseContext<'a> {
preserve_header_case: bool, preserve_header_case: bool,
h09_responses: bool, h09_responses: bool,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &'a mut Option<crate::ffi::OnInformational>,
#[cfg(feature = "ffi")]
raw_headers: bool, raw_headers: bool,
} }

View File

@@ -991,6 +991,13 @@ 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::empty()));
}
}
// Parsing a 1xx response could have consumed the buffer, check if // Parsing a 1xx response could have consumed the buffer, check if
// it is empty now... // it is empty now...
if buf.is_empty() { if buf.is_empty() {
@@ -1428,6 +1435,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -1453,6 +1462,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
@@ -1473,6 +1484,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
Server::parse(&mut raw, ctx).unwrap_err(); Server::parse(&mut raw, ctx).unwrap_err();
@@ -1491,6 +1504,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: true, h09_responses: true,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
@@ -1511,6 +1526,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
Client::parse(&mut raw, ctx).unwrap_err(); Client::parse(&mut raw, ctx).unwrap_err();
@@ -1535,6 +1552,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); let msg = Client::parse(&mut raw, ctx).unwrap().unwrap();
@@ -1556,6 +1575,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
Client::parse(&mut raw, ctx).unwrap_err(); Client::parse(&mut raw, ctx).unwrap_err();
@@ -1572,6 +1593,8 @@ mod tests {
preserve_header_case: true, preserve_header_case: true,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}; };
let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap(); let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap();
@@ -1609,6 +1632,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -1627,6 +1652,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -1854,6 +1881,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
} }
) )
@@ -1872,6 +1901,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -1890,6 +1921,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -2383,6 +2416,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -2465,6 +2500,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )
@@ -2503,6 +2540,8 @@ mod tests {
preserve_header_case: false, preserve_header_case: false,
h09_responses: false, h09_responses: false,
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false, raw_headers: false,
}, },
) )

View File

@@ -57,3 +57,14 @@ pub(crate) enum Dispatched {
#[cfg(feature = "http1")] #[cfg(feature = "http1")]
Upgrade(crate::upgrade::Pending), Upgrade(crate::upgrade::Pending),
} }
impl MessageHead<http::StatusCode> {
fn into_response<B>(self, body: B) -> http::Response<B> {
let mut res = http::Response::new(body);
*res.status_mut() = self.subject;
*res.headers_mut() = self.headers;
*res.version_mut() = self.version;
*res.extensions_mut() = self.extensions;
res
}
}