diff --git a/capi/examples/upload.c b/capi/examples/upload.c index ed6f37a709..caf56aafd3 100644 --- a/capi/examples/upload.c +++ b/capi/examples/upload.c @@ -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, @@ -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); @@ -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(); diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 452512362e..fc3b71e71d 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -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); @@ -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. */ diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index e192e4bc0a..e26557cebf 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -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}; @@ -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! { @@ -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::() { @@ -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) { + let mut resp = hyper_response(resp); + (self.func)(self.data.0, &mut resp); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 83011ff0fc..fd67a880a6 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -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"); diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index cb8fdd0ac9..f3eb01addb 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -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, @@ -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, } )) { @@ -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; @@ -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::(); + } + Some(encoder) } Err(err) => { @@ -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, #[cfg(feature = "ffi")] raw_headers: bool, /// Set to true when the Dispatcher should poll read operations diff --git a/src/proto/h1/dispatch.rs b/src/proto/h1/dispatch.rs index 1a72450b15..51fabc63ad 100644 --- a/src/proto/h1/dispatch.rs +++ b/src/proto/h1/dispatch.rs @@ -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 { diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 4adc6c4419..2ff3d5a48a 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -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, }, )? { @@ -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 diff --git a/src/proto/h1/mod.rs b/src/proto/h1/mod.rs index b3f62f911b..758ac7b073 100644 --- a/src/proto/h1/mod.rs +++ b/src/proto/h1/mod.rs @@ -75,6 +75,8 @@ pub(crate) struct ParseContext<'a> { preserve_header_case: bool, h09_responses: bool, #[cfg(feature = "ffi")] + on_informational: &'a mut Option, + #[cfg(feature = "ffi")] raw_headers: bool, } diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 098febab3e..8828b94a0a 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -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 // it is empty now... if buf.is_empty() { @@ -1428,6 +1435,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -1453,6 +1462,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); @@ -1473,6 +1484,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; Server::parse(&mut raw, ctx).unwrap_err(); @@ -1491,6 +1504,8 @@ mod tests { preserve_header_case: false, h09_responses: true, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); @@ -1511,6 +1526,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; Client::parse(&mut raw, ctx).unwrap_err(); @@ -1535,6 +1552,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; let msg = Client::parse(&mut raw, ctx).unwrap().unwrap(); @@ -1556,6 +1575,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; Client::parse(&mut raw, ctx).unwrap_err(); @@ -1572,6 +1593,8 @@ mod tests { preserve_header_case: true, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }; let parsed_message = Server::parse(&mut raw, ctx).unwrap().unwrap(); @@ -1609,6 +1632,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -1627,6 +1652,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -1854,6 +1881,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, } ) @@ -1872,6 +1901,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -1890,6 +1921,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -2383,6 +2416,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -2465,6 +2500,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) @@ -2503,6 +2540,8 @@ mod tests { preserve_header_case: false, h09_responses: false, #[cfg(feature = "ffi")] + on_informational: &mut None, + #[cfg(feature = "ffi")] raw_headers: false, }, ) diff --git a/src/proto/mod.rs b/src/proto/mod.rs index 6f422078f6..513b70f86f 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -57,3 +57,14 @@ pub(crate) enum Dispatched { #[cfg(feature = "http1")] Upgrade(crate::upgrade::Pending), } + +impl MessageHead { + fn into_response(self, body: B) -> http::Response { + 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 + } +}