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:
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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::<hyper_headers>() {
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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::<crate::ffi::OnInformational>();
|
||||
}
|
||||
|
||||
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<crate::ffi::OnInformational>,
|
||||
#[cfg(feature = "ffi")]
|
||||
raw_headers: bool,
|
||||
/// Set to true when the Dispatcher should poll read operations
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -57,3 +57,14 @@ pub(crate) enum Dispatched {
|
||||
#[cfg(feature = "http1")]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user