Skip to content

Commit

Permalink
feat(ffi): add hyper_request_on_informational
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
seanmonstar committed Jul 12, 2021
1 parent 1cd40b7 commit 25d18c0
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 8 deletions.
17 changes: 15 additions & 2 deletions capi/examples/upload.c
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,16 @@ static int print_each_header(void *userdata,
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 {
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
EXAMPLE_HANDSHAKE,
Expand All @@ -172,7 +182,7 @@ int main(int argc, char *argv[]) {
upload.fd = open(file, O_RDONLY);

if (upload.fd < 0) {
printf("error opening file to upload: %d", errno);
printf("error opening file to upload: %s\n", strerror(errno));
return 1;
}
printf("connecting to port %s on %s...\n", port, host);
Expand Down Expand Up @@ -262,7 +272,10 @@ int main(int argc, char *argv[]) {
}

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
hyper_body *body = hyper_body_new();
Expand Down
23 changes: 23 additions & 0 deletions capi/include/hyper.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t);
Expand Down Expand Up @@ -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);

/*
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.
*/
Expand Down
44 changes: 43 additions & 1 deletion src/ffi/http_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::ffi::c_void;
use super::body::{hyper_body, hyper_buf};
use super::error::hyper_code;
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::header::{HeaderName, HeaderValue};
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
Expand All @@ -29,6 +29,13 @@ pub(crate) struct ReasonPhrase(pub(crate) Bytes);

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 =====

ffi_fn! {
Expand Down Expand Up @@ -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 {
pub(super) fn finalize_request(&mut self) {
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
Expand Down Expand Up @@ -394,6 +427,15 @@ unsafe fn raw_name_value(
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)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions src/ffi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
// to do the right thing.
unsafe impl Send for UserDataPointer {}
unsafe impl Sync for UserDataPointer {}

/// cbindgen:ignore
static VERSION_CSTR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
Expand Down
23 changes: 23 additions & 0 deletions src/proto/h1/conn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ where
title_case_headers: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: None,
#[cfg(feature = "ffi")]
raw_headers: false,
notify_read: false,
reading: Reading::Init,
Expand Down Expand Up @@ -170,6 +172,8 @@ where
preserve_header_case: self.state.preserve_header_case,
h09_responses: self.state.h09_responses,
#[cfg(feature = "ffi")]
on_informational: &mut self.state.on_informational,
#[cfg(feature = "ffi")]
raw_headers: self.state.raw_headers,
}
)) {
Expand All @@ -185,6 +189,12 @@ where
// Prevent accepting HTTP/0.9 responses after the initial one, if any.
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.keep_alive &= msg.keep_alive;
self.state.version = msg.head.version;
Expand Down Expand Up @@ -525,6 +535,14 @@ where
debug_assert!(self.state.cached_headers.is_none());
debug_assert!(head.headers.is_empty());
self.state.cached_headers = Some(head.headers);

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

Some(encoder)
}
Err(err) => {
Expand Down Expand Up @@ -775,6 +793,11 @@ struct State {
preserve_header_case: bool,
title_case_headers: 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")]
raw_headers: bool,
/// Set to true when the Dispatcher should poll read operations
Expand Down
6 changes: 1 addition & 5 deletions src/proto/h1/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -598,11 +598,7 @@ cfg_client! {
match msg {
Ok((msg, body)) => {
if let Some(cb) = self.callback.take() {
let mut res = http::Response::new(body);
*res.status_mut() = msg.subject;
*res.headers_mut() = msg.headers;
*res.version_mut() = msg.version;
*res.extensions_mut() = msg.extensions;
let res = msg.into_response(body);
cb.send(Ok(res));
Ok(())
} else {
Expand Down
4 changes: 4 additions & 0 deletions src/proto/h1/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ where
preserve_header_case: parse_ctx.preserve_header_case,
h09_responses: parse_ctx.h09_responses,
#[cfg(feature = "ffi")]
on_informational: parse_ctx.on_informational,
#[cfg(feature = "ffi")]
raw_headers: parse_ctx.raw_headers,
},
)? {
Expand Down Expand Up @@ -678,6 +680,8 @@ mod tests {
preserve_header_case: false,
h09_responses: false,
#[cfg(feature = "ffi")]
on_informational: &mut None,
#[cfg(feature = "ffi")]
raw_headers: false,
};
assert!(buffered
Expand Down
2 changes: 2 additions & 0 deletions src/proto/h1/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ pub(crate) struct ParseContext<'a> {
preserve_header_case: bool,
h09_responses: bool,
#[cfg(feature = "ffi")]
on_informational: &'a mut Option<crate::ffi::OnInformational>,
#[cfg(feature = "ffi")]
raw_headers: bool,
}

Expand Down
Loading

0 comments on commit 25d18c0

Please sign in to comment.