Skip to content

feat(ffi): add hyper_request_on_informational #2594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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