From ba5f6d9b40d51f155a8f659ff25d08c57edbbc03 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 16:14:11 +0100 Subject: [PATCH 01/54] Add server-side FFI layer --- src/ffi/io.rs | 11 ++++++ src/ffi/mod.rs | 2 + src/ffi/server.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++ src/ffi/task.rs | 12 ++++++ 4 files changed, 118 insertions(+) create mode 100644 src/ffi/server.rs diff --git a/src/ffi/io.rs b/src/ffi/io.rs index bff666dbcf..03b8e9dad0 100644 --- a/src/ffi/io.rs +++ b/src/ffi/io.rs @@ -59,6 +59,17 @@ ffi_fn! { } } +ffi_fn! { + /// Get the user data pointer for this IO value. + /// + /// The userdata is still owned by the IO so must be treated as "borrowed" + /// + /// Returns NULL if no userdata has been set. + fn hyper_io_get_userdata(io: *mut hyper_io) -> *mut c_void { + non_null!(&mut *io ?= std::ptr::null_mut()).userdata + } +} + ffi_fn! { /// Set the read function for this IO transport. /// diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 83d18a9a41..8c0db39298 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -49,6 +49,7 @@ mod macros; mod body; mod client; +mod server; mod error; mod http_types; mod io; @@ -76,6 +77,7 @@ pub const HYPER_HTTP_VERSION_1_1: libc::c_int = 11; /// The HTTP/2 version. pub const HYPER_HTTP_VERSION_2: libc::c_int = 20; +#[derive(Clone, Copy)] struct UserDataPointer(*mut std::ffi::c_void); // We don't actually know anything about this pointer, it's up to the user diff --git a/src/ffi/server.rs b/src/ffi/server.rs new file mode 100644 index 0000000000..e7066e1e1e --- /dev/null +++ b/src/ffi/server.rs @@ -0,0 +1,93 @@ +use std::sync::Arc; +use std::ptr; +use std::ffi::c_void; + +use crate::ffi::UserDataPointer; +use crate::ffi::io::hyper_io; +use crate::ffi::http_types::{hyper_request, hyper_response}; +use crate::ffi::task::{hyper_executor, hyper_task, hyper_task_return_type, AsTaskType, IntoDynTaskType, WeakExec}; +use crate::server::conn::{Connection, Http}; + +pub struct hyper_serverconn_options(Http); +pub struct hyper_serverconn(Connection); +pub struct hyper_service { + service_fn: hyper_service_callback, + userdata: UserDataPointer, +} +pub struct hyper_response_channel(futures_channel::oneshot::Sender>); + +type hyper_service_callback = extern "C" fn(*mut c_void, *mut hyper_request, *mut hyper_response, *mut hyper_response_channel); + +ffi_fn! { + fn hyper_serverconn_options_new(exec: *const hyper_executor) -> *mut hyper_serverconn_options { + let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; + let weak_exec = hyper_executor::downgrade(&exec); + std::mem::forget(exec); // We've not incremented the strong count when we loaded + // `from_raw` + Box::into_raw(Box::new(hyper_serverconn_options(Http::new().with_executor(weak_exec)))) + } +} + +ffi_fn! { + fn hyper_service_new(service_fn: hyper_service_callback) -> *mut hyper_service { + Box::into_raw(Box::new(hyper_service { + service_fn: service_fn, + userdata: UserDataPointer(ptr::null_mut()), + })) + } ?= ptr::null_mut() +} + +ffi_fn! { + fn hyper_service_set_userdata(service: *mut hyper_service, userdata: *mut c_void){ + let s = non_null!{ &mut *service ?= () }; + s.userdata = UserDataPointer(userdata); + } +} + +ffi_fn! { + fn hyper_serve_connection(serverconn_options: *mut hyper_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { + let serverconn_options = non_null! { &mut *serverconn_options ?= ptr::null_mut() }; + let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; + let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; + let task = hyper_task::boxed(hyper_serverconn(serverconn_options.0.serve_connection(*io, *service))); + Box::into_raw(task) + } ?= ptr::null_mut() +} + +ffi_fn! { + fn hyper_response_channel_send(channel: *mut hyper_response_channel, response: *mut hyper_response) { + let channel = non_null! { Box::from_raw(channel) ?= () }; + let response = non_null! { Box::from_raw(response) ?= () }; + let _ = channel.0.send(response); + } +} + +impl crate::service::Service> for hyper_service { + type Response = crate::Response; + type Error = crate::Error; + type Future = std::pin::Pin> + Send>>; + + fn call(&mut self, req: crate::Request) -> Self::Future { + let req_ptr = Box::into_raw(Box::new(hyper_request(req))); + let res = crate::Response::new(crate::body::Recv::empty()); + let res_ptr = Box::into_raw(Box::new(hyper_response(res))); + + let (tx, rx) = futures_channel::oneshot::channel(); + let res_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); + + (self.service_fn)(self.userdata.0, req_ptr, res_ptr, res_channel); + + Box::pin(async move { + let res = rx.await.expect("Channel closed?"); + Ok(res.0) + }) + } +} + +impl std::future::Future for hyper_serverconn { + type Output = crate::Result<()>; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + std::pin::Pin::new(&mut self.0).poll(cx) + } +} diff --git a/src/ffi/task.rs b/src/ffi/task.rs index ef54fe408f..dd76409e2c 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -14,6 +14,8 @@ use libc::c_int; use super::error::hyper_code; use super::UserDataPointer; +use crate::proto::h2::server::H2Stream; + type BoxFuture = Pin + Send>>; type BoxAny = Box; @@ -185,6 +187,16 @@ impl crate::rt::Executor> for WeakExec { } } +impl crate::common::exec::ConnStreamExec for WeakExec +where + H2Stream: Future + Send + 'static, + B: crate::body::Body, +{ + fn execute_h2stream(&mut self, fut: H2Stream) { + >::execute(&self, Box::pin(fut)) + } +} + ffi_fn! { /// Creates a new task executor. fn hyper_executor_new() -> *const hyper_executor { From 391ccf993725bc1fbb4d3309c3f359ef40672649 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 16:14:34 +0100 Subject: [PATCH 02/54] Re-generate FFI headers --- capi/gen_header.sh | 2 +- capi/include/hyper.h | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/capi/gen_header.sh b/capi/gen_header.sh index d0b9c13a32..a3fc117840 100755 --- a/capi/gen_header.sh +++ b/capi/gen_header.sh @@ -71,7 +71,7 @@ cp "$CAPI_DIR/include/hyper.h" "$header_file_backup" cd "${WORK_DIR}" || exit 2 # Expand just the ffi module -if ! output=$(RUSTFLAGS='--cfg hyper_unstable_ffi' cargo rustc -- -Z unpretty=expanded 2>&1 > expanded.rs); then +if ! output=$(RUSTFLAGS='--cfg hyper_unstable_ffi' cargo +nightly rustc -- -Z unpretty=expanded 2>&1 > expanded.rs); then # As of April 2021 the script above prints a lot of warnings/errors, and # exits with a nonzero return code, but hyper.h still gets generated. # diff --git a/capi/include/hyper.h b/capi/include/hyper.h index d41ccaaccd..2ea57dad81 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -193,6 +193,12 @@ typedef struct hyper_request hyper_request; */ typedef struct hyper_response hyper_response; +typedef struct hyper_response_channel hyper_response_channel; + +typedef struct hyper_serverconn_options hyper_serverconn_options; + +typedef struct hyper_service hyper_service; + /* An async task. */ @@ -207,6 +213,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_service_callback)(void*, struct hyper_request*, struct hyper_response*, struct hyper_response_channel*); + typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*); typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t); @@ -401,6 +409,19 @@ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options * enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hyper_clientconn_options *opts, int enabled); +struct hyper_serverconn_options *hyper_serverconn_options_new(const struct hyper_executor *exec); + +struct hyper_service *hyper_service_new(hyper_service_callback service_fn); + +void hyper_service_set_userdata(struct hyper_service *service, void *userdata); + +struct hyper_task *hyper_serve_connection(struct hyper_serverconn_options *serverconn_options, + struct hyper_io *io, + struct hyper_service *service); + +void hyper_response_channel_send(struct hyper_response_channel *channel, + struct hyper_response *response); + /* Frees a `hyper_error`. */ @@ -641,6 +662,15 @@ void hyper_io_free(struct hyper_io *io); */ void hyper_io_set_userdata(struct hyper_io *io, void *data); +/* + Get the user data pointer for this IO value. + + The userdata is still owned by the IO so must be treated as "borrowed" + + Returns NULL if no userdata has been set. + */ +void *hyper_io_get_userdata(struct hyper_io *io); + /* Set the read function for this IO transport. From 9faf3278d2e96ab4464c6ad266fdc9552b9a5668 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 16:15:01 +0100 Subject: [PATCH 03/54] Add example FFI server --- capi/examples/Makefile | 23 ++-- capi/examples/server.c | 290 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 299 insertions(+), 14 deletions(-) create mode 100644 capi/examples/server.c diff --git a/capi/examples/Makefile b/capi/examples/Makefile index 951c99fe62..3a0f324aa0 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -2,24 +2,19 @@ # Build the example client # -TARGET = client -TARGET2 = upload - -OBJS = client.o -OBJS2 = upload.o - RPATH=$(PWD)/../../target/debug -CFLAGS = -I../include +CFLAGS = -I../include -ggdb3 LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lhyper -all: $(TARGET) $(TARGET2) - -$(TARGET): $(OBJS) - $(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LIBS) +all: client upload server -$(TARGET2): $(OBJS2) - $(CC) -o $(TARGET2) $(OBJS2) $(LDFLAGS) $(LIBS) +client: client.o + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) +upload: upload.o + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) +server: server.o + $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) clean: - rm -f $(OBJS) $(TARGET) $(OBJS2) $(TARGET2) + rm -f *.o client server upload diff --git a/capi/examples/server.c b/capi/examples/server.c new file mode 100644 index 0000000000..1b7062d91f --- /dev/null +++ b/capi/examples/server.c @@ -0,0 +1,290 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "hyper.h" + +static const int MAX_EVENTS = 10; + +typedef struct conn_data_s { + int fd; + bool active; + hyper_waker *read_waker; + hyper_waker *write_waker; +} conn_data; + +int epoll = -1; + +static int listen_on(const char* host, const char* port) { + int sock = socket(AF_INET, SOCK_STREAM, 0); + struct sockaddr_in sin; + sin.sin_family = AF_INET; + sin.sin_port = htons(1234); + sin.sin_addr.s_addr = 0; + if (bind(sock, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) < 0) { + perror("bind"); + return -1; + } + if (listen(sock, 5) < 0) { + perror("listen"); + return -1; + } + + return sock; +} + +static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) { + conn_data *conn = (conn_data *)userdata; + ssize_t ret = read(conn->fd, buf, buf_len); + + if (ret == 0) { + // Closed + return ret; + } + + if (ret >= 0) { + return ret; + } + + if (errno != EAGAIN) { + // kaboom + return HYPER_IO_ERROR; + } + + // would block, register interest + if (conn->read_waker != NULL) { + hyper_waker_free(conn->read_waker); + } + conn->read_waker = hyper_context_waker(ctx); + return HYPER_IO_PENDING; +} + +static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) { + conn_data *conn = (conn_data *)userdata; + ssize_t ret = write(conn->fd, buf, buf_len); + + if (ret >= 0) { + return ret; + } + + if (errno != EAGAIN) { + // kaboom + return HYPER_IO_ERROR; + } + + // would block, register interest + if (conn->write_waker != NULL) { + hyper_waker_free(conn->write_waker); + } + conn->write_waker = hyper_context_waker(ctx); + return HYPER_IO_PENDING; +} + +static conn_data* create_conn_data(int fd) { + conn_data *conn = malloc(sizeof(conn_data)); + + if (epoll_ctl(epoll, EPOLL_CTL_ADD, new_fd, &transport_event) < 0) { + perror("epoll_ctl (transport)"); + return 1; + } + + conn->active = false; + conn->fd = fd; + conn->read_waker = NULL; + conn->write_waker = NULL; + + return conn; +} + +static hyper_io* create_io(conn_data* conn) { + // Hookup the IO + hyper_io *io = hyper_io_new(); + hyper_io_set_userdata(io, (void *)conn); + hyper_io_set_read(io, read_cb); + hyper_io_set_write(io, write_cb); + conn->active = true; + + return io; +} + +static void free_conn_data(conn_data *conn) { + if (conn->read_waker) { + hyper_waker_free(conn->read_waker); + conn->read_waker = NULL; + } + if (conn->write_waker) { + hyper_waker_free(conn->write_waker); + conn->write_waker = NULL; + } + + free(conn); +} + +static int print_each_header(void *userdata, + const uint8_t *name, + size_t name_len, + const uint8_t *value, + size_t value_len) { + printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value); + return HYPER_ITER_CONTINUE; +} + +static int print_each_chunk(void *userdata, const hyper_buf *chunk) { + const uint8_t *buf = hyper_buf_bytes(chunk); + size_t len = hyper_buf_len(chunk); + + write(1, buf, len); + + return HYPER_ITER_CONTINUE; +} + +typedef enum { + EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set + EXAMPLE_HANDSHAKE, + EXAMPLE_SEND, + EXAMPLE_RESP_BODY +} example_id; + +static void server_callback(void* userdata, hyper_request* request, hyper_response* response, hyper_response_channel* channel) { + hyper_response_channel_send(channel, response); +} + +#define STR_ARG(XX) (uint8_t *)XX, strlen(XX) + +int main(int argc, char *argv[]) { + const char *host = argc > 1 ? argv[1] : "127.0.0.1"; + const char *port = argc > 2 ? argv[2] : "1234"; + printf("listening on port %s on %s...\n", port, host); + + int listen_fd = listen_on(host, port); + if (listen_fd < 0) { + return 1; + } + + if (fcntl(listen_fd, F_SETFL, O_NONBLOCK) != 0) { + perror("fcntl(O_NONBLOCK) (listening)\n"); + return 1; + } + + // Use epoll cos' it's cool + epoll = epoll_create1(EPOLL_CLOEXEC); + if (epoll < 0) { + perror("epoll"); + return 1; + } + + // Always await new connections from the listen socket + struct epoll_event listen_event; + listen_event.events = EPOLLIN; + listen_event.data.ptr = NULL; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, listen_fd, &listen_event) < 0) { + perror("epoll_crt (add listening)"); + return 1; + } + + printf("http handshake (hyper v%s) ...\n", hyper_version()); + + // We need an executor generally to poll futures + const hyper_executor *exec = hyper_executor_new(); + + // Might have an error + hyper_error *err; + + while (1) { + while (1) { + hyper_task* task = hyper_executor_poll(exec); + if (!task) { + break; + } + printf("Task completed\n"); + + if (hyper_task_type(task) == HYPER_TASK_ERROR) { + printf("handshake error!\n"); + err = hyper_task_value(task); + goto fail; + } + + if (hyper_task_type(task) == HYPER_TASK_EMPTY) { + printf("server connection complete\n"); + conn_data* conn = hyper_task_userdata(task); + free_conn_data(conn); + hyper_task_free(task); + } + } + + printf("Processed all tasks\n"); + + struct epoll_event events[MAX_EVENTS]; + + int nevents = epoll_wait(epoll, events, MAX_EVENTS, -1); + if (nevents < 0) { + perror("epoll"); + return 1; + } + + for (int n = 0; n < nevents; n++) { + if (events[n].data.ptr == NULL) { + // Incoming connection on listen_fd + int new_fd = accept(listen_fd, NULL, 0); + if (new_fd < 0) { + perror("accept"); + return 1; + } + + // Set non-blocking + if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { + perror("fcntl(O_NONBLOCK) (listening)\n"); + return 1; + } + + // Wire up IO + conn_data *conn = create_conn_data(new_fd); + hyper_io* io = create_io(conn); + struct epoll_event transport_event; + transport_event.events = EPOLLIN; + transport_event.data.ptr = conn; + + // Ask hyper to drive this connection + hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); + hyper_service *service = hyper_service_new(server_callback); + hyper_task *serverconn = hyper_serve_connection(opts, io, service); + hyper_task_set_userdata(serverconn, conn); + hyper_executor_push(exec, serverconn); + } else { + // Existing transport socket, poke the wakers or close the socket + conn_data* conn = events[n].data.ptr; + if ((events[n].events & EPOLLIN) && conn->read_waker) { + hyper_waker_wake(conn->read_waker); + conn->read_waker = NULL; + } + if ((events[n].events & EPOLLOUT) && conn->write_waker) { + hyper_waker_wake(conn->write_waker); + conn->write_waker = NULL; + } + } + } + } + +fail: + if (err) { + printf("error code: %d\n", hyper_error_code(err)); + // grab the error details + uint8_t errbuf [256]; + size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); + printf("details: %.*s\n", (int) errlen, errbuf); + + // clean up the error + hyper_error_free(err); + } + + return 1; +} From 42c3f6739a70b162661f9670cb87262abbbef672 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 17:24:47 +0100 Subject: [PATCH 04/54] Fix deadlock in executor poll_next --- src/ffi/task.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index dd76409e2c..71e8e32fe5 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -128,7 +128,8 @@ impl hyper_executor { let mut cx = Context::from_waker(&waker); loop { - match Pin::new(&mut *self.driver.lock().unwrap()).poll_next(&mut cx) { + let poll = Pin::new(&mut *self.driver.lock().unwrap()).poll_next(&mut cx); + match poll { Poll::Ready(val) => return val, Poll::Pending => { // Check if any of the pending tasks tried to spawn From dbb54fd14dc847669a8ba5513c928a686ae286b7 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 17:25:24 +0100 Subject: [PATCH 05/54] Standardize ownership of conn_data in sample server code --- capi/examples/server.c | 64 ++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 1b7062d91f..f3977286e1 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -17,7 +17,6 @@ static const int MAX_EVENTS = 10; typedef struct conn_data_s { int fd; - bool active; hyper_waker *read_waker; hyper_waker *write_waker; } conn_data; @@ -26,6 +25,10 @@ int epoll = -1; static int listen_on(const char* host, const char* port) { int sock = socket(AF_INET, SOCK_STREAM, 0); + int reuseaddr = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) < 0) { + perror("setsockopt"); + } struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(1234); @@ -92,12 +95,16 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s static conn_data* create_conn_data(int fd) { conn_data *conn = malloc(sizeof(conn_data)); - if (epoll_ctl(epoll, EPOLL_CTL_ADD, new_fd, &transport_event) < 0) { + // Add fd to epoll set, associated with this `conn` + struct epoll_event transport_event; + transport_event.events = EPOLLIN; + transport_event.data.ptr = conn; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &transport_event) < 0) { perror("epoll_ctl (transport)"); - return 1; + free(conn); + return NULL; } - conn->active = false; conn->fd = fd; conn->read_waker = NULL; conn->write_waker = NULL; @@ -111,12 +118,18 @@ static hyper_io* create_io(conn_data* conn) { hyper_io_set_userdata(io, (void *)conn); hyper_io_set_read(io, read_cb); hyper_io_set_write(io, write_cb); - conn->active = true; return io; } static void free_conn_data(conn_data *conn) { + // Disassociate with the epoll + if (epoll_ctl(epoll, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { + perror("epoll_ctl (transport)"); + } + + close(conn->fd); + if (conn->read_waker) { hyper_waker_free(conn->read_waker); conn->read_waker = NULL; @@ -209,19 +222,37 @@ int main(int argc, char *argv[]) { if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("handshake error!\n"); + err = hyper_task_value(task); - goto fail; + printf("error code: %d\n", hyper_error_code(err)); + uint8_t errbuf [256]; + size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); + printf("details: %.*s\n", (int) errlen, errbuf); + + // clean up the error + hyper_error_free(err); + + // clean up the task + conn_data* conn = hyper_task_userdata(task); + if (conn) { + free_conn_data(conn); + } + hyper_task_free(task); } if (hyper_task_type(task) == HYPER_TASK_EMPTY) { - printf("server connection complete\n"); conn_data* conn = hyper_task_userdata(task); - free_conn_data(conn); + if (conn) { + printf("server connection complete\n"); + free_conn_data(conn); + } else { + printf("internal hyper task complete\n"); + } hyper_task_free(task); } } - printf("Processed all tasks\n"); + printf("Processed all tasks - polling for events\n"); struct epoll_event events[MAX_EVENTS]; @@ -249,9 +280,6 @@ int main(int argc, char *argv[]) { // Wire up IO conn_data *conn = create_conn_data(new_fd); hyper_io* io = create_io(conn); - struct epoll_event transport_event; - transport_event.events = EPOLLIN; - transport_event.data.ptr = conn; // Ask hyper to drive this connection hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); @@ -274,17 +302,5 @@ int main(int argc, char *argv[]) { } } -fail: - if (err) { - printf("error code: %d\n", hyper_error_code(err)); - // grab the error details - uint8_t errbuf [256]; - size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); - printf("details: %.*s\n", (int) errlen, errbuf); - - // clean up the error - hyper_error_free(err); - } - return 1; } From ee3a8061631f4b5befdef8f916316c84ed976024 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 19:10:18 +0100 Subject: [PATCH 06/54] Accept multiple incoming connections in a single poll --- capi/examples/server.c | 51 +++++++++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index f3977286e1..5ebab1bb38 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -13,7 +13,7 @@ #include "hyper.h" -static const int MAX_EVENTS = 10; +static const int MAX_EVENTS = 128; typedef struct conn_data_s { int fd; @@ -238,6 +238,8 @@ int main(int argc, char *argv[]) { free_conn_data(conn); } hyper_task_free(task); + + continue; } if (hyper_task_type(task) == HYPER_TASK_EMPTY) { @@ -249,6 +251,8 @@ int main(int argc, char *argv[]) { printf("internal hyper task complete\n"); } hyper_task_free(task); + + continue; } } @@ -262,31 +266,36 @@ int main(int argc, char *argv[]) { return 1; } + printf("Poll reported %d events\n", nevents); + for (int n = 0; n < nevents; n++) { if (events[n].data.ptr == NULL) { - // Incoming connection on listen_fd - int new_fd = accept(listen_fd, NULL, 0); - if (new_fd < 0) { - perror("accept"); - return 1; + // Incoming connection(s) on listen_fd + int new_fd; + while ((new_fd = accept(listen_fd, NULL, 0)) >= 0) { + printf("New incoming connection\n"); + + // Set non-blocking + if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { + perror("fcntl(O_NONBLOCK) (listening)\n"); + return 1; + } + + // Wire up IO + conn_data *conn = create_conn_data(new_fd); + hyper_io* io = create_io(conn); + + // Ask hyper to drive this connection + hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); + hyper_service *service = hyper_service_new(server_callback); + hyper_task *serverconn = hyper_serve_connection(opts, io, service); + hyper_task_set_userdata(serverconn, conn); + hyper_executor_push(exec, serverconn); } - // Set non-blocking - if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { - perror("fcntl(O_NONBLOCK) (listening)\n"); - return 1; + if (errno != EAGAIN) { + perror("accept"); } - - // Wire up IO - conn_data *conn = create_conn_data(new_fd); - hyper_io* io = create_io(conn); - - // Ask hyper to drive this connection - hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); - hyper_service *service = hyper_service_new(server_callback); - hyper_task *serverconn = hyper_serve_connection(opts, io, service); - hyper_task_set_userdata(serverconn, conn); - hyper_executor_push(exec, serverconn); } else { // Existing transport socket, poke the wakers or close the socket conn_data* conn = events[n].data.ptr; From c1066a603170ca9330de6d19655caf005212c230 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 17 Oct 2022 19:10:43 +0100 Subject: [PATCH 07/54] Support external C/LDFLAGS in Makefile --- capi/examples/Makefile | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/capi/examples/Makefile b/capi/examples/Makefile index 3a0f324aa0..aea8963e22 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -3,18 +3,21 @@ # RPATH=$(PWD)/../../target/debug -CFLAGS = -I../include -ggdb3 -LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH) +HYPER_CFLAGS += -I../include -ggdb3 +HYPER_LDFLAGS += -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lhyper all: client upload server +%.o : %.c ../include/hyper.h + ${CC} -c -o $@ $< $(HYPER_CFLAGS) $(CFLAGS) + client: client.o - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) upload: upload.o - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) server: server.o - $(CC) -o $@ $^ $(LDFLAGS) $(LIBS) + $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) clean: rm -f *.o client server upload From f626faf361e54047d5eb01c2839e58b5d79fc352 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 18 Oct 2022 02:36:44 +0100 Subject: [PATCH 08/54] Make sure we drop serverconn --- src/ffi/server.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ffi/server.rs b/src/ffi/server.rs index e7066e1e1e..7b7920b6e6 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -5,7 +5,7 @@ use std::ffi::c_void; use crate::ffi::UserDataPointer; use crate::ffi::io::hyper_io; use crate::ffi::http_types::{hyper_request, hyper_response}; -use crate::ffi::task::{hyper_executor, hyper_task, hyper_task_return_type, AsTaskType, IntoDynTaskType, WeakExec}; +use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; use crate::server::conn::{Connection, Http}; pub struct hyper_serverconn_options(Http); @@ -46,7 +46,7 @@ ffi_fn! { ffi_fn! { fn hyper_serve_connection(serverconn_options: *mut hyper_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { - let serverconn_options = non_null! { &mut *serverconn_options ?= ptr::null_mut() }; + let serverconn_options = non_null! { Box::from_raw(serverconn_options) ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; let task = hyper_task::boxed(hyper_serverconn(serverconn_options.0.serve_connection(*io, *service))); @@ -77,7 +77,7 @@ impl crate::service::Service> for hyper_servic (self.service_fn)(self.userdata.0, req_ptr, res_ptr, res_channel); - Box::pin(async move { + Box::pin(async move { let res = rx.await.expect("Channel closed?"); Ok(res.0) }) From 1e8dbc52d65e27a4735c06e5249c96af61292685 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 18 Oct 2022 02:37:59 +0100 Subject: [PATCH 09/54] Shutdown cleanly (at least when there are no in-flight transactions) --- capi/examples/server.c | 196 +++++++++++++++++++++++++++++------------ 1 file changed, 139 insertions(+), 57 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 5ebab1bb38..a52b2cab70 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -4,12 +4,14 @@ #include #include #include -#include +#include +#include +#include #include #include +#include #include -#include #include "hyper.h" @@ -21,23 +23,78 @@ typedef struct conn_data_s { hyper_waker *write_waker; } conn_data; -int epoll = -1; +typedef enum task_state_type_e { + TASK_STATE_NONE, + TASK_STATE_SERVERCONN, + TASK_STATE_CLIENTCONN, +} task_state_type; + +typedef struct task_state_s { + task_state_type type; + union { + conn_data* conn; + } data; +} task_state; static int listen_on(const char* host, const char* port) { - int sock = socket(AF_INET, SOCK_STREAM, 0); - int reuseaddr = 1; - if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) < 0) { - perror("setsockopt"); - } - struct sockaddr_in sin; - sin.sin_family = AF_INET; - sin.sin_port = htons(1234); - sin.sin_addr.s_addr = 0; - if (bind(sock, (struct sockaddr*)&sin, sizeof(struct sockaddr_in)) < 0) { - perror("bind"); + struct addrinfo hints; + struct addrinfo *result; + + // Work out bind address + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + hints.ai_protocol = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + int gai_rc = getaddrinfo(host, port, &hints, &result); + if (gai_rc != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(gai_rc)); return -1; } - if (listen(sock, 5) < 0) { + + // Try each bind address until one works + int sock = -1; + for (struct addrinfo *resp = result; resp; resp = resp->ai_next) { + sock = socket(resp->ai_family, resp->ai_socktype, resp->ai_protocol); + if (sock < 0) { + perror("socket"); + continue; + } + + // Enable SO_REUSEADDR + int reuseaddr = 1; + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int)) < 0) { + perror("setsockopt"); + } + + // Attempt to bind to the address + if (bind(sock, resp->ai_addr, resp->ai_addrlen) == 0) { + break; + } + + // Failed, tidy up + close(sock); + sock = -1; + } + + freeaddrinfo(result); + + if (sock < 0) { + return -1; + } + + // Non-blocking for async + if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { + perror("fcntl(O_NONBLOCK) (listening)\n"); + return 1; + } + + // Enable listening mode + if (listen(sock, 32) < 0) { perror("listen"); return -1; } @@ -49,12 +106,8 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b conn_data *conn = (conn_data *)userdata; ssize_t ret = read(conn->fd, buf, buf_len); - if (ret == 0) { - // Closed - return ret; - } - if (ret >= 0) { + // Normal (synchronous) read successful (or socket is closed) return ret; } @@ -63,7 +116,7 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b return HYPER_IO_ERROR; } - // would block, register interest + // Otherwise this would block, so register interest and return pending if (conn->read_waker != NULL) { hyper_waker_free(conn->read_waker); } @@ -76,6 +129,7 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s ssize_t ret = write(conn->fd, buf, buf_len); if (ret >= 0) { + // Normal (synchronous) write successful (or socket is closed) return ret; } @@ -84,7 +138,7 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s return HYPER_IO_ERROR; } - // would block, register interest + // Otherwise this would block, so register interest and return pending if (conn->write_waker != NULL) { hyper_waker_free(conn->write_waker); } @@ -92,7 +146,7 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s return HYPER_IO_PENDING; } -static conn_data* create_conn_data(int fd) { +static conn_data* create_conn_data(int epoll, int fd) { conn_data *conn = malloc(sizeof(conn_data)); // Add fd to epoll set, associated with this `conn` @@ -122,14 +176,13 @@ static hyper_io* create_io(conn_data* conn) { return io; } -static void free_conn_data(conn_data *conn) { +static void free_conn_data(int epoll, conn_data *conn) { // Disassociate with the epoll if (epoll_ctl(epoll, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { perror("epoll_ctl (transport)"); } - close(conn->fd); - + // Drop any saved-off wakers if (conn->read_waker) { hyper_waker_free(conn->read_waker); conn->read_waker = NULL; @@ -139,25 +192,11 @@ static void free_conn_data(conn_data *conn) { conn->write_waker = NULL; } - free(conn); -} - -static int print_each_header(void *userdata, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len) { - printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value); - return HYPER_ITER_CONTINUE; -} - -static int print_each_chunk(void *userdata, const hyper_buf *chunk) { - const uint8_t *buf = hyper_buf_bytes(chunk); - size_t len = hyper_buf_len(chunk); - - write(1, buf, len); + // Shut down the socket connection + close(conn->fd); - return HYPER_ITER_CONTINUE; + // ...and clean up + free(conn); } typedef enum { @@ -168,28 +207,39 @@ typedef enum { } example_id; static void server_callback(void* userdata, hyper_request* request, hyper_response* response, hyper_response_channel* channel) { + hyper_request_free(request); hyper_response_channel_send(channel, response); } -#define STR_ARG(XX) (uint8_t *)XX, strlen(XX) - int main(int argc, char *argv[]) { const char *host = argc > 1 ? argv[1] : "127.0.0.1"; const char *port = argc > 2 ? argv[2] : "1234"; printf("listening on port %s on %s...\n", port, host); + // The main listening socket int listen_fd = listen_on(host, port); if (listen_fd < 0) { return 1; } - if (fcntl(listen_fd, F_SETFL, O_NONBLOCK) != 0) { - perror("fcntl(O_NONBLOCK) (listening)\n"); - return 1; + // We'll quit on any of these signals + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGQUIT); + int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (signal_fd < 0) { + perror("signalfd"); + return 1; + } + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("sigprocmask"); + return 1; } // Use epoll cos' it's cool - epoll = epoll_create1(EPOLL_CLOEXEC); + int epoll = epoll_create1(EPOLL_CLOEXEC); if (epoll < 0) { perror("epoll"); return 1; @@ -198,12 +248,22 @@ int main(int argc, char *argv[]) { // Always await new connections from the listen socket struct epoll_event listen_event; listen_event.events = EPOLLIN; - listen_event.data.ptr = NULL; + listen_event.data.ptr = &listen_fd; if (epoll_ctl(epoll, EPOLL_CTL_ADD, listen_fd, &listen_event) < 0) { - perror("epoll_crt (add listening)"); + perror("epoll_ctl (add listening)"); + return 1; + } + + // Always await signals on the signal socket + struct epoll_event signal_event; + signal_event.events = EPOLLIN; + signal_event.data.ptr = &signal_fd; + if (epoll_ctl(epoll, EPOLL_CTL_ADD, signal_fd, &signal_event) < 0) { + perror("epoll_ctl (add signal)"); return 1; } + printf("http handshake (hyper v%s) ...\n", hyper_version()); // We need an executor generally to poll futures @@ -235,7 +295,7 @@ int main(int argc, char *argv[]) { // clean up the task conn_data* conn = hyper_task_userdata(task); if (conn) { - free_conn_data(conn); + free_conn_data(epoll, conn); } hyper_task_free(task); @@ -246,7 +306,7 @@ int main(int argc, char *argv[]) { conn_data* conn = hyper_task_userdata(task); if (conn) { printf("server connection complete\n"); - free_conn_data(conn); + free_conn_data(epoll, conn); } else { printf("internal hyper task complete\n"); } @@ -269,7 +329,7 @@ int main(int argc, char *argv[]) { printf("Poll reported %d events\n", nevents); for (int n = 0; n < nevents; n++) { - if (events[n].data.ptr == NULL) { + if (events[n].data.ptr == &listen_fd) { // Incoming connection(s) on listen_fd int new_fd; while ((new_fd = accept(listen_fd, NULL, 0)) >= 0) { @@ -282,7 +342,7 @@ int main(int argc, char *argv[]) { } // Wire up IO - conn_data *conn = create_conn_data(new_fd); + conn_data *conn = create_conn_data(epoll, new_fd); hyper_io* io = create_io(conn); // Ask hyper to drive this connection @@ -294,7 +354,26 @@ int main(int argc, char *argv[]) { } if (errno != EAGAIN) { - perror("accept"); + perror("accept"); + } + } else if (events[n].data.ptr == &signal_fd) { + struct signalfd_siginfo siginfo; + if (read(signal_fd, &siginfo, sizeof(struct signalfd_siginfo)) != sizeof(struct signalfd_siginfo)) { + perror("read (signal_fd)"); + return 1; + } + + if (siginfo.ssi_signo == SIGINT) { + printf("Caught SIGINT... exiting\n"); + goto EXIT; + } else if (siginfo.ssi_signo == SIGTERM) { + printf("Caught SIGTERM... exiting\n"); + goto EXIT; + } else if (siginfo.ssi_signo == SIGQUIT) { + printf("Caught SIGQUIT... exiting\n"); + goto EXIT; + } else { + printf("Caught unexpected signal %d... ignoring\n", siginfo.ssi_signo); } } else { // Existing transport socket, poke the wakers or close the socket @@ -311,5 +390,8 @@ int main(int argc, char *argv[]) { } } +EXIT: + hyper_executor_free(exec); + return 1; } From ec89a9ebf2b1d2c83797d299bd64dd207248e6c0 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 18 Oct 2022 22:00:32 +0100 Subject: [PATCH 10/54] Better logging on new connections --- capi/examples/server.c | 65 +++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index a52b2cab70..1b5b3966e7 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "hyper.h" @@ -90,6 +91,12 @@ static int listen_on(const char* host, const char* port) { // Non-blocking for async if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) { perror("fcntl(O_NONBLOCK) (listening)\n"); + return -1; + } + + // Close handle on exec(ve) + if (fcntl(sock, F_SETFD, FD_CLOEXEC) != 0) { + perror("fcntl(FD_CLOEXEC) (listening)\n"); return 1; } @@ -102,6 +109,27 @@ static int listen_on(const char* host, const char* port) { return sock; } +// Register interest in various termination signals. The returned fd can be +// polled with epoll. +static int register_signal_handler() { + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigaddset(&mask, SIGTERM); + sigaddset(&mask, SIGQUIT); + int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + if (signal_fd < 0) { + perror("signalfd"); + return 1; + } + if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { + perror("sigprocmask"); + return 1; + } + + return signal_fd; +} + static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) { conn_data *conn = (conn_data *)userdata; ssize_t ret = read(conn->fd, buf, buf_len); @@ -222,19 +250,8 @@ int main(int argc, char *argv[]) { return 1; } - // We'll quit on any of these signals - sigset_t mask; - sigemptyset(&mask); - sigaddset(&mask, SIGINT); - sigaddset(&mask, SIGTERM); - sigaddset(&mask, SIGQUIT); - int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); + int signal_fd = register_signal_handler(); if (signal_fd < 0) { - perror("signalfd"); - return 1; - } - if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { - perror("sigprocmask"); return 1; } @@ -278,8 +295,6 @@ int main(int argc, char *argv[]) { if (!task) { break; } - printf("Task completed\n"); - if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("handshake error!\n"); @@ -332,15 +347,31 @@ int main(int argc, char *argv[]) { if (events[n].data.ptr == &listen_fd) { // Incoming connection(s) on listen_fd int new_fd; - while ((new_fd = accept(listen_fd, NULL, 0)) >= 0) { - printf("New incoming connection\n"); + struct sockaddr_storage remote_addr_storage; + struct sockaddr* remote_addr = (struct sockaddr*)&remote_addr_storage; + socklen_t remote_addr_len = sizeof(struct sockaddr_storage); + while ((new_fd = accept(listen_fd, (struct sockaddr*)&remote_addr_storage, &remote_addr_len)) >= 0) { + char remote_host[128]; + char remote_port[8]; + if (getnameinfo(remote_addr, remote_addr_len, remote_host, sizeof(remote_host), remote_port, sizeof(remote_port), NI_NUMERICHOST | NI_NUMERICSERV) < 0) { + perror("getnameinfo"); + printf("New incoming connection from (unknown)\n"); + } else { + printf("New incoming connection from (%s:%s)\n", remote_host, remote_port); + } // Set non-blocking if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { - perror("fcntl(O_NONBLOCK) (listening)\n"); + perror("fcntl(O_NONBLOCK) (transport)\n"); return 1; } + // Close handle on exec(ve) + if (fcntl(new_fd, F_SETFD, FD_CLOEXEC) != 0) { + perror("fcntl(FD_CLOEXEC) (transport)\n"); + return 1; + } + // Wire up IO conn_data *conn = create_conn_data(epoll, new_fd); hyper_io* io = create_io(conn); From 51350ec895c611bf3c1d22cdd61006cb35eace8e Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:17:16 +0100 Subject: [PATCH 11/54] Allow re-using connection options --- capi/examples/server.c | 5 ++++- capi/include/hyper.h | 2 ++ src/ffi/server.rs | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 1b5b3966e7..f38c7a86e5 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -286,6 +286,9 @@ int main(int argc, char *argv[]) { // We need an executor generally to poll futures const hyper_executor *exec = hyper_executor_new(); + // Configure the server HTTP stack + hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); + // Might have an error hyper_error *err; @@ -377,7 +380,6 @@ int main(int argc, char *argv[]) { hyper_io* io = create_io(conn); // Ask hyper to drive this connection - hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); hyper_service *service = hyper_service_new(server_callback); hyper_task *serverconn = hyper_serve_connection(opts, io, service); hyper_task_set_userdata(serverconn, conn); @@ -422,6 +424,7 @@ int main(int argc, char *argv[]) { } EXIT: + hyper_serverconn_options_free(opts); hyper_executor_free(exec); return 1; diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 2ea57dad81..1505473e42 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -411,6 +411,8 @@ enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hy struct hyper_serverconn_options *hyper_serverconn_options_new(const struct hyper_executor *exec); +void hyper_serverconn_options_free(struct hyper_serverconn_options *opts); + struct hyper_service *hyper_service_new(hyper_service_callback service_fn); void hyper_service_set_userdata(struct hyper_service *service, void *userdata); diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 7b7920b6e6..cda40b42d1 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -46,7 +46,7 @@ ffi_fn! { ffi_fn! { fn hyper_serve_connection(serverconn_options: *mut hyper_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { - let serverconn_options = non_null! { Box::from_raw(serverconn_options) ?= ptr::null_mut() }; + let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; let task = hyper_task::boxed(hyper_serverconn(serverconn_options.0.serve_connection(*io, *service))); From a9a3e10ccee2b7bad960f4018c42f407f9555ea4 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:28:08 +0100 Subject: [PATCH 12/54] Improve feature checking for FFI module --- src/ffi/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 8c0db39298..39cbe7dec7 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -2,8 +2,6 @@ #![allow(non_camel_case_types)] // fmt::Debug isn't helpful on FFI types #![allow(missing_debug_implementations)] -// unreachable_pub warns `#[no_mangle] pub extern fn` in private mod. -#![allow(unreachable_pub)] //! # hyper C API //! @@ -33,8 +31,8 @@ // the `Cargo.toml`. // // But for now, give a clear message that this compile error is expected. -#[cfg(not(all(feature = "client", feature = "http1")))] -compile_error!("The `ffi` feature currently requires the `client` and `http1` features."); +#[cfg(not(all(feature = "client", feature = "server", feature = "http1")))] +compile_error!("The `ffi` feature currently requires the `client`, `server` and `http1` features."); #[cfg(not(hyper_unstable_ffi))] compile_error!( From 7029c62631c60bc55369e7a18a8820758682d0c6 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:28:54 +0100 Subject: [PATCH 13/54] Allow reading Copy data from pointers --- src/ffi/macros.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ffi/macros.rs b/src/ffi/macros.rs index 022711baaa..5a727d57eb 100644 --- a/src/ffi/macros.rs +++ b/src/ffi/macros.rs @@ -36,8 +36,12 @@ macro_rules! non_null { if $ptr.is_null() { return $err; } + #[allow(unused_unsafe)] unsafe { $eval } }}; + (*$ptr:ident ?= $err:expr) => {{ + non_null!($ptr, *$ptr, $err) + }}; (&*$ptr:ident ?= $err:expr) => {{ non_null!($ptr, &*$ptr, $err) }}; From 15b1b3a23d190c2965e69aef3f30139a3c22eb34 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:31:55 +0100 Subject: [PATCH 14/54] Fill out request/response methods --- capi/examples/server.c | 20 +++++ capi/include/hyper.h | 83 ++++++++++++++++++++ src/ffi/error.rs | 2 + src/ffi/http_types.rs | 174 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 279 insertions(+) diff --git a/capi/examples/server.c b/capi/examples/server.c index f38c7a86e5..3e9dcc83a0 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -235,7 +235,27 @@ typedef enum { } example_id; static void server_callback(void* userdata, hyper_request* request, hyper_response* response, hyper_response_channel* channel) { + unsigned char scheme[16]; + size_t scheme_len = sizeof(scheme); + unsigned char authority[16]; + size_t authority_len = sizeof(authority); + unsigned char path_and_query[16]; + size_t path_and_query_len = sizeof(path_and_query); + if (hyper_request_uri_parts(request, scheme, &scheme_len, authority, &authority_len, path_and_query, &path_and_query_len) == 0) { + printf("Request scheme was %.*s\n", (int)scheme_len, scheme); + printf("Request authority was %.*s\n", (int)authority_len, authority); + printf("Request path_and_query was %.*s\n", (int)path_and_query_len, path_and_query); + } + int version = hyper_request_version(request); + printf("Request version was %d\n", version); + unsigned char method[16]; + size_t method_len = sizeof(method); + if (hyper_request_method(request, method, &method_len) == 0) { + printf("Request method was %.*s\n", (int)method_len, method); + } + hyper_request_free(request); + hyper_response_set_status(response, 404); hyper_response_channel_send(channel, response); } diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 1505473e42..1fef185152 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -104,6 +104,10 @@ typedef enum hyper_code { The peer sent an HTTP message that could not be parsed. */ HYPERE_INVALID_PEER_MESSAGE, + /* + A provided buffer is too small to hold the value that would be written to it. + */ + HYPERE_INSUFFICIENT_SPACE, } hyper_code; /* @@ -461,6 +465,13 @@ enum hyper_code hyper_request_set_method(struct hyper_request *req, const uint8_t *method, size_t method_len); +/* + Get the HTTP Method of the request. + */ +enum hyper_code hyper_request_method(const struct hyper_request *req, + uint8_t *method, + size_t *method_len); + /* Set the URI of the request. @@ -498,6 +509,29 @@ enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req, const uint8_t *path_and_query, size_t path_and_query_len); +/* + Get the URI of the request split into scheme, authority and path/query strings. + + Each of `scheme`, `authority` and `path_and_query` may be pointers to buffers that this + function will populate with the appopriate values from the request. If one of these + pointers is non-NULL then the associated `_len` field must be a pointer to a `size_t` + which, on call, is populated with the maximum length of the buffer and, on successful + response, will be set to the actual length of the value written into the buffer. + + If a buffer is passed as `NULL` then the `_len` field will be ignored and that component + will be skipped. + + This function may fail with `HYPERE_INSUFFICIENT_SPACE` if one of the provided buffers is + not long enough to hold the value from the request. + */ +enum hyper_code hyper_request_uri_parts(const struct hyper_request *req, + uint8_t *scheme, + size_t *scheme_len, + uint8_t *authority, + size_t *authority_len, + uint8_t *path_and_query, + size_t *path_and_query_len); + /* Set the preferred HTTP version of the request. @@ -508,6 +542,18 @@ enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req, */ enum hyper_code hyper_request_set_version(struct hyper_request *req, int version); +/* + Get the HTTP version used by this request. + + The returned value could be: + + - `HYPER_HTTP_VERSION_1_0` + - `HYPER_HTTP_VERSION_1_1` + - `HYPER_HTTP_VERSION_2` + - `HYPER_HTTP_VERSION_NONE` if newer (or older). + */ +int hyper_request_version(const struct hyper_request *resp); + /* Gets a reference to the HTTP headers of this request @@ -526,6 +572,13 @@ 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); +/* + Take ownership of the body of this request. + + It is safe to free the request even after taking ownership of its body. + */ +struct hyper_body *hyper_request_body(struct hyper_request *req); + /* Set an informational (1xx) response callback. @@ -547,6 +600,11 @@ enum hyper_code hyper_request_on_informational(struct hyper_request *req, hyper_request_on_informational_callback callback, void *data); +/* + Construct a new HTTP 200 Ok response + */ +struct hyper_response *hyper_response_new(void); + /* Free an HTTP response after using it. */ @@ -559,6 +617,11 @@ void hyper_response_free(struct hyper_response *resp); */ uint16_t hyper_response_status(const struct hyper_response *resp); +/* + Set the HTTP Status-Code of this response. + */ +void hyper_response_set_status(struct hyper_response *resp, uint16_t status); + /* Get a pointer to the reason-phrase of this response. @@ -579,6 +642,16 @@ const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp); */ size_t hyper_response_reason_phrase_len(const struct hyper_response *resp); +/* + Set the preferred HTTP version of the response. + + The version value should be one of the `HYPER_HTTP_VERSION_` constants. + + Note that this won't change the major HTTP version of the connection, + since that is determined at the handshake step. + */ +enum hyper_code hyper_response_set_version(struct hyper_response *req, int version); + /* Get the HTTP version used by this response. @@ -599,6 +672,16 @@ int hyper_response_version(const struct hyper_response *resp); */ struct hyper_headers *hyper_response_headers(struct hyper_response *resp); +/* + Set the body of the response. + + The default is an empty body. + + This takes ownership of the `hyper_body *`, you must not use it or + free it after setting it on the request. + */ +enum hyper_code hyper_response_set_body(struct hyper_response *rsp, struct hyper_body *body); + /* Take ownership of the body of this response. diff --git a/src/ffi/error.rs b/src/ffi/error.rs index 015e595aee..541e49ac21 100644 --- a/src/ffi/error.rs +++ b/src/ffi/error.rs @@ -24,6 +24,8 @@ pub enum hyper_code { HYPERE_FEATURE_NOT_ENABLED, /// The peer sent an HTTP message that could not be parsed. HYPERE_INVALID_PEER_MESSAGE, + /// A provided buffer is too small to hold the value that would be written to it. + HYPERE_INSUFFICIENT_SPACE, } // ===== impl hyper_error ===== diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 473feefa84..1d8885453e 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -68,6 +68,26 @@ ffi_fn! { } } +ffi_fn! { + /// Get the HTTP Method of the request. + fn hyper_request_method(req: *const hyper_request, method: *mut u8, method_len: *mut size_t) -> hyper_code { + let req = non_null!(&*req ?= hyper_code::HYPERE_INVALID_ARG); + if method.is_null() { + return hyper_code::HYPERE_INVALID_ARG; + } + let req_method_str = req.0.method().as_str(); + unsafe { + if non_null!(*method_len ?= hyper_code::HYPERE_INVALID_ARG) < req_method_str.len() { + return hyper_code::HYPERE_INSUFFICIENT_SPACE; + } + std::ptr::copy_nonoverlapping(req_method_str.as_ptr(), method, req_method_str.len()); + *method_len = req_method_str.len(); + } + hyper_code::HYPERE_OK + } +} + + ffi_fn! { /// Set the URI of the request. /// @@ -148,6 +168,74 @@ ffi_fn! { } } +ffi_fn! { + /// Get the URI of the request split into scheme, authority and path/query strings. + /// + /// Each of `scheme`, `authority` and `path_and_query` may be pointers to buffers that this + /// function will populate with the appopriate values from the request. If one of these + /// pointers is non-NULL then the associated `_len` field must be a pointer to a `size_t` + /// which, on call, is populated with the maximum length of the buffer and, on successful + /// response, will be set to the actual length of the value written into the buffer. + /// + /// If a buffer is passed as `NULL` then the `_len` field will be ignored and that component + /// will be skipped. + /// + /// This function may fail with `HYPERE_INSUFFICIENT_SPACE` if one of the provided buffers is + /// not long enough to hold the value from the request. + fn hyper_request_uri_parts( + req: *const hyper_request, + scheme: *mut u8, + scheme_len: *mut size_t, + authority: *mut u8, + authority_len: *mut size_t, + path_and_query: *mut u8, + path_and_query_len: *mut size_t + ) -> hyper_code { + let req = non_null!(&*req ?= hyper_code::HYPERE_INVALID_ARG); + let uri = req.0.uri(); + if !scheme.is_null() { + let req_scheme_str = match uri.scheme() { + Some(s) => s.as_str(), + None => "", + }; + unsafe { + if non_null!(*scheme_len ?= hyper_code::HYPERE_INVALID_ARG) < req_scheme_str.len() { + return hyper_code::HYPERE_INSUFFICIENT_SPACE; + } + std::ptr::copy_nonoverlapping(req_scheme_str.as_ptr(), scheme, req_scheme_str.len()); + *scheme_len = req_scheme_str.len(); + } + } + if !authority.is_null() { + let req_authority_str = match uri.authority() { + Some(s) => s.as_str(), + None => "", + }; + unsafe { + if non_null!(*authority_len ?= hyper_code::HYPERE_INVALID_ARG) < req_authority_str.len() { + return hyper_code::HYPERE_INSUFFICIENT_SPACE; + } + std::ptr::copy_nonoverlapping(req_authority_str.as_ptr(), authority, req_authority_str.len()); + *authority_len = req_authority_str.len(); + } + } + if !path_and_query.is_null() { + let req_path_and_query_str = match uri.path_and_query() { + Some(s) => s.as_str(), + None => "", + }; + unsafe { + if non_null!(*path_and_query_len ?= hyper_code::HYPERE_INVALID_ARG) < req_path_and_query_str.len() { + return hyper_code::HYPERE_INSUFFICIENT_SPACE; + } + std::ptr::copy_nonoverlapping(req_path_and_query_str.as_ptr(), path_and_query, req_path_and_query_str.len()); + *path_and_query_len = req_path_and_query_str.len(); + } + } + hyper_code::HYPERE_OK + } +} + ffi_fn! { /// Set the preferred HTTP version of the request. /// @@ -173,6 +261,27 @@ ffi_fn! { } } +ffi_fn! { + /// Get the HTTP version used by this request. + /// + /// The returned value could be: + /// + /// - `HYPER_HTTP_VERSION_1_0` + /// - `HYPER_HTTP_VERSION_1_1` + /// - `HYPER_HTTP_VERSION_2` + /// - `HYPER_HTTP_VERSION_NONE` if newer (or older). + fn hyper_request_version(resp: *const hyper_request) -> c_int { + use http::Version; + + match non_null!(&*resp ?= 0).0.version() { + Version::HTTP_10 => super::HYPER_HTTP_VERSION_1_0, + Version::HTTP_11 => super::HYPER_HTTP_VERSION_1_1, + Version::HTTP_2 => super::HYPER_HTTP_VERSION_2, + _ => super::HYPER_HTTP_VERSION_NONE, + } + } +} + ffi_fn! { /// Gets a reference to the HTTP headers of this request /// @@ -198,6 +307,16 @@ ffi_fn! { } } +ffi_fn! { + /// Take ownership of the body of this request. + /// + /// It is safe to free the request even after taking ownership of its body. + fn hyper_request_body(req: *mut hyper_request) -> *mut hyper_body { + let body = std::mem::replace(non_null!(&mut *req ?= std::ptr::null_mut()).0.body_mut(), crate::Recv::empty()); + Box::into_raw(Box::new(hyper_body(body))) + } ?= std::ptr::null_mut() +} + ffi_fn! { /// Set an informational (1xx) response callback. /// @@ -237,6 +356,13 @@ impl hyper_request { // ===== impl hyper_response ===== +ffi_fn! { + /// Construct a new HTTP 200 Ok response + fn hyper_response_new() -> *mut hyper_response { + Box::into_raw(Box::new(hyper_response(Response::new(Recv::empty())))) + } ?= std::ptr::null_mut() +} + ffi_fn! { /// Free an HTTP response after using it. fn hyper_response_free(resp: *mut hyper_response) { @@ -253,6 +379,14 @@ ffi_fn! { } } +ffi_fn! { + /// Set the HTTP Status-Code of this response. + fn hyper_response_set_status(resp: *mut hyper_response, status: u16) { + let status = crate::StatusCode::from_u16(status).unwrap(); + *non_null!(&mut *resp ?= ()).0.status_mut() = status; + } +} + ffi_fn! { /// Get a pointer to the reason-phrase of this response. /// @@ -277,6 +411,31 @@ ffi_fn! { } } +ffi_fn! { + /// Set the preferred HTTP version of the response. + /// + /// The version value should be one of the `HYPER_HTTP_VERSION_` constants. + /// + /// Note that this won't change the major HTTP version of the connection, + /// since that is determined at the handshake step. + fn hyper_response_set_version(req: *mut hyper_response, version: c_int) -> hyper_code { + use http::Version; + + let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); + *req.0.version_mut() = match version { + super::HYPER_HTTP_VERSION_NONE => Version::HTTP_11, + super::HYPER_HTTP_VERSION_1_0 => Version::HTTP_10, + super::HYPER_HTTP_VERSION_1_1 => Version::HTTP_11, + super::HYPER_HTTP_VERSION_2 => Version::HTTP_2, + _ => { + // We don't know this version + return hyper_code::HYPERE_INVALID_ARG; + } + }; + hyper_code::HYPERE_OK + } +} + ffi_fn! { /// Get the HTTP version used by this response. /// @@ -308,6 +467,21 @@ ffi_fn! { } ?= std::ptr::null_mut() } +ffi_fn! { + /// Set the body of the response. + /// + /// The default is an empty body. + /// + /// This takes ownership of the `hyper_body *`, you must not use it or + /// free it after setting it on the request. + fn hyper_response_set_body(rsp: *mut hyper_response, body: *mut hyper_body) -> hyper_code { + let body = non_null!(Box::from_raw(body) ?= hyper_code::HYPERE_INVALID_ARG); + let rsp = non_null!(&mut *rsp ?= hyper_code::HYPERE_INVALID_ARG); + *rsp.0.body_mut() = body.0; + hyper_code::HYPERE_OK + } +} + ffi_fn! { /// Take ownership of the body of this response. /// From 997dd799d377a835cb4ad384d1ca5f2e41d7f054 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:33:30 +0100 Subject: [PATCH 15/54] Service callback creates response --- capi/examples/server.c | 3 ++- capi/include/hyper.h | 2 +- src/ffi/server.rs | 6 ++---- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 3e9dcc83a0..ccbab32fe9 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -234,7 +234,7 @@ typedef enum { EXAMPLE_RESP_BODY } example_id; -static void server_callback(void* userdata, hyper_request* request, hyper_response* response, hyper_response_channel* channel) { +static void server_callback(void* userdata, hyper_request* request, hyper_response_channel* channel) { unsigned char scheme[16]; size_t scheme_len = sizeof(scheme); unsigned char authority[16]; @@ -255,6 +255,7 @@ static void server_callback(void* userdata, hyper_request* request, hyper_respon } hyper_request_free(request); + hyper_response* response = hyper_response_new(); hyper_response_set_status(response, 404); hyper_response_channel_send(channel, response); } diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 1fef185152..16a73cd75b 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -217,7 +217,7 @@ 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_service_callback)(void*, struct hyper_request*, struct hyper_response*, struct hyper_response_channel*); +typedef void (*hyper_service_callback)(void*, struct hyper_request*, struct hyper_response_channel*); typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*); diff --git a/src/ffi/server.rs b/src/ffi/server.rs index cda40b42d1..94a3ea750f 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -16,7 +16,7 @@ pub struct hyper_service { } pub struct hyper_response_channel(futures_channel::oneshot::Sender>); -type hyper_service_callback = extern "C" fn(*mut c_void, *mut hyper_request, *mut hyper_response, *mut hyper_response_channel); +type hyper_service_callback = extern "C" fn(*mut c_void, *mut hyper_request, *mut hyper_response_channel); ffi_fn! { fn hyper_serverconn_options_new(exec: *const hyper_executor) -> *mut hyper_serverconn_options { @@ -69,13 +69,11 @@ impl crate::service::Service> for hyper_servic fn call(&mut self, req: crate::Request) -> Self::Future { let req_ptr = Box::into_raw(Box::new(hyper_request(req))); - let res = crate::Response::new(crate::body::Recv::empty()); - let res_ptr = Box::into_raw(Box::new(hyper_response(res))); let (tx, rx) = futures_channel::oneshot::channel(); let res_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); - (self.service_fn)(self.userdata.0, req_ptr, res_ptr, res_channel); + (self.service_fn)(self.userdata.0, req_ptr, res_channel); Box::pin(async move { let res = rx.await.expect("Channel closed?"); From be3c00ed0b524747bfcb6c906bc9ccf622e31320 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:34:42 +0100 Subject: [PATCH 16/54] Fill out server connection options --- capi/cbindgen.toml | 2 +- capi/include/hyper.h | 48 +++++++++++++++ src/ffi/server.rs | 137 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 185 insertions(+), 2 deletions(-) diff --git a/capi/cbindgen.toml b/capi/cbindgen.toml index d1a58234b5..81b5555b14 100644 --- a/capi/cbindgen.toml +++ b/capi/cbindgen.toml @@ -7,7 +7,7 @@ header = """/* */""" include_guard = "_HYPER_H" no_includes = true -sys_includes = ["stdint.h", "stddef.h"] +sys_includes = ["stdint.h", "stddef.h", "stdbool.h"] cpp_compat = true documentation_style = "c" diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 16a73cd75b..03896eb3ef 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -8,6 +8,7 @@ #include #include +#include /* Return in iter functions to continue iterating. @@ -417,6 +418,53 @@ struct hyper_serverconn_options *hyper_serverconn_options_new(const struct hyper void hyper_serverconn_options_free(struct hyper_serverconn_options *opts); +enum hyper_code hyper_serverconn_options_http1_only(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http1_half_close(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http1_keep_alive(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http1_title_case_headers(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http1_preserve_header_case(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http1_writev(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http2_only(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http2_initial_stream_window_size(struct hyper_serverconn_options *opts, + unsigned int window_size); + +enum hyper_code hyper_serverconn_options_http2_initial_connection_window_size(struct hyper_serverconn_options *opts, + unsigned int window_size); + +enum hyper_code hyper_serverconn_options_http2_adaptive_window(struct hyper_serverconn_options *opts, + bool enabled); + +enum hyper_code hyper_serverconn_options_http2_max_frame_size(struct hyper_serverconn_options *opts, + unsigned int frame_size); + +enum hyper_code hyper_serverconn_options_http2_max_concurrent_streams(struct hyper_serverconn_options *opts, + unsigned int max_streams); + +enum hyper_code hyper_serverconn_options_http2_max_send_buf_size(struct hyper_serverconn_options *opts, + uintptr_t max_buf_size); + +enum hyper_code hyper_serverconn_options_http2_enable_connect_protocol(struct hyper_serverconn_options *opts); + +enum hyper_code hyper_serverconn_options_max_buf_size(struct hyper_serverconn_options *opts, + uintptr_t max_buf_size); + +enum hyper_code hyper_serverconn_options_pipeline_flush(struct hyper_serverconn_options *opts, + bool enabled); + struct hyper_service *hyper_service_new(hyper_service_callback service_fn); void hyper_service_set_userdata(struct hyper_service *service, void *userdata); diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 94a3ea750f..a4e3365853 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -1,9 +1,10 @@ use std::sync::Arc; use std::ptr; -use std::ffi::c_void; +use std::ffi::{c_void, c_uint}; use crate::ffi::UserDataPointer; use crate::ffi::io::hyper_io; +use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; use crate::server::conn::{Connection, Http}; @@ -28,6 +29,140 @@ ffi_fn! { } } +ffi_fn! { + fn hyper_serverconn_options_free(opts: *mut hyper_serverconn_options) { + let _ = non_null! { Box::from_raw(opts) ?= () }; + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_only(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_half_close(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_half_close(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_keep_alive(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_keep_alive(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_title_case_headers(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_title_case_headers(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_preserve_header_case(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_preserve_header_case(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http1_writev(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_writev(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_only(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_initial_stream_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_initial_stream_window_size(if window_size == 0 { None } else { Some(window_size) }); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_initial_connection_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_initial_connection_window_size(if window_size == 0 { None } else { Some(window_size) }); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_adaptive_window(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_adaptive_window(enabled); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_max_frame_size(opts: *mut hyper_serverconn_options, frame_size: c_uint) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_max_frame_size(if frame_size == 0 { None } else { Some(frame_size) }); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_max_concurrent_streams(opts: *mut hyper_serverconn_options, max_streams: c_uint) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_max_concurrent_streams(if max_streams == 0 { None } else { Some(max_streams) }); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_max_send_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_max_send_buf_size(max_buf_size); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_http2_enable_connect_protocol(opts: *mut hyper_serverconn_options) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_enable_connect_protocol(); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_max_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.max_buf_size(max_buf_size); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + fn hyper_serverconn_options_pipeline_flush(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.pipeline_flush(enabled); + hyper_code::HYPERE_OK + } +} + ffi_fn! { fn hyper_service_new(service_fn: hyper_service_callback) -> *mut hyper_service { Box::into_raw(Box::new(hyper_service { From ed80b096532a63020c71f3936a6956277687d84b Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 20 Oct 2022 23:40:33 +0100 Subject: [PATCH 17/54] Simplify gen_header --- capi/cbindgen.toml | 5 +-- capi/gen_header.sh | 89 ++++++++------------------------------------ capi/include/hyper.h | 5 +-- 3 files changed, 18 insertions(+), 81 deletions(-) diff --git a/capi/cbindgen.toml b/capi/cbindgen.toml index 81b5555b14..112747a412 100644 --- a/capi/cbindgen.toml +++ b/capi/cbindgen.toml @@ -5,11 +5,8 @@ header = """/* * Copyright 2021 Sean McArthur. MIT License. * Generated by gen_header.sh. Do not edit directly. */""" -include_guard = "_HYPER_H" +pragma_once = true no_includes = true sys_includes = ["stdint.h", "stddef.h", "stdbool.h"] cpp_compat = true documentation_style = "c" - -[parse.expand] -crates = ["hyper-capi"] diff --git a/capi/gen_header.sh b/capi/gen_header.sh index a3fc117840..617132cdee 100755 --- a/capi/gen_header.sh +++ b/capi/gen_header.sh @@ -6,101 +6,44 @@ set -e CAPI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -WORK_DIR=$(mktemp -d) - -# check if tmp dir was created -if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then - echo "Could not create temp dir" - exit 1 -fi - header_file_backup="$CAPI_DIR/include/hyper.h.backup" function cleanup { - rm -rf "$WORK_DIR" + rm -rf "$WORK_DIR" || true rm "$header_file_backup" || true } trap cleanup EXIT -mkdir "$WORK_DIR/src" - -# Fake a library -cat > "$WORK_DIR/src/lib.rs" << EOF -#[path = "$CAPI_DIR/../src/ffi/mod.rs"] -pub mod ffi; -EOF - -# And its Cargo.toml -cat > "$WORK_DIR/Cargo.toml" << EOF -[package] -name = "hyper" -version = "0.0.0" -edition = "2018" -publish = false - -[dependencies] -# Determined which dependencies we need by running the "cargo rustc" command -# below and watching the compile error output for references to unknown imports, -# until we didn't get any errors. -bytes = "1" -futures-channel = "0.3" -futures-util = { version = "0.3", default-features = false, features = ["alloc"] } -libc = { version = "0.2", optional = true } -http = "0.2" -http-body = "0.4" -tokio = { version = "1", features = ["rt"] } - -[features] -default = [ - "client", - "ffi", - "http1", -] +WORK_DIR=$(mktemp -d) -http1 = [] -client = [] -ffi = ["libc", "tokio/rt"] -EOF +# check if tmp dir was created +if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then + echo "Could not create temp dir" + exit 1 +fi cp "$CAPI_DIR/include/hyper.h" "$header_file_backup" -#cargo metadata --no-default-features --features ffi --format-version 1 > "$WORK_DIR/metadata.json" - -cd "${WORK_DIR}" || exit 2 - # Expand just the ffi module -if ! output=$(RUSTFLAGS='--cfg hyper_unstable_ffi' cargo +nightly rustc -- -Z unpretty=expanded 2>&1 > expanded.rs); then - # As of April 2021 the script above prints a lot of warnings/errors, and - # exits with a nonzero return code, but hyper.h still gets generated. - # - # However, on Github Actions, this will result in automatic "annotations" - # being added to files not related to a PR, so if this is `--verify` mode, - # then don't show it. - # - # But yes show it when using it locally. - if [[ "--verify" != "$1" ]]; then - echo "$output" - fi +if ! RUSTFLAGS='--cfg hyper_unstable_ffi' cargo expand --features ffi,server,client,http1,http2 ::ffi 2> $WORK_DIR/expand_stderr.err > $WORK_DIR/expanded.rs; then + cat $WORK_DIR/expand_stderr.err fi -# Replace the previous copy with the single expanded file -rm -rf ./src -mkdir src -mv expanded.rs src/lib.rs - - # Bindgen! if ! cbindgen \ --config "$CAPI_DIR/cbindgen.toml" \ --lockfile "$CAPI_DIR/../Cargo.lock" \ --output "$CAPI_DIR/include/hyper.h" \ - "${@}"; then + "${@}"\ + $WORK_DIR/expanded.rs 2> $WORK_DIR/cbindgen_stderr.err; then bindgen_exit_code=$? if [[ "--verify" == "$1" ]]; then - echo "diff generated (<) vs backup (>)" - diff "$CAPI_DIR/include/hyper.h" "$header_file_backup" + echo "Changes from previous header (old < > new)" + diff -u "$header_file_backup" "$CAPI_DIR/include/hyper.h" + else + echo "cbindgen failed:" + cat $WORK_DIR/cbindgen_stderr.err fi exit $bindgen_exit_code fi diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 03896eb3ef..3cbff3e36d 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -3,8 +3,7 @@ * Generated by gen_header.sh. Do not edit directly. */ -#ifndef _HYPER_H -#define _HYPER_H +#pragma once #include #include @@ -922,5 +921,3 @@ void hyper_waker_wake(struct hyper_waker *waker); #ifdef __cplusplus } // extern "C" #endif // __cplusplus - -#endif /* _HYPER_H */ From ad6393ce34081a744a6d32b567e3b9daecae4cfb Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 00:33:49 +0100 Subject: [PATCH 18/54] Remove hyper_serverconn wrapper --- src/ffi/server.rs | 13 ++----------- src/lib.rs | 6 ++++-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/ffi/server.rs b/src/ffi/server.rs index a4e3365853..315c2a851c 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -7,10 +7,9 @@ use crate::ffi::io::hyper_io; use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; -use crate::server::conn::{Connection, Http}; +use crate::server::conn::Http; pub struct hyper_serverconn_options(Http); -pub struct hyper_serverconn(Connection); pub struct hyper_service { service_fn: hyper_service_callback, userdata: UserDataPointer, @@ -184,7 +183,7 @@ ffi_fn! { let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; - let task = hyper_task::boxed(hyper_serverconn(serverconn_options.0.serve_connection(*io, *service))); + let task = hyper_task::boxed(serverconn_options.0.serve_connection(*io, *service)); Box::into_raw(task) } ?= ptr::null_mut() } @@ -216,11 +215,3 @@ impl crate::service::Service> for hyper_servic }) } } - -impl std::future::Future for hyper_serverconn { - type Output = crate::Result<()>; - - fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { - std::pin::Pin::new(&mut self.0).poll(cx) - } -} diff --git a/src/lib.rs b/src/lib.rs index f5f442d71e..91786b0e92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,8 +78,10 @@ pub mod rt; pub mod service; pub mod upgrade; -#[cfg(feature = "ffi")] -pub mod ffi; +cfg_feature! { + #![feature = "ffi"] + pub mod ffi; +} cfg_proto! { mod headers; From 282c9aee570c30111e11ecb4d7d4d6b37efbd155 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 00:34:44 +0100 Subject: [PATCH 19/54] Add documentation for ffi server types and functions --- capi/include/hyper.h | 174 ++++++++++++++++++++++++++++++++++++++++++- src/ffi/mod.rs | 1 + src/ffi/server.rs | 134 ++++++++++++++++++++++++++++++++- 3 files changed, 304 insertions(+), 5 deletions(-) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 3cbff3e36d..098ca59268 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -197,10 +197,19 @@ typedef struct hyper_request hyper_request; */ typedef struct hyper_response hyper_response; +/* + A channel on which to send back a response to complete a transaction for a service. + */ typedef struct hyper_response_channel hyper_response_channel; +/* + Configuration options for server connections. + */ typedef struct hyper_serverconn_options hyper_serverconn_options; +/* + A service that can serve a single server connection. + */ typedef struct hyper_service hyper_service; /* @@ -217,6 +226,21 @@ typedef int (*hyper_body_foreach_callback)(void*, const struct hyper_buf*); typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**); +/* + The main definition of a service. This callback will be invoked for each transaction on the + connection. + + The first argument contains the userdata registered with this service. + + The second argument contains the `hyper_request` that started this transaction. This request + is given to the callback which should free it when it is no longer needed (see + [crate::ffi::hyper_request_free]). + + The third argument contains a channel on which a single `hyper_response` must be sent in order + to conclude the transaction. This channel is given to the callback so the sending of the + response can be deferred (e.g. by passing it to a different thread, or waiting until other + async operations have completed). + */ typedef void (*hyper_service_callback)(void*, struct hyper_request*, struct hyper_response_channel*); typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*); @@ -413,65 +437,211 @@ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options * enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hyper_clientconn_options *opts, int enabled); +/* + Create a new HTTP serverconn options bound to the provided executor. + */ struct hyper_serverconn_options *hyper_serverconn_options_new(const struct hyper_executor *exec); +/* + Free a `hyper_serverconn_options*`. + */ void hyper_serverconn_options_free(struct hyper_serverconn_options *opts); +/* + Configure whether HTTP/1 is required. + + Default is `false` + */ enum hyper_code hyper_serverconn_options_http1_only(struct hyper_serverconn_options *opts, bool enabled); +/* + Set whether HTTP/1 connections should support half-closures. + + Clients can chose to shutdown their write-side while waiting for the server to respond. + Setting this to true will prevent closing the connection immediately if read detects an EOF + in the middle of a request. + + Default is `false` + */ enum hyper_code hyper_serverconn_options_http1_half_close(struct hyper_serverconn_options *opts, bool enabled); +/* + Enables or disables HTTP/1 keep-alive. + + Default is `true`. + */ enum hyper_code hyper_serverconn_options_http1_keep_alive(struct hyper_serverconn_options *opts, bool enabled); +/* + Set whether HTTP/1 connections will write header names as title case at the socket level. + + Note that this setting does not affect HTTP/2. + + Default is `false`. + */ enum hyper_code hyper_serverconn_options_http1_title_case_headers(struct hyper_serverconn_options *opts, bool enabled); +/* + Set whether to support preserving original header cases. + + Currently, this will record the original cases received, and store them in a private + extension on the Request. It will also look for and use such an extension in any provided + Response. + + Since the relevant extension is still private, there is no way to interact with the + original cases. The only effect this can have now is to forward the cases in a proxy-like + fashion. + + Note that this setting does not affect HTTP/2. + + Default is `false`. + */ enum hyper_code hyper_serverconn_options_http1_preserve_header_case(struct hyper_serverconn_options *opts, bool enabled); +/* + Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a + single buffer. + + Note that setting this to false may mean more copies of body data, but may also improve + performance when an IO transport doesn’t support vectored writes well, such as most TLS + implementations. + + Setting this to true will force hyper to use queued strategy which may eliminate + unnecessary cloning on some TLS backends. + + Default is to automatically guess which mode to use, this function overrides the huristic. + */ enum hyper_code hyper_serverconn_options_http1_writev(struct hyper_serverconn_options *opts, bool enabled); +/* + Set the maximum buffer size for the connection. Must be no lower `8192`. + + Default is a sensible value. + */ +enum hyper_code hyper_serverconn_options_http1_max_buf_size(struct hyper_serverconn_options *opts, + uintptr_t max_buf_size); + +/* + Configure whether HTTP/2 is required. + + Default is `false`. + */ enum hyper_code hyper_serverconn_options_http2_only(struct hyper_serverconn_options *opts, bool enabled); +/* + Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. + + Passing `0` instructs hyper to use a sensible default value. + */ enum hyper_code hyper_serverconn_options_http2_initial_stream_window_size(struct hyper_serverconn_options *opts, unsigned int window_size); +/* + Sets the max connection-level flow control for HTTP/2. + + Passing `0` instructs hyper to use a sensible default value. + */ enum hyper_code hyper_serverconn_options_http2_initial_connection_window_size(struct hyper_serverconn_options *opts, unsigned int window_size); +/* + Sets whether to use an adaptive flow control. + + Enabling this will override the limits set in http2_initial_stream_window_size and + http2_initial_connection_window_size. + + Default is `false`. + */ enum hyper_code hyper_serverconn_options_http2_adaptive_window(struct hyper_serverconn_options *opts, bool enabled); +/* + Sets the maximum frame size to use for HTTP/2. + + Passing `0` instructs hyper to use a sensible default value. + */ enum hyper_code hyper_serverconn_options_http2_max_frame_size(struct hyper_serverconn_options *opts, unsigned int frame_size); +/* + Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. + + Default is no limit (`std::u32::MAX`). Passing `0` will use this default. + */ enum hyper_code hyper_serverconn_options_http2_max_concurrent_streams(struct hyper_serverconn_options *opts, unsigned int max_streams); +/* + Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than + `u32::MAX`. + + Default is a sensible value. + */ enum hyper_code hyper_serverconn_options_http2_max_send_buf_size(struct hyper_serverconn_options *opts, uintptr_t max_buf_size); +/* + Enables the extended `CONNECT` protocol. + */ enum hyper_code hyper_serverconn_options_http2_enable_connect_protocol(struct hyper_serverconn_options *opts); -enum hyper_code hyper_serverconn_options_max_buf_size(struct hyper_serverconn_options *opts, - uintptr_t max_buf_size); +/* + Sets the max size of received header frames. + + Default is a sensible value. + */ +enum hyper_code hyper_serverconn_options_http2_max_header_list_size(struct hyper_serverconn_options *opts, + uint32_t max); +/* + Aggregates flushes to better support pipelined responses. + + Experimental, may have bugs. + + Default is `false`. + */ enum hyper_code hyper_serverconn_options_pipeline_flush(struct hyper_serverconn_options *opts, bool enabled); +/* + Create a service from a wrapped callback function. + */ struct hyper_service *hyper_service_new(hyper_service_callback service_fn); +/* + Register opaque userdata with the `hyper_service`. + + The service borrows the userdata until the service is driven on a connection and the + associated task completes. + */ void hyper_service_set_userdata(struct hyper_service *service, void *userdata); +/* + Associate a `hyper_io*` and a `hyper_service*` togther with the options specified in a + `hyper_serverconn_options*`. + + Returns a `hyper_task*` which must be given to an executor to make progress. + + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. + */ struct hyper_task *hyper_serve_connection(struct hyper_serverconn_options *serverconn_options, struct hyper_io *io, struct hyper_service *service); +/* + Sends a `hyper_response*` back to the client. This function consumes the response and the + channel. + + See [hyper_service_callback] for details. + */ void hyper_response_channel_send(struct hyper_response_channel *channel, struct hyper_response *response); diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 39cbe7dec7..48e61460f9 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -58,6 +58,7 @@ pub use self::client::*; pub use self::error::*; pub use self::http_types::*; pub use self::io::*; +pub use self::server::*; pub use self::task::*; /// Return in iter functions to continue iterating. diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 315c2a851c..99a649b423 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -9,16 +9,35 @@ use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; use crate::server::conn::Http; +/// Configuration options for server connections. pub struct hyper_serverconn_options(Http); + +/// A service that can serve a single server connection. pub struct hyper_service { service_fn: hyper_service_callback, userdata: UserDataPointer, } + +/// A channel on which to send back a response to complete a transaction for a service. pub struct hyper_response_channel(futures_channel::oneshot::Sender>); -type hyper_service_callback = extern "C" fn(*mut c_void, *mut hyper_request, *mut hyper_response_channel); +/// The main definition of a service. This callback will be invoked for each transaction on the +/// connection. +/// +/// The first argument contains the userdata registered with this service. +/// +/// The second argument contains the `hyper_request` that started this transaction. This request +/// is given to the callback which should free it when it is no longer needed (see +/// [crate::ffi::hyper_request_free]). +/// +/// The third argument contains a channel on which a single `hyper_response` must be sent in order +/// to conclude the transaction. This channel is given to the callback so the sending of the +/// response can be deferred (e.g. by passing it to a different thread, or waiting until other +/// async operations have completed). +pub type hyper_service_callback = extern "C" fn(*mut c_void, *mut hyper_request, *mut hyper_response_channel); ffi_fn! { + /// Create a new HTTP serverconn options bound to the provided executor. fn hyper_serverconn_options_new(exec: *const hyper_executor) -> *mut hyper_serverconn_options { let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; let weak_exec = hyper_executor::downgrade(&exec); @@ -29,12 +48,16 @@ ffi_fn! { } ffi_fn! { + /// Free a `hyper_serverconn_options*`. fn hyper_serverconn_options_free(opts: *mut hyper_serverconn_options) { let _ = non_null! { Box::from_raw(opts) ?= () }; } } ffi_fn! { + /// Configure whether HTTP/1 is required. + /// + /// Default is `false` fn hyper_serverconn_options_http1_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_only(enabled); @@ -43,6 +66,13 @@ ffi_fn! { } ffi_fn! { + /// Set whether HTTP/1 connections should support half-closures. + /// + /// Clients can chose to shutdown their write-side while waiting for the server to respond. + /// Setting this to true will prevent closing the connection immediately if read detects an EOF + /// in the middle of a request. + /// + /// Default is `false` fn hyper_serverconn_options_http1_half_close(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_half_close(enabled); @@ -51,6 +81,9 @@ ffi_fn! { } ffi_fn! { + /// Enables or disables HTTP/1 keep-alive. + /// + /// Default is `true`. fn hyper_serverconn_options_http1_keep_alive(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_keep_alive(enabled); @@ -59,6 +92,11 @@ ffi_fn! { } ffi_fn! { + /// Set whether HTTP/1 connections will write header names as title case at the socket level. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is `false`. fn hyper_serverconn_options_http1_title_case_headers(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_title_case_headers(enabled); @@ -67,6 +105,19 @@ ffi_fn! { } ffi_fn! { + /// Set whether to support preserving original header cases. + /// + /// Currently, this will record the original cases received, and store them in a private + /// extension on the Request. It will also look for and use such an extension in any provided + /// Response. + /// + /// Since the relevant extension is still private, there is no way to interact with the + /// original cases. The only effect this can have now is to forward the cases in a proxy-like + /// fashion. + /// + /// Note that this setting does not affect HTTP/2. + /// + /// Default is `false`. fn hyper_serverconn_options_http1_preserve_header_case(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_preserve_header_case(enabled); @@ -74,7 +125,20 @@ ffi_fn! { } } +// TODO: http1_header_read_timeout + ffi_fn! { + /// Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a + /// single buffer. + /// + /// Note that setting this to false may mean more copies of body data, but may also improve + /// performance when an IO transport doesn’t support vectored writes well, such as most TLS + /// implementations. + /// + /// Setting this to true will force hyper to use queued strategy which may eliminate + /// unnecessary cloning on some TLS backends. + /// + /// Default is to automatically guess which mode to use, this function overrides the huristic. fn hyper_serverconn_options_http1_writev(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_writev(enabled); @@ -83,6 +147,20 @@ ffi_fn! { } ffi_fn! { + /// Set the maximum buffer size for the connection. Must be no lower `8192`. + /// + /// Default is a sensible value. + fn hyper_serverconn_options_http1_max_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.max_buf_size(max_buf_size); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + /// Configure whether HTTP/2 is required. + /// + /// Default is `false`. fn hyper_serverconn_options_http2_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_only(enabled); @@ -91,6 +169,9 @@ ffi_fn! { } ffi_fn! { + /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. + /// + /// Passing `0` instructs hyper to use a sensible default value. fn hyper_serverconn_options_http2_initial_stream_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_initial_stream_window_size(if window_size == 0 { None } else { Some(window_size) }); @@ -99,6 +180,9 @@ ffi_fn! { } ffi_fn! { + /// Sets the max connection-level flow control for HTTP/2. + /// + /// Passing `0` instructs hyper to use a sensible default value. fn hyper_serverconn_options_http2_initial_connection_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_initial_connection_window_size(if window_size == 0 { None } else { Some(window_size) }); @@ -107,6 +191,12 @@ ffi_fn! { } ffi_fn! { + /// Sets whether to use an adaptive flow control. + /// + /// Enabling this will override the limits set in http2_initial_stream_window_size and + /// http2_initial_connection_window_size. + /// + /// Default is `false`. fn hyper_serverconn_options_http2_adaptive_window(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_adaptive_window(enabled); @@ -115,6 +205,9 @@ ffi_fn! { } ffi_fn! { + /// Sets the maximum frame size to use for HTTP/2. + /// + /// Passing `0` instructs hyper to use a sensible default value. fn hyper_serverconn_options_http2_max_frame_size(opts: *mut hyper_serverconn_options, frame_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_frame_size(if frame_size == 0 { None } else { Some(frame_size) }); @@ -123,6 +216,9 @@ ffi_fn! { } ffi_fn! { + /// Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. + /// + /// Default is no limit (`std::u32::MAX`). Passing `0` will use this default. fn hyper_serverconn_options_http2_max_concurrent_streams(opts: *mut hyper_serverconn_options, max_streams: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_concurrent_streams(if max_streams == 0 { None } else { Some(max_streams) }); @@ -130,7 +226,14 @@ ffi_fn! { } } +// TODO: http2_keep_alive_interval +// TODO: http2_keep_alive_timeout + ffi_fn! { + /// Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than + /// `u32::MAX`. + /// + /// Default is a sensible value. fn hyper_serverconn_options_http2_max_send_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_send_buf_size(max_buf_size); @@ -139,6 +242,7 @@ ffi_fn! { } ffi_fn! { + /// Enables the extended `CONNECT` protocol. fn hyper_serverconn_options_http2_enable_connect_protocol(opts: *mut hyper_serverconn_options) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_enable_connect_protocol(); @@ -147,14 +251,22 @@ ffi_fn! { } ffi_fn! { - fn hyper_serverconn_options_max_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + /// Sets the max size of received header frames. + /// + /// Default is a sensible value. + fn hyper_serverconn_options_http2_max_header_list_size(opts: *mut hyper_serverconn_options, max: u32) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.max_buf_size(max_buf_size); + opts.0.http2_max_header_list_size(max); hyper_code::HYPERE_OK } } ffi_fn! { + /// Aggregates flushes to better support pipelined responses. + /// + /// Experimental, may have bugs. + /// + /// Default is `false`. fn hyper_serverconn_options_pipeline_flush(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.pipeline_flush(enabled); @@ -163,6 +275,7 @@ ffi_fn! { } ffi_fn! { + /// Create a service from a wrapped callback function. fn hyper_service_new(service_fn: hyper_service_callback) -> *mut hyper_service { Box::into_raw(Box::new(hyper_service { service_fn: service_fn, @@ -172,6 +285,10 @@ ffi_fn! { } ffi_fn! { + /// Register opaque userdata with the `hyper_service`. + /// + /// The service borrows the userdata until the service is driven on a connection and the + /// associated task completes. fn hyper_service_set_userdata(service: *mut hyper_service, userdata: *mut c_void){ let s = non_null!{ &mut *service ?= () }; s.userdata = UserDataPointer(userdata); @@ -179,6 +296,13 @@ ffi_fn! { } ffi_fn! { + /// Associate a `hyper_io*` and a `hyper_service*` togther with the options specified in a + /// `hyper_serverconn_options*`. + /// + /// Returns a `hyper_task*` which must be given to an executor to make progress. + /// + /// This function consumes the IO and Service objects and thus they should not be accessed + /// after this function is called. fn hyper_serve_connection(serverconn_options: *mut hyper_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; @@ -189,6 +313,10 @@ ffi_fn! { } ffi_fn! { + /// Sends a `hyper_response*` back to the client. This function consumes the response and the + /// channel. + /// + /// See [hyper_service_callback] for details. fn hyper_response_channel_send(channel: *mut hyper_response_channel, response: *mut hyper_response) { let channel = non_null! { Box::from_raw(channel) ?= () }; let response = non_null! { Box::from_raw(response) ?= () }; From 4000e6884543ba4ac2a37a5ba9b8272cf8776e1c Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 01:08:41 +0100 Subject: [PATCH 20/54] Linting the C code * Fix some compiler issues (signedness and pointer sizing) * Include What You Use * Sort the includes --- capi/examples/Makefile | 2 +- capi/examples/client.c | 27 ++++++++++++++++----------- capi/examples/server.c | 18 ++++++++---------- capi/examples/upload.c | 27 ++++++++++++++++----------- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/capi/examples/Makefile b/capi/examples/Makefile index aea8963e22..66cc5fba76 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -2,7 +2,7 @@ # Build the example client # -RPATH=$(PWD)/../../target/debug +RPATH=$(PWD)/../../target/x86_64-unknown-linux-gnu/release HYPER_CFLAGS += -I../include -ggdb3 HYPER_LDFLAGS += -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lhyper diff --git a/capi/examples/client.c b/capi/examples/client.c index 57a3e7b6c7..a8f35a7431 100644 --- a/capi/examples/client.c +++ b/capi/examples/client.c @@ -1,15 +1,15 @@ -#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include #include "hyper.h" @@ -135,7 +135,12 @@ typedef enum { EXAMPLE_HANDSHAKE, EXAMPLE_SEND, EXAMPLE_RESP_BODY -} example_id; +} example_state; + +typedef union example_id { + void* ptr; + example_state state; +} example_userdata; #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) @@ -198,7 +203,7 @@ int main(int argc, char *argv[]) { if (!task) { break; } - switch ((example_id) hyper_task_userdata(task)) { + switch (((example_userdata)hyper_task_userdata(task)).state) { case EXAMPLE_HANDSHAKE: ; if (hyper_task_type(task) == HYPER_TASK_ERROR) { @@ -332,7 +337,7 @@ int main(int argc, char *argv[]) { if (err) { printf("error code: %d\n", hyper_error_code(err)); // grab the error details - char errbuf [256]; + unsigned char errbuf [256]; size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); printf("details: %.*s\n", (int) errlen, errbuf); diff --git a/capi/examples/server.c b/capi/examples/server.c index ccbab32fe9..57145fefa2 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -1,18 +1,16 @@ -#include -#include -#include -#include #include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include #include -#include -#include #include -#include -#include +#include #include "hyper.h" diff --git a/capi/examples/upload.c b/capi/examples/upload.c index 4bf44e6513..926152a51c 100644 --- a/capi/examples/upload.c +++ b/capi/examples/upload.c @@ -1,15 +1,15 @@ -#include +#include +#include +#include +#include +#include #include +#include +#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include #include "hyper.h" @@ -114,7 +114,7 @@ static int connect_to(const char *host, const char *port) { struct upload_body { int fd; - char *buf; + unsigned char *buf; size_t len; }; @@ -160,7 +160,12 @@ typedef enum { EXAMPLE_HANDSHAKE, EXAMPLE_SEND, EXAMPLE_RESP_BODY -} example_id; +} example_state; + +typedef union example_id { + void* ptr; + example_state state; +} example_userdata; #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) @@ -243,7 +248,7 @@ int main(int argc, char *argv[]) { } hyper_task_return_type task_type = hyper_task_type(task); - switch ((example_id) hyper_task_userdata(task)) { + switch (((example_userdata)hyper_task_userdata(task)).state) { case EXAMPLE_HANDSHAKE: ; if (task_type == HYPER_TASK_ERROR) { From 0b2d900f7bbe8e150bef7b002a11d11241920862 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 19:18:15 +0100 Subject: [PATCH 21/54] Split up HTTP/1 and HTTP/2 options into separate structs --- capi/examples/server.c | 12 ++-- capi/include/hyper.h | 124 +++++++++++++++++++------------- src/ffi/server.rs | 158 +++++++++++++++++++++++------------------ 3 files changed, 168 insertions(+), 126 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 57145fefa2..07d1c81915 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -305,8 +305,11 @@ int main(int argc, char *argv[]) { // We need an executor generally to poll futures const hyper_executor *exec = hyper_executor_new(); - // Configure the server HTTP stack - hyper_serverconn_options *opts = hyper_serverconn_options_new(exec); + // Configure the server HTTP/1 stack + hyper_http1_serverconn_options *http1_opts = hyper_http1_serverconn_options_new(); + + // Configure the server HTTP/2 stack + hyper_http2_serverconn_options *http2_opts = hyper_http2_serverconn_options_new(exec); // Might have an error hyper_error *err; @@ -400,7 +403,7 @@ int main(int argc, char *argv[]) { // Ask hyper to drive this connection hyper_service *service = hyper_service_new(server_callback); - hyper_task *serverconn = hyper_serve_connection(opts, io, service); + hyper_task *serverconn = hyper_serve_http1_connection(http1_opts, io, service); hyper_task_set_userdata(serverconn, conn); hyper_executor_push(exec, serverconn); } @@ -443,7 +446,8 @@ int main(int argc, char *argv[]) { } EXIT: - hyper_serverconn_options_free(opts); + hyper_http1_serverconn_options_free(http1_opts); + hyper_http2_serverconn_options_free(http2_opts); hyper_executor_free(exec); return 1; diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 098ca59268..4baf649b52 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -182,6 +182,16 @@ typedef struct hyper_executor hyper_executor; */ typedef struct hyper_headers hyper_headers; +/* + Configuration options for HTTP/1 server connections. + */ +typedef struct hyper_http1_serverconn_options hyper_http1_serverconn_options; + +/* + Configuration options for HTTP/2 server connections. + */ +typedef struct hyper_http2_serverconn_options hyper_http2_serverconn_options; + /* An IO object used to represent a socket or similar concept. */ @@ -202,11 +212,6 @@ typedef struct hyper_response hyper_response; */ typedef struct hyper_response_channel hyper_response_channel; -/* - Configuration options for server connections. - */ -typedef struct hyper_serverconn_options hyper_serverconn_options; - /* A service that can serve a single server connection. */ @@ -438,22 +443,14 @@ enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hy int enabled); /* - Create a new HTTP serverconn options bound to the provided executor. + Create a new HTTP/1 serverconn options object. */ -struct hyper_serverconn_options *hyper_serverconn_options_new(const struct hyper_executor *exec); +struct hyper_http1_serverconn_options *hyper_http1_serverconn_options_new(void); /* - Free a `hyper_serverconn_options*`. + Free a `hyper_http1_serverconn_options*`. */ -void hyper_serverconn_options_free(struct hyper_serverconn_options *opts); - -/* - Configure whether HTTP/1 is required. - - Default is `false` - */ -enum hyper_code hyper_serverconn_options_http1_only(struct hyper_serverconn_options *opts, - bool enabled); +void hyper_http1_serverconn_options_free(struct hyper_http1_serverconn_options *opts); /* Set whether HTTP/1 connections should support half-closures. @@ -464,7 +461,7 @@ enum hyper_code hyper_serverconn_options_http1_only(struct hyper_serverconn_opti Default is `false` */ -enum hyper_code hyper_serverconn_options_http1_half_close(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_half_close(struct hyper_http1_serverconn_options *opts, bool enabled); /* @@ -472,17 +469,15 @@ enum hyper_code hyper_serverconn_options_http1_half_close(struct hyper_servercon Default is `true`. */ -enum hyper_code hyper_serverconn_options_http1_keep_alive(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_keep_alive(struct hyper_http1_serverconn_options *opts, bool enabled); /* Set whether HTTP/1 connections will write header names as title case at the socket level. - Note that this setting does not affect HTTP/2. - Default is `false`. */ -enum hyper_code hyper_serverconn_options_http1_title_case_headers(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_title_case_headers(struct hyper_http1_serverconn_options *opts, bool enabled); /* @@ -496,11 +491,9 @@ enum hyper_code hyper_serverconn_options_http1_title_case_headers(struct hyper_s original cases. The only effect this can have now is to forward the cases in a proxy-like fashion. - Note that this setting does not affect HTTP/2. - Default is `false`. */ -enum hyper_code hyper_serverconn_options_http1_preserve_header_case(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_preserve_header_case(struct hyper_http1_serverconn_options *opts, bool enabled); /* @@ -516,31 +509,43 @@ enum hyper_code hyper_serverconn_options_http1_preserve_header_case(struct hyper Default is to automatically guess which mode to use, this function overrides the huristic. */ -enum hyper_code hyper_serverconn_options_http1_writev(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_writev(struct hyper_http1_serverconn_options *opts, bool enabled); /* - Set the maximum buffer size for the connection. Must be no lower `8192`. + Set the maximum buffer size for the HTTP/1 connection. Must be no lower `8192`. Default is a sensible value. */ -enum hyper_code hyper_serverconn_options_http1_max_buf_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http1_serverconn_options_max_buf_size(struct hyper_http1_serverconn_options *opts, uintptr_t max_buf_size); /* - Configure whether HTTP/2 is required. + Aggregates flushes to better support pipelined responses. + + Experimental, may have bugs. Default is `false`. */ -enum hyper_code hyper_serverconn_options_http2_only(struct hyper_serverconn_options *opts, - bool enabled); +enum hyper_code hyper_http1_serverconn_options_pipeline_flush(struct hyper_http1_serverconn_options *opts, + bool enabled); + +/* + Create a new HTTP/2 serverconn options object bound to the provided executor. + */ +struct hyper_http2_serverconn_options *hyper_http2_serverconn_options_new(struct hyper_executor *exec); + +/* + Free a `hyper_http2_serverconn_options*`. + */ +void hyper_http2_serverconn_options_free(struct hyper_http2_serverconn_options *opts); /* Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. Passing `0` instructs hyper to use a sensible default value. */ -enum hyper_code hyper_serverconn_options_http2_initial_stream_window_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_initial_stream_window_size(struct hyper_http2_serverconn_options *opts, unsigned int window_size); /* @@ -548,7 +553,7 @@ enum hyper_code hyper_serverconn_options_http2_initial_stream_window_size(struct Passing `0` instructs hyper to use a sensible default value. */ -enum hyper_code hyper_serverconn_options_http2_initial_connection_window_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_initial_connection_window_size(struct hyper_http2_serverconn_options *opts, unsigned int window_size); /* @@ -559,7 +564,7 @@ enum hyper_code hyper_serverconn_options_http2_initial_connection_window_size(st Default is `false`. */ -enum hyper_code hyper_serverconn_options_http2_adaptive_window(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_adaptive_window(struct hyper_http2_serverconn_options *opts, bool enabled); /* @@ -567,7 +572,7 @@ enum hyper_code hyper_serverconn_options_http2_adaptive_window(struct hyper_serv Passing `0` instructs hyper to use a sensible default value. */ -enum hyper_code hyper_serverconn_options_http2_max_frame_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_max_frame_size(struct hyper_http2_serverconn_options *opts, unsigned int frame_size); /* @@ -575,7 +580,7 @@ enum hyper_code hyper_serverconn_options_http2_max_frame_size(struct hyper_serve Default is no limit (`std::u32::MAX`). Passing `0` will use this default. */ -enum hyper_code hyper_serverconn_options_http2_max_concurrent_streams(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_max_concurrent_streams(struct hyper_http2_serverconn_options *opts, unsigned int max_streams); /* @@ -584,32 +589,22 @@ enum hyper_code hyper_serverconn_options_http2_max_concurrent_streams(struct hyp Default is a sensible value. */ -enum hyper_code hyper_serverconn_options_http2_max_send_buf_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_max_send_buf_size(struct hyper_http2_serverconn_options *opts, uintptr_t max_buf_size); /* Enables the extended `CONNECT` protocol. */ -enum hyper_code hyper_serverconn_options_http2_enable_connect_protocol(struct hyper_serverconn_options *opts); +enum hyper_code hyper_http2_serverconn_options_enable_connect_protocol(struct hyper_http2_serverconn_options *opts); /* Sets the max size of received header frames. Default is a sensible value. */ -enum hyper_code hyper_serverconn_options_http2_max_header_list_size(struct hyper_serverconn_options *opts, +enum hyper_code hyper_http2_serverconn_options_max_header_list_size(struct hyper_http2_serverconn_options *opts, uint32_t max); -/* - Aggregates flushes to better support pipelined responses. - - Experimental, may have bugs. - - Default is `false`. - */ -enum hyper_code hyper_serverconn_options_pipeline_flush(struct hyper_serverconn_options *opts, - bool enabled); - /* Create a service from a wrapped callback function. */ @@ -624,8 +619,35 @@ struct hyper_service *hyper_service_new(hyper_service_callback service_fn); void hyper_service_set_userdata(struct hyper_service *service, void *userdata); /* - Associate a `hyper_io*` and a `hyper_service*` togther with the options specified in a - `hyper_serverconn_options*`. + Serve the provided `hyper_service *` as an HTTP/1 endpoint over the provided `hyper_io *` + and configured as per the `hyper_http1_serverconn_options *`. + + Returns a `hyper_task*` which must be given to an executor to make progress. + + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. + */ +struct hyper_task *hyper_serve_http1_connection(struct hyper_http1_serverconn_options *serverconn_options, + struct hyper_io *io, + struct hyper_service *service); + +/* + Serve the provided `hyper_service *` as an HTTP/2 endpoint over the provided `hyper_io *` + and configured as per the `hyper_http2_serverconn_options *`. + + Returns a `hyper_task*` which must be given to an executor to make progress. + + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. + */ +struct hyper_task *hyper_serve_http2_connection(struct hyper_http2_serverconn_options *serverconn_options, + struct hyper_io *io, + struct hyper_service *service); + +/* + Serve the provided `hyper_service *` as either an HTTP/1 or HTTP/2 (depending on what the + client sends) endpoint over the provided `hyper_io *` and configured as per the + appropriate `hyper_httpX_serverconn_options *`. Returns a `hyper_task*` which must be given to an executor to make progress. diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 99a649b423..2e04ba2c4c 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -1,16 +1,20 @@ -use std::sync::Arc; +use std::ffi::{c_uint, c_void}; use std::ptr; -use std::ffi::{c_void, c_uint}; +use std::sync::Arc; -use crate::ffi::UserDataPointer; -use crate::ffi::io::hyper_io; use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; +use crate::ffi::io::hyper_io; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; -use crate::server::conn::Http; +use crate::ffi::UserDataPointer; +use crate::server::conn::http1; +use crate::server::conn::http2; -/// Configuration options for server connections. -pub struct hyper_serverconn_options(Http); +/// Configuration options for HTTP/1 server connections. +pub struct hyper_http1_serverconn_options(http1::Builder); + +/// Configuration options for HTTP/2 server connections. +pub struct hyper_http2_serverconn_options(http2::Builder); /// A service that can serve a single server connection. pub struct hyper_service { @@ -34,34 +38,22 @@ pub struct hyper_response_channel(futures_channel::oneshot::Sender *mut hyper_serverconn_options { - let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; - let weak_exec = hyper_executor::downgrade(&exec); - std::mem::forget(exec); // We've not incremented the strong count when we loaded - // `from_raw` - Box::into_raw(Box::new(hyper_serverconn_options(Http::new().with_executor(weak_exec)))) - } -} +// ===== impl http1_serverconn_options ===== ffi_fn! { - /// Free a `hyper_serverconn_options*`. - fn hyper_serverconn_options_free(opts: *mut hyper_serverconn_options) { - let _ = non_null! { Box::from_raw(opts) ?= () }; + /// Create a new HTTP/1 serverconn options object. + fn hyper_http1_serverconn_options_new() -> *mut hyper_http1_serverconn_options { + Box::into_raw(Box::new(hyper_http1_serverconn_options(http1::Builder::new()))) } } ffi_fn! { - /// Configure whether HTTP/1 is required. - /// - /// Default is `false` - fn hyper_serverconn_options_http1_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { - let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.http1_only(enabled); - hyper_code::HYPERE_OK + /// Free a `hyper_http1_serverconn_options*`. + fn hyper_http1_serverconn_options_free(opts: *mut hyper_http1_serverconn_options) { + let _ = non_null! { Box::from_raw(opts) ?= () }; } } @@ -73,7 +65,7 @@ ffi_fn! { /// in the middle of a request. /// /// Default is `false` - fn hyper_serverconn_options_http1_half_close(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_half_close(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_half_close(enabled); hyper_code::HYPERE_OK @@ -84,7 +76,7 @@ ffi_fn! { /// Enables or disables HTTP/1 keep-alive. /// /// Default is `true`. - fn hyper_serverconn_options_http1_keep_alive(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_keep_alive(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_keep_alive(enabled); hyper_code::HYPERE_OK @@ -94,10 +86,8 @@ ffi_fn! { ffi_fn! { /// Set whether HTTP/1 connections will write header names as title case at the socket level. /// - /// Note that this setting does not affect HTTP/2. - /// /// Default is `false`. - fn hyper_serverconn_options_http1_title_case_headers(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_title_case_headers(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_title_case_headers(enabled); hyper_code::HYPERE_OK @@ -115,10 +105,8 @@ ffi_fn! { /// original cases. The only effect this can have now is to forward the cases in a proxy-like /// fashion. /// - /// Note that this setting does not affect HTTP/2. - /// /// Default is `false`. - fn hyper_serverconn_options_http1_preserve_header_case(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_preserve_header_case(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_preserve_header_case(enabled); hyper_code::HYPERE_OK @@ -139,7 +127,7 @@ ffi_fn! { /// unnecessary cloning on some TLS backends. /// /// Default is to automatically guess which mode to use, this function overrides the huristic. - fn hyper_serverconn_options_http1_writev(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_writev(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_writev(enabled); hyper_code::HYPERE_OK @@ -147,10 +135,10 @@ ffi_fn! { } ffi_fn! { - /// Set the maximum buffer size for the connection. Must be no lower `8192`. + /// Set the maximum buffer size for the HTTP/1 connection. Must be no lower `8192`. /// /// Default is a sensible value. - fn hyper_serverconn_options_http1_max_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + fn hyper_http1_serverconn_options_max_buf_size(opts: *mut hyper_http1_serverconn_options, max_buf_size: usize) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.max_buf_size(max_buf_size); hyper_code::HYPERE_OK @@ -158,21 +146,43 @@ ffi_fn! { } ffi_fn! { - /// Configure whether HTTP/2 is required. + /// Aggregates flushes to better support pipelined responses. + /// + /// Experimental, may have bugs. /// /// Default is `false`. - fn hyper_serverconn_options_http2_only(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_pipeline_flush(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.http2_only(enabled); + opts.0.pipeline_flush(enabled); hyper_code::HYPERE_OK } } +// ===== impl hyper_http2_serverconn_options ===== + +ffi_fn! { + /// Create a new HTTP/2 serverconn options object bound to the provided executor. + fn hyper_http2_serverconn_options_new(exec: *mut hyper_executor) -> *mut hyper_http2_serverconn_options { + let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; + let weak = hyper_executor::downgrade(&exec); + std::mem::forget(exec); // We never incremented the strong count in this function so can't + // drop our Arc. + Box::into_raw(Box::new(hyper_http2_serverconn_options(http2::Builder::new(weak)))) + } +} + +ffi_fn! { + /// Free a `hyper_http2_serverconn_options*`. + fn hyper_http2_serverconn_options_free(opts: *mut hyper_http2_serverconn_options) { + let _ = non_null! { Box::from_raw(opts) ?= () }; + } +} + ffi_fn! { /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_serverconn_options_http2_initial_stream_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_initial_stream_window_size(opts: *mut hyper_http2_serverconn_options, window_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_initial_stream_window_size(if window_size == 0 { None } else { Some(window_size) }); hyper_code::HYPERE_OK @@ -183,7 +193,7 @@ ffi_fn! { /// Sets the max connection-level flow control for HTTP/2. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_serverconn_options_http2_initial_connection_window_size(opts: *mut hyper_serverconn_options, window_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_initial_connection_window_size(opts: *mut hyper_http2_serverconn_options, window_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_initial_connection_window_size(if window_size == 0 { None } else { Some(window_size) }); hyper_code::HYPERE_OK @@ -197,7 +207,7 @@ ffi_fn! { /// http2_initial_connection_window_size. /// /// Default is `false`. - fn hyper_serverconn_options_http2_adaptive_window(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http2_serverconn_options_adaptive_window(opts: *mut hyper_http2_serverconn_options, enabled: bool) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_adaptive_window(enabled); hyper_code::HYPERE_OK @@ -208,7 +218,7 @@ ffi_fn! { /// Sets the maximum frame size to use for HTTP/2. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_serverconn_options_http2_max_frame_size(opts: *mut hyper_serverconn_options, frame_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_max_frame_size(opts: *mut hyper_http2_serverconn_options, frame_size: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_frame_size(if frame_size == 0 { None } else { Some(frame_size) }); hyper_code::HYPERE_OK @@ -219,7 +229,7 @@ ffi_fn! { /// Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. /// /// Default is no limit (`std::u32::MAX`). Passing `0` will use this default. - fn hyper_serverconn_options_http2_max_concurrent_streams(opts: *mut hyper_serverconn_options, max_streams: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_max_concurrent_streams(opts: *mut hyper_http2_serverconn_options, max_streams: c_uint) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_concurrent_streams(if max_streams == 0 { None } else { Some(max_streams) }); hyper_code::HYPERE_OK @@ -234,7 +244,7 @@ ffi_fn! { /// `u32::MAX`. /// /// Default is a sensible value. - fn hyper_serverconn_options_http2_max_send_buf_size(opts: *mut hyper_serverconn_options, max_buf_size: usize) -> hyper_code { + fn hyper_http2_serverconn_options_max_send_buf_size(opts: *mut hyper_http2_serverconn_options, max_buf_size: usize) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_send_buf_size(max_buf_size); hyper_code::HYPERE_OK @@ -243,7 +253,7 @@ ffi_fn! { ffi_fn! { /// Enables the extended `CONNECT` protocol. - fn hyper_serverconn_options_http2_enable_connect_protocol(opts: *mut hyper_serverconn_options) -> hyper_code { + fn hyper_http2_serverconn_options_enable_connect_protocol(opts: *mut hyper_http2_serverconn_options) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_enable_connect_protocol(); hyper_code::HYPERE_OK @@ -254,26 +264,13 @@ ffi_fn! { /// Sets the max size of received header frames. /// /// Default is a sensible value. - fn hyper_serverconn_options_http2_max_header_list_size(opts: *mut hyper_serverconn_options, max: u32) -> hyper_code { + fn hyper_http2_serverconn_options_max_header_list_size(opts: *mut hyper_http2_serverconn_options, max: u32) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_header_list_size(max); hyper_code::HYPERE_OK } } -ffi_fn! { - /// Aggregates flushes to better support pipelined responses. - /// - /// Experimental, may have bugs. - /// - /// Default is `false`. - fn hyper_serverconn_options_pipeline_flush(opts: *mut hyper_serverconn_options, enabled: bool) -> hyper_code { - let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.pipeline_flush(enabled); - hyper_code::HYPERE_OK - } -} - ffi_fn! { /// Create a service from a wrapped callback function. fn hyper_service_new(service_fn: hyper_service_callback) -> *mut hyper_service { @@ -296,14 +293,31 @@ ffi_fn! { } ffi_fn! { - /// Associate a `hyper_io*` and a `hyper_service*` togther with the options specified in a - /// `hyper_serverconn_options*`. + /// Serve the provided `hyper_service *` as an HTTP/1 endpoint over the provided `hyper_io *` + /// and configured as per the `hyper_http1_serverconn_options *`. + /// + /// Returns a `hyper_task*` which must be given to an executor to make progress. + /// + /// This function consumes the IO and Service objects and thus they should not be accessed + /// after this function is called. + fn hyper_serve_http1_connection(serverconn_options: *mut hyper_http1_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { + let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; + let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; + let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; + let task = hyper_task::boxed(serverconn_options.0.serve_connection(*io, *service)); + Box::into_raw(task) + } ?= ptr::null_mut() +} + +ffi_fn! { + /// Serve the provided `hyper_service *` as an HTTP/2 endpoint over the provided `hyper_io *` + /// and configured as per the `hyper_http2_serverconn_options *`. /// /// Returns a `hyper_task*` which must be given to an executor to make progress. /// /// This function consumes the IO and Service objects and thus they should not be accessed /// after this function is called. - fn hyper_serve_connection(serverconn_options: *mut hyper_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { + fn hyper_serve_http2_connection(serverconn_options: *mut hyper_http2_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; @@ -327,19 +341,21 @@ ffi_fn! { impl crate::service::Service> for hyper_service { type Response = crate::Response; type Error = crate::Error; - type Future = std::pin::Pin> + Send>>; + type Future = std::pin::Pin< + Box> + Send>, + >; fn call(&mut self, req: crate::Request) -> Self::Future { let req_ptr = Box::into_raw(Box::new(hyper_request(req))); let (tx, rx) = futures_channel::oneshot::channel(); - let res_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); + let rsp_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); - (self.service_fn)(self.userdata.0, req_ptr, res_channel); + (self.service_fn)(self.userdata.0, req_ptr, rsp_channel); Box::pin(async move { - let res = rx.await.expect("Channel closed?"); - Ok(res.0) + let rsp = rx.await.expect("Channel closed?"); + Ok(rsp.0) }) } } From 56ae2b4586f817df7259c19b4fd344c6fb05d904 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 19:19:11 +0100 Subject: [PATCH 22/54] (Re-)add ability to serve a dual stack (H1/H2) --- capi/examples/server.c | 2 +- capi/include/hyper.h | 7 +-- src/ffi/server.rs | 97 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 07d1c81915..d4a05a483a 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -403,7 +403,7 @@ int main(int argc, char *argv[]) { // Ask hyper to drive this connection hyper_service *service = hyper_service_new(server_callback); - hyper_task *serverconn = hyper_serve_http1_connection(http1_opts, io, service); + hyper_task *serverconn = hyper_serve_httpX_connection(http1_opts, http2_opts, io, service); hyper_task_set_userdata(serverconn, conn); hyper_executor_push(exec, serverconn); } diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 4baf649b52..12e1f0d459 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -654,9 +654,10 @@ struct hyper_task *hyper_serve_http2_connection(struct hyper_http2_serverconn_op This function consumes the IO and Service objects and thus they should not be accessed after this function is called. */ -struct hyper_task *hyper_serve_connection(struct hyper_serverconn_options *serverconn_options, - struct hyper_io *io, - struct hyper_service *service); +struct hyper_task *hyper_serve_httpX_connection(struct hyper_http1_serverconn_options *http1_serverconn_options, + struct hyper_http2_serverconn_options *http2_serverconn_options, + struct hyper_io *io, + struct hyper_service *service); /* Sends a `hyper_response*` back to the client. This function consumes the response and the diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 2e04ba2c4c..b328459621 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -326,6 +326,37 @@ ffi_fn! { } ?= ptr::null_mut() } +ffi_fn! { + /// Serve the provided `hyper_service *` as either an HTTP/1 or HTTP/2 (depending on what the + /// client requests) endpoint over the provided `hyper_io *` and configured as per the + /// appropriate `hyper_httpX_serverconn_options *`. + /// + /// Returns a `hyper_task*` which must be given to an executor to make progress. + /// + /// This function consumes the IO and Service objects and thus they should not be accessed + /// after this function is called. + fn hyper_serve_httpX_connection( + http1_serverconn_options: *mut hyper_http1_serverconn_options, + http2_serverconn_options: *mut hyper_http2_serverconn_options, + io: *mut hyper_io, + service: *mut hyper_service + ) -> *mut hyper_task { + let http1_serverconn_options = non_null! { &*http1_serverconn_options ?= ptr::null_mut() }; + let http2_serverconn_options = non_null! { &*http2_serverconn_options ?= ptr::null_mut() }; + let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; + let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; + let task = hyper_task::boxed( + AutoConnection::H1( + Some(( + http1_serverconn_options.0.serve_connection(*io, *service), + http2_serverconn_options.0.clone() + )) + ) + ); + Box::into_raw(task) + } ?= ptr::null_mut() +} + ffi_fn! { /// Sends a `hyper_response*` back to the client. This function consumes the response and the /// channel. @@ -359,3 +390,69 @@ impl crate::service::Service> for hyper_servic }) } } + +enum AutoConnection { + // The internals are in an `Option` so they can be extracted during H1->H2 fallback. Otherwise + // this must always be `Some(h1, h2)` (and code is allowed to panic if that's not true). + H1( + Option<( + http1::Connection, + http2::Builder, + )>, + ), + H2(http2::Connection, hyper_service, WeakExec>), +} + +impl std::future::Future for AutoConnection { + type Output = crate::Result<()>; + + fn poll( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + let zelf = std::pin::Pin::into_inner(self); + let (h1, h2) = match zelf { + AutoConnection::H1(inner) => { + match ready!(std::pin::Pin::new(&mut inner.as_mut().unwrap().0).poll(cx)) { + Ok(_done) => return std::task::Poll::Ready(Ok(())), + Err(e) => { + let kind = e.kind(); + if matches!( + kind, + crate::error::Kind::Parse(crate::error::Parse::VersionH2) + ) { + // Fallback - switching variant has to happen outside the match block since + // `self` is borrowed. + // + // This breaks the invariant of the H1 variant, so we _must_ fix up `zelf` + // before returning from this function. + inner.take().unwrap() + } else { + // Some other error, pass upwards + return std::task::Poll::Ready(Err(e)); + } + } + } + } + AutoConnection::H2(h2) => match ready!(std::pin::Pin::new(h2).poll(cx)) { + Ok(_done) => return std::task::Poll::Ready(Ok(())), + Err(e) => return std::task::Poll::Ready(Err(e)), + }, + }; + + // We've not returned already (for pending, success or "other" errors) so we must be + // switching to H2 - rewind the IO, build an H2 connection, update `zelf` to the H2 variant + // then re-schedule this future for mainline processing. + let http1::Parts { + io, + read_buf, + service, + .. + } = h1.into_parts(); + let rewind = crate::common::io::Rewind::new_buffered(io, read_buf); + let h2 = h2.serve_connection(rewind, service); + *zelf = AutoConnection::H2(h2); + cx.waker().wake_by_ref(); + std::task::Poll::Pending + } +} From 425f72fc5f6e4d2822f16e5d8b8bf47cac441723 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 21 Oct 2022 21:10:47 +0100 Subject: [PATCH 23/54] Fix const-correctness in http2_serverconn --- capi/include/hyper.h | 4 ++-- src/ffi/server.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 12e1f0d459..08f2a080ed 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -533,7 +533,7 @@ enum hyper_code hyper_http1_serverconn_options_pipeline_flush(struct hyper_http1 /* Create a new HTTP/2 serverconn options object bound to the provided executor. */ -struct hyper_http2_serverconn_options *hyper_http2_serverconn_options_new(struct hyper_executor *exec); +struct hyper_http2_serverconn_options *hyper_http2_serverconn_options_new(const struct hyper_executor *exec); /* Free a `hyper_http2_serverconn_options*`. @@ -646,7 +646,7 @@ struct hyper_task *hyper_serve_http2_connection(struct hyper_http2_serverconn_op /* Serve the provided `hyper_service *` as either an HTTP/1 or HTTP/2 (depending on what the - client sends) endpoint over the provided `hyper_io *` and configured as per the + client requests) endpoint over the provided `hyper_io *` and configured as per the appropriate `hyper_httpX_serverconn_options *`. Returns a `hyper_task*` which must be given to an executor to make progress. diff --git a/src/ffi/server.rs b/src/ffi/server.rs index b328459621..320d1fcb51 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -162,7 +162,7 @@ ffi_fn! { ffi_fn! { /// Create a new HTTP/2 serverconn options object bound to the provided executor. - fn hyper_http2_serverconn_options_new(exec: *mut hyper_executor) -> *mut hyper_http2_serverconn_options { + fn hyper_http2_serverconn_options_new(exec: *const hyper_executor) -> *mut hyper_http2_serverconn_options { let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; let weak = hyper_executor::downgrade(&exec); std::mem::forget(exec); // We never incremented the strong count in this function so can't From da8ffb717ad44e101f33efab500ca84280edb401 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Sat, 22 Oct 2022 22:21:38 +0100 Subject: [PATCH 24/54] Remove dead code from server.c --- capi/examples/server.c | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index d4a05a483a..4bb9fb9851 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -22,19 +22,6 @@ typedef struct conn_data_s { hyper_waker *write_waker; } conn_data; -typedef enum task_state_type_e { - TASK_STATE_NONE, - TASK_STATE_SERVERCONN, - TASK_STATE_CLIENTCONN, -} task_state_type; - -typedef struct task_state_s { - task_state_type type; - union { - conn_data* conn; - } data; -} task_state; - static int listen_on(const char* host, const char* port) { struct addrinfo hints; struct addrinfo *result; @@ -225,13 +212,6 @@ static void free_conn_data(int epoll, conn_data *conn) { free(conn); } -typedef enum { - EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set - EXAMPLE_HANDSHAKE, - EXAMPLE_SEND, - EXAMPLE_RESP_BODY -} example_id; - static void server_callback(void* userdata, hyper_request* request, hyper_response_channel* channel) { unsigned char scheme[16]; size_t scheme_len = sizeof(scheme); From e3983b0cc54e8e94eee77d7e616a296606b8ae97 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Sat, 22 Oct 2022 22:22:17 +0100 Subject: [PATCH 25/54] Make `ffi` feature depend on `full` for now --- Cargo.toml | 2 +- src/ffi/mod.rs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 7139406122..d667a7ee58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ client = [] server = [] # C-API support (currently unstable (no semver)) -ffi = ["libc", "http-body-util"] +ffi = ["libc", "full", "http-body-util"] # internal features used in CI nightly = [] diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 48e61460f9..9a17d148dc 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -26,14 +26,6 @@ //! RUSTFLAGS="--cfg hyper_unstable_ffi" cargo rustc --features client,http1,http2,ffi --crate-type cdylib //! ``` -// We may eventually allow the FFI to be enabled without `client` or `http1`, -// that is why we don't auto enable them as `ffi = ["client", "http1"]` in -// the `Cargo.toml`. -// -// But for now, give a clear message that this compile error is expected. -#[cfg(not(all(feature = "client", feature = "server", feature = "http1")))] -compile_error!("The `ffi` feature currently requires the `client`, `server` and `http1` features."); - #[cfg(not(hyper_unstable_ffi))] compile_error!( "\ From a64b71e37907ffbf09a619cf06610538bd832d34 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Sat, 22 Oct 2022 23:37:24 +0100 Subject: [PATCH 26/54] Rust format the server code --- src/ffi/macros.rs | 8 +-- src/ffi/server.rs | 121 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 101 insertions(+), 28 deletions(-) diff --git a/src/ffi/macros.rs b/src/ffi/macros.rs index 5a727d57eb..5147522943 100644 --- a/src/ffi/macros.rs +++ b/src/ffi/macros.rs @@ -1,5 +1,5 @@ macro_rules! ffi_fn { - ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty $body:block ?= $default:expr) => { + ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)? ) -> $ret:ty $body:block ?= $default:expr) => { $(#[$doc])* #[no_mangle] pub extern fn $name($($arg: $arg_ty),*) -> $ret { @@ -14,18 +14,18 @@ macro_rules! ffi_fn { } }; - ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty $body:block) => { + ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)? ) -> $ret:ty $body:block) => { ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> $ret $body ?= { eprintln!("panic unwind caught, aborting"); std::process::abort() }); }; - ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) $body:block ?= $default:expr) => { + ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)? ) $body:block ?= $default:expr) => { ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> () $body ?= $default); }; - ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) $body:block) => { + ($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),* $(,)? ) $body:block) => { ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> () $body); }; } diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 320d1fcb51..aebe6648de 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -65,7 +65,10 @@ ffi_fn! { /// in the middle of a request. /// /// Default is `false` - fn hyper_http1_serverconn_options_half_close(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_half_close( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_half_close(enabled); hyper_code::HYPERE_OK @@ -76,7 +79,10 @@ ffi_fn! { /// Enables or disables HTTP/1 keep-alive. /// /// Default is `true`. - fn hyper_http1_serverconn_options_keep_alive(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_keep_alive( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_keep_alive(enabled); hyper_code::HYPERE_OK @@ -87,7 +93,10 @@ ffi_fn! { /// Set whether HTTP/1 connections will write header names as title case at the socket level. /// /// Default is `false`. - fn hyper_http1_serverconn_options_title_case_headers(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_title_case_headers( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_title_case_headers(enabled); hyper_code::HYPERE_OK @@ -106,7 +115,10 @@ ffi_fn! { /// fashion. /// /// Default is `false`. - fn hyper_http1_serverconn_options_preserve_header_case(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_preserve_header_case( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_preserve_header_case(enabled); hyper_code::HYPERE_OK @@ -127,7 +139,10 @@ ffi_fn! { /// unnecessary cloning on some TLS backends. /// /// Default is to automatically guess which mode to use, this function overrides the huristic. - fn hyper_http1_serverconn_options_writev(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_writev( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http1_writev(enabled); hyper_code::HYPERE_OK @@ -138,7 +153,10 @@ ffi_fn! { /// Set the maximum buffer size for the HTTP/1 connection. Must be no lower `8192`. /// /// Default is a sensible value. - fn hyper_http1_serverconn_options_max_buf_size(opts: *mut hyper_http1_serverconn_options, max_buf_size: usize) -> hyper_code { + fn hyper_http1_serverconn_options_max_buf_size( + opts: *mut hyper_http1_serverconn_options, + max_buf_size: usize, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.max_buf_size(max_buf_size); hyper_code::HYPERE_OK @@ -151,7 +169,10 @@ ffi_fn! { /// Experimental, may have bugs. /// /// Default is `false`. - fn hyper_http1_serverconn_options_pipeline_flush(opts: *mut hyper_http1_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http1_serverconn_options_pipeline_flush( + opts: *mut hyper_http1_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.pipeline_flush(enabled); hyper_code::HYPERE_OK @@ -162,12 +183,16 @@ ffi_fn! { ffi_fn! { /// Create a new HTTP/2 serverconn options object bound to the provided executor. - fn hyper_http2_serverconn_options_new(exec: *const hyper_executor) -> *mut hyper_http2_serverconn_options { + fn hyper_http2_serverconn_options_new( + exec: *const hyper_executor, + ) -> *mut hyper_http2_serverconn_options { let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; let weak = hyper_executor::downgrade(&exec); std::mem::forget(exec); // We never incremented the strong count in this function so can't // drop our Arc. - Box::into_raw(Box::new(hyper_http2_serverconn_options(http2::Builder::new(weak)))) + Box::into_raw(Box::new(hyper_http2_serverconn_options( + http2::Builder::new(weak), + ))) } } @@ -182,9 +207,17 @@ ffi_fn! { /// Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_http2_serverconn_options_initial_stream_window_size(opts: *mut hyper_http2_serverconn_options, window_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_initial_stream_window_size( + opts: *mut hyper_http2_serverconn_options, + window_size: c_uint, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.http2_initial_stream_window_size(if window_size == 0 { None } else { Some(window_size) }); + opts.0 + .http2_initial_stream_window_size(if window_size == 0 { + None + } else { + Some(window_size) + }); hyper_code::HYPERE_OK } } @@ -193,9 +226,17 @@ ffi_fn! { /// Sets the max connection-level flow control for HTTP/2. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_http2_serverconn_options_initial_connection_window_size(opts: *mut hyper_http2_serverconn_options, window_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_initial_connection_window_size( + opts: *mut hyper_http2_serverconn_options, + window_size: c_uint, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.http2_initial_connection_window_size(if window_size == 0 { None } else { Some(window_size) }); + opts.0 + .http2_initial_connection_window_size(if window_size == 0 { + None + } else { + Some(window_size) + }); hyper_code::HYPERE_OK } } @@ -207,7 +248,10 @@ ffi_fn! { /// http2_initial_connection_window_size. /// /// Default is `false`. - fn hyper_http2_serverconn_options_adaptive_window(opts: *mut hyper_http2_serverconn_options, enabled: bool) -> hyper_code { + fn hyper_http2_serverconn_options_adaptive_window( + opts: *mut hyper_http2_serverconn_options, + enabled: bool, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_adaptive_window(enabled); hyper_code::HYPERE_OK @@ -218,7 +262,10 @@ ffi_fn! { /// Sets the maximum frame size to use for HTTP/2. /// /// Passing `0` instructs hyper to use a sensible default value. - fn hyper_http2_serverconn_options_max_frame_size(opts: *mut hyper_http2_serverconn_options, frame_size: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_max_frame_size( + opts: *mut hyper_http2_serverconn_options, + frame_size: c_uint, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_frame_size(if frame_size == 0 { None } else { Some(frame_size) }); hyper_code::HYPERE_OK @@ -229,9 +276,16 @@ ffi_fn! { /// Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. /// /// Default is no limit (`std::u32::MAX`). Passing `0` will use this default. - fn hyper_http2_serverconn_options_max_concurrent_streams(opts: *mut hyper_http2_serverconn_options, max_streams: c_uint) -> hyper_code { + fn hyper_http2_serverconn_options_max_concurrent_streams( + opts: *mut hyper_http2_serverconn_options, + max_streams: c_uint, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; - opts.0.http2_max_concurrent_streams(if max_streams == 0 { None } else { Some(max_streams) }); + opts.0.http2_max_concurrent_streams(if max_streams == 0 { + None + } else { + Some(max_streams) + }); hyper_code::HYPERE_OK } } @@ -244,7 +298,10 @@ ffi_fn! { /// `u32::MAX`. /// /// Default is a sensible value. - fn hyper_http2_serverconn_options_max_send_buf_size(opts: *mut hyper_http2_serverconn_options, max_buf_size: usize) -> hyper_code { + fn hyper_http2_serverconn_options_max_send_buf_size( + opts: *mut hyper_http2_serverconn_options, + max_buf_size: usize, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_send_buf_size(max_buf_size); hyper_code::HYPERE_OK @@ -253,7 +310,9 @@ ffi_fn! { ffi_fn! { /// Enables the extended `CONNECT` protocol. - fn hyper_http2_serverconn_options_enable_connect_protocol(opts: *mut hyper_http2_serverconn_options) -> hyper_code { + fn hyper_http2_serverconn_options_enable_connect_protocol( + opts: *mut hyper_http2_serverconn_options, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_enable_connect_protocol(); hyper_code::HYPERE_OK @@ -264,7 +323,10 @@ ffi_fn! { /// Sets the max size of received header frames. /// /// Default is a sensible value. - fn hyper_http2_serverconn_options_max_header_list_size(opts: *mut hyper_http2_serverconn_options, max: u32) -> hyper_code { + fn hyper_http2_serverconn_options_max_header_list_size( + opts: *mut hyper_http2_serverconn_options, + max: u32, + ) -> hyper_code { let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; opts.0.http2_max_header_list_size(max); hyper_code::HYPERE_OK @@ -300,7 +362,11 @@ ffi_fn! { /// /// This function consumes the IO and Service objects and thus they should not be accessed /// after this function is called. - fn hyper_serve_http1_connection(serverconn_options: *mut hyper_http1_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { + fn hyper_serve_http1_connection( + serverconn_options: *mut hyper_http1_serverconn_options, + io: *mut hyper_io, + service: *mut hyper_service, + ) -> *mut hyper_task { let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; @@ -317,7 +383,11 @@ ffi_fn! { /// /// This function consumes the IO and Service objects and thus they should not be accessed /// after this function is called. - fn hyper_serve_http2_connection(serverconn_options: *mut hyper_http2_serverconn_options, io: *mut hyper_io, service: *mut hyper_service) -> *mut hyper_task { + fn hyper_serve_http2_connection( + serverconn_options: *mut hyper_http2_serverconn_options, + io: *mut hyper_io, + service: *mut hyper_service, + ) -> *mut hyper_task { let serverconn_options = non_null! { &*serverconn_options ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io) ?= ptr::null_mut() }; let service = non_null! { Box::from_raw(service) ?= ptr::null_mut() }; @@ -339,7 +409,7 @@ ffi_fn! { http1_serverconn_options: *mut hyper_http1_serverconn_options, http2_serverconn_options: *mut hyper_http2_serverconn_options, io: *mut hyper_io, - service: *mut hyper_service + service: *mut hyper_service, ) -> *mut hyper_task { let http1_serverconn_options = non_null! { &*http1_serverconn_options ?= ptr::null_mut() }; let http2_serverconn_options = non_null! { &*http2_serverconn_options ?= ptr::null_mut() }; @@ -362,7 +432,10 @@ ffi_fn! { /// channel. /// /// See [hyper_service_callback] for details. - fn hyper_response_channel_send(channel: *mut hyper_response_channel, response: *mut hyper_response) { + fn hyper_response_channel_send( + channel: *mut hyper_response_channel, + response: *mut hyper_response, + ) { let channel = non_null! { Box::from_raw(channel) ?= () }; let response = non_null! { Box::from_raw(response) ?= () }; let _ = channel.0.send(response); From 56105c371c122bfbf40f0cbe4454bc97fd10cf4e Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Sat, 22 Oct 2022 23:42:00 +0100 Subject: [PATCH 27/54] Format example C code --- capi/examples/.clang-format | 191 ++++++++++++++++++++++++++++++++++++ capi/examples/client.c | 27 ++--- capi/examples/server.c | 125 +++++++++++++---------- capi/examples/upload.c | 26 ++--- 4 files changed, 282 insertions(+), 87 deletions(-) create mode 100644 capi/examples/.clang-format diff --git a/capi/examples/.clang-format b/capi/examples/.clang-format new file mode 100644 index 0000000000..6f387775e1 --- /dev/null +++ b/capi/examples/.clang-format @@ -0,0 +1,191 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: BlockIndent +AlignArrayOfStructures: None +AlignConsecutiveMacros: None +AlignConsecutiveAssignments: None +AlignConsecutiveBitFields: None +AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Right +AlignOperands: Align +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortEnumsOnASingleLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +AttributeMacros: + - __capability +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: true +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +QualifierAlignment: Leave +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +PackConstructorInitializers: BinPack +BasedOnStyle: '' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +AllowAllConstructorInitializersOnNextLine: true +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + SortPriority: 0 + CaseSensitive: false + - Regex: '.*' + Priority: 1 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: '(Test)?$' +IncludeIsMainSourceRegex: '' +IndentAccessModifiers: false +IndentCaseLabels: false +IndentCaseBlocks: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentExternBlock: AfterExternBlock +IndentRequires: false +IndentWidth: 4 +IndentWrappedFunctionNames: false +InsertTrailingCommas: None +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LambdaBodyIndentation: Signature +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 1000 +PenaltyIndentedWhitespace: 0 +PointerAlignment: Right +PPIndentWidth: -1 +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + BeforeNonEmptyParentheses: false +SpaceAroundPointerQualifiers: Default +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +BitFieldColonSpacing: Both +Standard: Latest +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseCRLF: false +UseTab: Never +WhitespaceSensitiveMacros: + - STRINGIZE + - PP_STRINGIZE + - BOOST_PP_STRINGIZE + - NS_SWIFT_NAME + - CF_SWIFT_NAME +... + diff --git a/capi/examples/client.c b/capi/examples/client.c index a8f35a7431..7f1efa3edf 100644 --- a/capi/examples/client.c +++ b/capi/examples/client.c @@ -13,7 +13,6 @@ #include "hyper.h" - struct conn_data { int fd; hyper_waker *read_waker; @@ -112,12 +111,10 @@ static int connect_to(const char *host, const char *port) { return sfd; } -static int print_each_header(void *userdata, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len) { - printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value); +static int print_each_header( + void *userdata, const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len +) { + printf("%.*s: %.*s\n", (int)name_len, name, (int)value_len, value); return HYPER_ITER_CONTINUE; } @@ -138,8 +135,8 @@ typedef enum { } example_state; typedef union example_id { - void* ptr; - example_state state; + void *ptr; + example_state state; } example_userdata; #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) @@ -205,7 +202,6 @@ int main(int argc, char *argv[]) { } switch (((example_userdata)hyper_task_userdata(task)).state) { case EXAMPLE_HANDSHAKE: - ; if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("handshake error!\n"); err = hyper_task_value(task); @@ -230,7 +226,7 @@ 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)); // Send it! hyper_task *send = hyper_clientconn_send(client, req); @@ -243,7 +239,6 @@ int main(int argc, char *argv[]) { break; case EXAMPLE_SEND: - ; if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("send error!\n"); err = hyper_task_value(task); @@ -259,7 +254,7 @@ int main(int argc, char *argv[]) { const uint8_t *rp = hyper_response_reason_phrase(resp); size_t rp_len = hyper_response_reason_phrase_len(resp); - printf("\nResponse Status: %d %.*s\n", http_status, (int) rp_len, rp); + printf("\nResponse Status: %d %.*s\n", http_status, (int)rp_len, rp); hyper_headers *headers = hyper_response_headers(resp); hyper_headers_foreach(headers, print_each_header, NULL); @@ -275,7 +270,6 @@ int main(int argc, char *argv[]) { break; case EXAMPLE_RESP_BODY: - ; if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("body error!\n"); err = hyper_task_value(task); @@ -328,7 +322,6 @@ int main(int argc, char *argv[]) { hyper_waker_wake(conn->write_waker); conn->write_waker = NULL; } - } return 0; @@ -337,9 +330,9 @@ int main(int argc, char *argv[]) { if (err) { printf("error code: %d\n", hyper_error_code(err)); // grab the error details - unsigned char errbuf [256]; + unsigned char errbuf[256]; size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); - printf("details: %.*s\n", (int) errlen, errbuf); + printf("details: %.*s\n", (int)errlen, errbuf); // clean up the error hyper_error_free(err); diff --git a/capi/examples/server.c b/capi/examples/server.c index 4bb9fb9851..370431254c 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -22,7 +22,7 @@ typedef struct conn_data_s { hyper_waker *write_waker; } conn_data; -static int listen_on(const char* host, const char* port) { +static int listen_on(const char *host, const char *port) { struct addrinfo hints; struct addrinfo *result; @@ -70,7 +70,7 @@ static int listen_on(const char* host, const char* port) { freeaddrinfo(result); if (sock < 0) { - return -1; + return -1; } // Non-blocking for async @@ -104,12 +104,12 @@ static int register_signal_handler() { sigaddset(&mask, SIGQUIT); int signal_fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC); if (signal_fd < 0) { - perror("signalfd"); - return 1; + perror("signalfd"); + return 1; } if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { - perror("sigprocmask"); - return 1; + perror("sigprocmask"); + return 1; } return signal_fd; @@ -159,7 +159,7 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s return HYPER_IO_PENDING; } -static conn_data* create_conn_data(int epoll, int fd) { +static conn_data *create_conn_data(int epoll, int fd) { conn_data *conn = malloc(sizeof(conn_data)); // Add fd to epoll set, associated with this `conn` @@ -179,7 +179,7 @@ static conn_data* create_conn_data(int epoll, int fd) { return conn; } -static hyper_io* create_io(conn_data* conn) { +static hyper_io *create_io(conn_data *conn) { // Hookup the IO hyper_io *io = hyper_io_new(); hyper_io_set_userdata(io, (void *)conn); @@ -212,14 +212,24 @@ static void free_conn_data(int epoll, conn_data *conn) { free(conn); } -static void server_callback(void* userdata, hyper_request* request, hyper_response_channel* channel) { +static void server_callback( + void *userdata, hyper_request *request, hyper_response_channel *channel +) { unsigned char scheme[16]; size_t scheme_len = sizeof(scheme); unsigned char authority[16]; size_t authority_len = sizeof(authority); unsigned char path_and_query[16]; size_t path_and_query_len = sizeof(path_and_query); - if (hyper_request_uri_parts(request, scheme, &scheme_len, authority, &authority_len, path_and_query, &path_and_query_len) == 0) { + if (hyper_request_uri_parts( + request, + scheme, + &scheme_len, + authority, + &authority_len, + path_and_query, + &path_and_query_len + ) == 0) { printf("Request scheme was %.*s\n", (int)scheme_len, scheme); printf("Request authority was %.*s\n", (int)authority_len, authority); printf("Request path_and_query was %.*s\n", (int)path_and_query_len, path_and_query); @@ -233,7 +243,7 @@ static void server_callback(void* userdata, hyper_request* request, hyper_respon } hyper_request_free(request); - hyper_response* response = hyper_response_new(); + hyper_response *response = hyper_response_new(); hyper_response_set_status(response, 404); hyper_response_channel_send(channel, response); } @@ -251,7 +261,7 @@ int main(int argc, char *argv[]) { int signal_fd = register_signal_handler(); if (signal_fd < 0) { - return 1; + return 1; } // Use epoll cos' it's cool @@ -279,7 +289,6 @@ int main(int argc, char *argv[]) { return 1; } - printf("http handshake (hyper v%s) ...\n", hyper_version()); // We need an executor generally to poll futures @@ -296,7 +305,7 @@ int main(int argc, char *argv[]) { while (1) { while (1) { - hyper_task* task = hyper_executor_poll(exec); + hyper_task *task = hyper_executor_poll(exec); if (!task) { break; } @@ -305,15 +314,15 @@ int main(int argc, char *argv[]) { err = hyper_task_value(task); printf("error code: %d\n", hyper_error_code(err)); - uint8_t errbuf [256]; + uint8_t errbuf[256]; size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); - printf("details: %.*s\n", (int) errlen, errbuf); + printf("details: %.*s\n", (int)errlen, errbuf); // clean up the error hyper_error_free(err); // clean up the task - conn_data* conn = hyper_task_userdata(task); + conn_data *conn = hyper_task_userdata(task); if (conn) { free_conn_data(epoll, conn); } @@ -323,7 +332,7 @@ int main(int argc, char *argv[]) { } if (hyper_task_type(task) == HYPER_TASK_EMPTY) { - conn_data* conn = hyper_task_userdata(task); + conn_data *conn = hyper_task_userdata(task); if (conn) { printf("server connection complete\n"); free_conn_data(epoll, conn); @@ -353,39 +362,50 @@ int main(int argc, char *argv[]) { // Incoming connection(s) on listen_fd int new_fd; struct sockaddr_storage remote_addr_storage; - struct sockaddr* remote_addr = (struct sockaddr*)&remote_addr_storage; + struct sockaddr *remote_addr = (struct sockaddr *)&remote_addr_storage; socklen_t remote_addr_len = sizeof(struct sockaddr_storage); - while ((new_fd = accept(listen_fd, (struct sockaddr*)&remote_addr_storage, &remote_addr_len)) >= 0) { - char remote_host[128]; - char remote_port[8]; - if (getnameinfo(remote_addr, remote_addr_len, remote_host, sizeof(remote_host), remote_port, sizeof(remote_port), NI_NUMERICHOST | NI_NUMERICSERV) < 0) { - perror("getnameinfo"); - printf("New incoming connection from (unknown)\n"); - } else { - printf("New incoming connection from (%s:%s)\n", remote_host, remote_port); - } - - // Set non-blocking - if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { - perror("fcntl(O_NONBLOCK) (transport)\n"); - return 1; - } - - // Close handle on exec(ve) - if (fcntl(new_fd, F_SETFD, FD_CLOEXEC) != 0) { - perror("fcntl(FD_CLOEXEC) (transport)\n"); - return 1; - } - - // Wire up IO - conn_data *conn = create_conn_data(epoll, new_fd); - hyper_io* io = create_io(conn); - - // Ask hyper to drive this connection - hyper_service *service = hyper_service_new(server_callback); - hyper_task *serverconn = hyper_serve_httpX_connection(http1_opts, http2_opts, io, service); - hyper_task_set_userdata(serverconn, conn); - hyper_executor_push(exec, serverconn); + while ((new_fd = accept( + listen_fd, (struct sockaddr *)&remote_addr_storage, &remote_addr_len + )) >= 0) { + char remote_host[128]; + char remote_port[8]; + if (getnameinfo( + remote_addr, + remote_addr_len, + remote_host, + sizeof(remote_host), + remote_port, + sizeof(remote_port), + NI_NUMERICHOST | NI_NUMERICSERV + ) < 0) { + perror("getnameinfo"); + printf("New incoming connection from (unknown)\n"); + } else { + printf("New incoming connection from (%s:%s)\n", remote_host, remote_port); + } + + // Set non-blocking + if (fcntl(new_fd, F_SETFL, O_NONBLOCK) != 0) { + perror("fcntl(O_NONBLOCK) (transport)\n"); + return 1; + } + + // Close handle on exec(ve) + if (fcntl(new_fd, F_SETFD, FD_CLOEXEC) != 0) { + perror("fcntl(FD_CLOEXEC) (transport)\n"); + return 1; + } + + // Wire up IO + conn_data *conn = create_conn_data(epoll, new_fd); + hyper_io *io = create_io(conn); + + // Ask hyper to drive this connection + hyper_service *service = hyper_service_new(server_callback); + hyper_task *serverconn = + hyper_serve_httpX_connection(http1_opts, http2_opts, io, service); + hyper_task_set_userdata(serverconn, conn); + hyper_executor_push(exec, serverconn); } if (errno != EAGAIN) { @@ -393,7 +413,8 @@ int main(int argc, char *argv[]) { } } else if (events[n].data.ptr == &signal_fd) { struct signalfd_siginfo siginfo; - if (read(signal_fd, &siginfo, sizeof(struct signalfd_siginfo)) != sizeof(struct signalfd_siginfo)) { + if (read(signal_fd, &siginfo, sizeof(struct signalfd_siginfo)) != + sizeof(struct signalfd_siginfo)) { perror("read (signal_fd)"); return 1; } @@ -412,7 +433,7 @@ int main(int argc, char *argv[]) { } } else { // Existing transport socket, poke the wakers or close the socket - conn_data* conn = events[n].data.ptr; + conn_data *conn = events[n].data.ptr; if ((events[n].events & EPOLLIN) && conn->read_waker) { hyper_waker_wake(conn->read_waker); conn->read_waker = NULL; diff --git a/capi/examples/upload.c b/capi/examples/upload.c index 926152a51c..bdd82e8ea6 100644 --- a/capi/examples/upload.c +++ b/capi/examples/upload.c @@ -13,7 +13,6 @@ #include "hyper.h" - struct conn_data { int fd; hyper_waker *read_waker; @@ -118,10 +117,8 @@ struct upload_body { size_t len; }; -static int poll_req_upload(void *userdata, - hyper_context *ctx, - hyper_buf **chunk) { - struct upload_body* upload = userdata; +static int poll_req_upload(void *userdata, hyper_context *ctx, hyper_buf **chunk) { + struct upload_body *upload = userdata; ssize_t res = read(upload->fd, upload->buf, upload->len); if (res > 0) { @@ -140,12 +137,10 @@ static int poll_req_upload(void *userdata, return HYPER_POLL_ERROR; } -static int print_each_header(void *userdata, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len) { - printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value); +static int print_each_header( + void *userdata, const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len +) { + printf("%.*s: %.*s\n", (int)name_len, name, (int)value_len, value); return HYPER_ITER_CONTINUE; } @@ -163,8 +158,8 @@ typedef enum { } example_state; typedef union example_id { - void* ptr; - example_state state; + void *ptr; + example_state state; } example_userdata; #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) @@ -213,7 +208,6 @@ int main(int argc, char *argv[]) { conn->read_waker = NULL; conn->write_waker = NULL; - // Hookup the IO hyper_io *io = hyper_io_new(); hyper_io_set_userdata(io, (void *)conn); @@ -250,7 +244,6 @@ int main(int argc, char *argv[]) { switch (((example_userdata)hyper_task_userdata(task)).state) { case EXAMPLE_HANDSHAKE: - ; if (task_type == HYPER_TASK_ERROR) { printf("handshake error!\n"); return 1; @@ -300,7 +293,6 @@ int main(int argc, char *argv[]) { break; case EXAMPLE_SEND: - ; if (task_type == HYPER_TASK_ERROR) { printf("send error!\n"); return 1; @@ -331,7 +323,6 @@ int main(int argc, char *argv[]) { break; case EXAMPLE_RESP_BODY: - ; if (task_type == HYPER_TASK_ERROR) { printf("body error!\n"); return 1; @@ -400,6 +391,5 @@ int main(int argc, char *argv[]) { } } - return 0; } From 65fe1d5ee482c0a4ff3f28287cf037217f79adc5 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 26 Oct 2022 03:23:38 +0100 Subject: [PATCH 28/54] Genericize AutoConnection --- capi/examples/server.c | 4 ++-- src/ffi/server.rs | 28 +++++++++++++++++++--------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 370431254c..a86c8a6e3b 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -167,7 +167,7 @@ static conn_data *create_conn_data(int epoll, int fd) { transport_event.events = EPOLLIN; transport_event.data.ptr = conn; if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &transport_event) < 0) { - perror("epoll_ctl (transport)"); + perror("epoll_ctl (transport, add)"); free(conn); return NULL; } @@ -192,7 +192,7 @@ static hyper_io *create_io(conn_data *conn) { static void free_conn_data(int epoll, conn_data *conn) { // Disassociate with the epoll if (epoll_ctl(epoll, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { - perror("epoll_ctl (transport)"); + perror("epoll_ctl (transport, delete)"); } // Drop any saved-off wakers diff --git a/src/ffi/server.rs b/src/ffi/server.rs index aebe6648de..216669c89f 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -464,19 +464,30 @@ impl crate::service::Service> for hyper_servic } } -enum AutoConnection { +enum AutoConnection +where + Serv: crate::service::HttpService, +{ // The internals are in an `Option` so they can be extracted during H1->H2 fallback. Otherwise // this must always be `Some(h1, h2)` (and code is allowed to panic if that's not true). H1( Option<( - http1::Connection, - http2::Builder, + http1::Connection, + http2::Builder, )>, ), - H2(http2::Connection, hyper_service, WeakExec>), + H2(http2::Connection, Serv, Exec>), } -impl std::future::Future for AutoConnection { + +impl std::future::Future for AutoConnection +where + IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static, + Serv: crate::service::HttpService, + Exec: crate::common::exec::ConnStreamExec + Unpin, + http1::Connection: std::future::Future> + Unpin, + http2::Connection, Serv, Exec>: std::future::Future> + Unpin, +{ type Output = crate::Result<()>; fn poll( @@ -487,7 +498,7 @@ impl std::future::Future for AutoConnection { let (h1, h2) = match zelf { AutoConnection::H1(inner) => { match ready!(std::pin::Pin::new(&mut inner.as_mut().unwrap().0).poll(cx)) { - Ok(_done) => return std::task::Poll::Ready(Ok(())), + Ok(()) => return std::task::Poll::Ready(Ok(())), Err(e) => { let kind = e.kind(); if matches!( @@ -508,7 +519,7 @@ impl std::future::Future for AutoConnection { } } AutoConnection::H2(h2) => match ready!(std::pin::Pin::new(h2).poll(cx)) { - Ok(_done) => return std::task::Poll::Ready(Ok(())), + Ok(()) => return std::task::Poll::Ready(Ok(())), Err(e) => return std::task::Poll::Ready(Err(e)), }, }; @@ -525,7 +536,6 @@ impl std::future::Future for AutoConnection { let rewind = crate::common::io::Rewind::new_buffered(io, read_buf); let h2 = h2.serve_connection(rewind, service); *zelf = AutoConnection::H2(h2); - cx.waker().wake_by_ref(); - std::task::Poll::Pending + std::pin::Pin::new(zelf).poll(cx) } } From 83278e7d30e83cd9325c8bf0b0e61347a5b2cfdf Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 31 Oct 2022 21:20:34 +0000 Subject: [PATCH 29/54] Rename body::Recv to body::Incoming --- capi/examples/Makefile | 2 +- src/ffi/http_types.rs | 4 ++-- src/ffi/server.rs | 13 +++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/capi/examples/Makefile b/capi/examples/Makefile index 66cc5fba76..859b5b3f33 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -2,7 +2,7 @@ # Build the example client # -RPATH=$(PWD)/../../target/x86_64-unknown-linux-gnu/release +RPATH=$(PWD)/../../target/release HYPER_CFLAGS += -I../include -ggdb3 HYPER_LDFLAGS += -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lhyper diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 1d8885453e..36b225984b 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -312,7 +312,7 @@ ffi_fn! { /// /// It is safe to free the request even after taking ownership of its body. fn hyper_request_body(req: *mut hyper_request) -> *mut hyper_body { - let body = std::mem::replace(non_null!(&mut *req ?= std::ptr::null_mut()).0.body_mut(), crate::Recv::empty()); + let body = std::mem::replace(non_null!(&mut *req ?= std::ptr::null_mut()).0.body_mut(), IncomingBody::empty()); Box::into_raw(Box::new(hyper_body(body))) } ?= std::ptr::null_mut() } @@ -359,7 +359,7 @@ impl hyper_request { ffi_fn! { /// Construct a new HTTP 200 Ok response fn hyper_response_new() -> *mut hyper_response { - Box::into_raw(Box::new(hyper_response(Response::new(Recv::empty())))) + Box::into_raw(Box::new(hyper_response(Response::new(IncomingBody::empty())))) } ?= std::ptr::null_mut() } diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 216669c89f..c87d894610 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -2,6 +2,7 @@ use std::ffi::{c_uint, c_void}; use std::ptr; use std::sync::Arc; +use crate::body::Incoming as IncomingBody; use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::io::hyper_io; @@ -442,14 +443,14 @@ ffi_fn! { } } -impl crate::service::Service> for hyper_service { - type Response = crate::Response; +impl crate::service::Service> for hyper_service { + type Response = crate::Response; type Error = crate::Error; type Future = std::pin::Pin< Box> + Send>, >; - fn call(&mut self, req: crate::Request) -> Self::Future { + fn call(&mut self, req: crate::Request) -> Self::Future { let req_ptr = Box::into_raw(Box::new(hyper_request(req))); let (tx, rx) = futures_channel::oneshot::channel(); @@ -466,7 +467,7 @@ impl crate::service::Service> for hyper_servic enum AutoConnection where - Serv: crate::service::HttpService, + Serv: crate::service::HttpService, { // The internals are in an `Option` so they can be extracted during H1->H2 fallback. Otherwise // this must always be `Some(h1, h2)` (and code is allowed to panic if that's not true). @@ -483,8 +484,8 @@ where impl std::future::Future for AutoConnection where IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static, - Serv: crate::service::HttpService, - Exec: crate::common::exec::ConnStreamExec + Unpin, + Serv: crate::service::HttpService, + Exec: crate::common::exec::ConnStreamExec + Unpin, http1::Connection: std::future::Future> + Unpin, http2::Connection, Serv, Exec>: std::future::Future> + Unpin, { From 30565f296e69330a6b5a30a0e0be0d32ee0d99f3 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 2 Nov 2022 23:27:53 +0000 Subject: [PATCH 30/54] Add support for setting and firing timers --- src/ffi/task.rs | 122 ++++++++++++++++++++++++++++++++++++++++++++++-- src/rt.rs | 2 + 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 71e8e32fe5..5c9014a10e 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -49,6 +49,39 @@ pub struct hyper_executor { /// This is used to track when a future calls `wake` while we are within /// `hyper_executor::poll_next`. is_woken: Arc, + + /// The heap of programmed timers, these will be progressed at the start of + /// `hyper_executor_poll` + timers: Mutex>, +} + +#[derive(Clone, Debug)] +struct Timer { + waker: std::task::Waker, + wake_at: std::time::Instant, +} + +// Consistency with `Ord` requires us to report `Timer`s with the same `wake_at` as equal. +impl std::cmp::PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.wake_at.eq(&other.wake_at) + } +} +impl std::cmp::Eq for Timer {} + +// BinaryHeap is a max-heap and we want the top of the heap to be the nearest to popping timer +// so we want the "bigger" timer to have the earlier `wake_at` time. We achieve this by flipping +// the sides of the comparisons in `Ord` and implementing `PartialOrd` in terms of `Ord`. +impl std::cmp::PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl std::cmp::Ord for Timer { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Note flipped order + other.wake_at.cmp(&self.wake_at) + } } #[derive(Clone)] @@ -106,6 +139,7 @@ impl hyper_executor { driver: Mutex::new(FuturesUnordered::new()), spawn_queue: Mutex::new(Vec::new()), is_woken: Arc::new(ExecWaker(AtomicBool::new(false))), + timers: Mutex::new(std::collections::BinaryHeap::new()), }) } @@ -121,9 +155,12 @@ impl hyper_executor { } fn poll_next(&self) -> Option> { - // Drain the queue first. + // Move any new tasks to the runnable queue self.drain_queue(); + // Wake all popped timers + self.pop_timers(); + let waker = futures_util::task::waker_ref(&self.is_woken); let mut cx = Context::from_waker(&waker); @@ -132,13 +169,17 @@ impl hyper_executor { match poll { Poll::Ready(val) => return val, Poll::Pending => { - // Check if any of the pending tasks tried to spawn - // some new tasks. If so, drain into the driver and loop. + // Time has progressed while polling above, so fire any wakers for timers that + // have popped in that window. + self.pop_timers(); + + // Check if any of the pending tasks tried to spawn some new tasks. If so, + // drain into the driver and loop. if self.drain_queue() { continue; } - // If the driver called `wake` while we were polling, + // If the driver called `wake` while we were polling or any timers have popped, // we should poll again immediately! if self.is_woken.0.swap(false, Ordering::SeqCst) { continue; @@ -164,6 +205,19 @@ impl hyper_executor { true } + + fn pop_timers(&self) { + let mut heap = self.timers.lock().unwrap(); + let now = std::time::Instant::now(); + while let Some(timer) = heap.pop() { + if timer.wake_at < now { + timer.waker.wake_by_ref(); + } else { + heap.push(timer); + break; + } + } + } } impl futures_util::task::ArcWake for ExecWaker { @@ -198,6 +252,45 @@ where } } +struct TimerFuture { + exec: WeakExec, + wake: std::time::Instant, +} + +impl std::future::Future for TimerFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + let now = std::time::Instant::now(); + + if self.wake - now < std::time::Duration::from_millis(1) { + return Poll::Ready(()); + } + + if let Some(exec) = self.exec.0.upgrade() { + let mut heap = exec.timers.lock().unwrap(); + let t = Timer { + waker: cx.waker().clone(), + wake_at: self.wake, + }; + heap.push(t); + } + + return Poll::Pending; + } +} + +impl crate::rt::Timer for WeakExec { + fn sleep(&self, duration: std::time::Duration) -> Box { + self.sleep_until(std::time::Instant::now() + duration) + } + fn sleep_until(&self, instant: std::time::Instant) -> Box { + Box::new(TimerFuture { + exec: self.clone(), + wake: instant, + }) + } +} + ffi_fn! { /// Creates a new task executor. fn hyper_executor_new() -> *const hyper_executor { @@ -241,6 +334,27 @@ ffi_fn! { } ?= ptr::null_mut() } +ffi_fn! { + /// Returns the time until the executor will be able to make progress on tasks due to internal + /// timers popping. The executor should be polled soon after this time if not earlier due to + /// IO operations becoming available. + /// + /// Returns the time in milliseconds - a return value of -1 means there's no configured timers + /// and the executor doesn't need polling until there's IO work available. + fn hyper_executor_next_timer_pop(exec: *const hyper_executor) -> std::ffi::c_int { + let exec = non_null!(&*exec ?= -1); + match exec.timers.lock().unwrap().peek() { + Some(timer) => { + let duration = timer.wake_at - std::time::Instant::now(); + let micros = duration.as_micros() + 999; // Add "1000 - 1" to round up to next + // millisecond + (micros / 1000) as _ + } + None => -1 + } + } +} + // ===== impl hyper_task ===== impl hyper_task { diff --git a/src/rt.rs b/src/rt.rs index 9998980670..d9d74dc1ef 100644 --- a/src/rt.rs +++ b/src/rt.rs @@ -33,3 +33,5 @@ pub trait Timer { /// A future returned by a `Timer`. pub trait Sleep: Send + Sync + Unpin + Future {} + +impl Sleep for T where T: Send + Sync + Unpin + Future {} From c3101eaa5b4f963f0e0d0c5961ba38e233696fe4 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 2 Nov 2022 23:28:21 +0000 Subject: [PATCH 31/54] Expose timer-related APIs in server FFI --- capi/include/hyper.h | 40 ++++++++++++++++++++++++- src/ffi/server.rs | 69 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 102 insertions(+), 7 deletions(-) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 08f2a080ed..abcb2e04aa 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -445,7 +445,7 @@ enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hy /* Create a new HTTP/1 serverconn options object. */ -struct hyper_http1_serverconn_options *hyper_http1_serverconn_options_new(void); +struct hyper_http1_serverconn_options *hyper_http1_serverconn_options_new(const struct hyper_executor *exec); /* Free a `hyper_http1_serverconn_options*`. @@ -496,6 +496,15 @@ enum hyper_code hyper_http1_serverconn_options_title_case_headers(struct hyper_h enum hyper_code hyper_http1_serverconn_options_preserve_header_case(struct hyper_http1_serverconn_options *opts, bool enabled); +/* + Set a timeout for reading client request headers. If a client does not + transmit the entire header within this time, the connection is closed. + + Default is to have no timeout. + */ +enum hyper_code hyper_http1_serverconn_options_header_read_timeout(struct hyper_http1_serverconn_options *opts, + uint64_t millis); + /* Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a single buffer. @@ -583,6 +592,25 @@ enum hyper_code hyper_http2_serverconn_options_max_frame_size(struct hyper_http2 enum hyper_code hyper_http2_serverconn_options_max_concurrent_streams(struct hyper_http2_serverconn_options *opts, unsigned int max_streams); +/* + Sets an interval for HTTP/2 Ping frames should be sent to keep a connection alive. + + Default is to not use keepalive pings. Passing `0` will use this default. + */ +enum hyper_code hyper_http2_serverconn_options_keep_alive_interval(struct hyper_http2_serverconn_options *opts, + uint64_t interval_seconds); + +/* + Sets a timeout for receiving an acknowledgement of the keep-alive ping. + + If the ping is not acknowledged within the timeout, the connection will be closed. Does + nothing if `hyper_http2_serverconn_options_keep_alive_interval` is disabled. + + Default is 20 seconds. + */ +enum hyper_code hyper_http2_serverconn_options_keep_alive_timeout(struct hyper_http2_serverconn_options *opts, + uint64_t timeout_seconds); + /* Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than `u32::MAX`. @@ -1061,6 +1089,16 @@ enum hyper_code hyper_executor_push(const struct hyper_executor *exec, struct hy */ struct hyper_task *hyper_executor_poll(const struct hyper_executor *exec); +/* + Returns the time until the executor will be able to make progress on tasks due to internal + timers popping. The executor should be polled soon after this time if not earlier due to + IO operations becoming available. + + Returns the time in milliseconds - a return value of -1 means there's no configured timers + and the executor doesn't need polling until there's IO work available. + */ +int hyper_executor_next_timer_pop(const struct hyper_executor *exec); + /* Free a task. */ diff --git a/src/ffi/server.rs b/src/ffi/server.rs index c87d894610..b7b03bf37e 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -46,8 +46,18 @@ pub type hyper_service_callback = ffi_fn! { /// Create a new HTTP/1 serverconn options object. - fn hyper_http1_serverconn_options_new() -> *mut hyper_http1_serverconn_options { - Box::into_raw(Box::new(hyper_http1_serverconn_options(http1::Builder::new()))) + fn hyper_http1_serverconn_options_new( + exec: *const hyper_executor + ) -> *mut hyper_http1_serverconn_options { + let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; + let weak = hyper_executor::downgrade(&exec); + std::mem::forget(exec); // We never incremented the strong count in this function so can't + // drop our Arc. + let mut builder = http1::Builder::new(); + builder.timer(weak); + Box::into_raw(Box::new(hyper_http1_serverconn_options( + builder + ))) } } @@ -126,7 +136,20 @@ ffi_fn! { } } -// TODO: http1_header_read_timeout +ffi_fn! { + /// Set a timeout for reading client request headers. If a client does not + /// transmit the entire header within this time, the connection is closed. + /// + /// Default is to have no timeout. + fn hyper_http1_serverconn_options_header_read_timeout( + opts: *mut hyper_http1_serverconn_options, + millis: u64, + ) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http1_header_read_timeout(std::time::Duration::from_millis(millis)); + hyper_code::HYPERE_OK + } +} ffi_fn! { /// Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a @@ -191,8 +214,10 @@ ffi_fn! { let weak = hyper_executor::downgrade(&exec); std::mem::forget(exec); // We never incremented the strong count in this function so can't // drop our Arc. + let mut builder = http2::Builder::new(weak.clone()); + builder.timer(weak); Box::into_raw(Box::new(hyper_http2_serverconn_options( - http2::Builder::new(weak), + builder ))) } } @@ -291,8 +316,40 @@ ffi_fn! { } } -// TODO: http2_keep_alive_interval -// TODO: http2_keep_alive_timeout +ffi_fn! { + /// Sets an interval for HTTP/2 Ping frames should be sent to keep a connection alive. + /// + /// Default is to not use keepalive pings. Passing `0` will use this default. + fn hyper_http2_serverconn_options_keep_alive_interval( + opts: *mut hyper_http2_serverconn_options, + interval_seconds: u64, + ) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_keep_alive_interval(if interval_seconds == 0 { + None + } else { + Some(std::time::Duration::from_secs(interval_seconds)) + }); + hyper_code::HYPERE_OK + } +} + +ffi_fn! { + /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. + /// + /// If the ping is not acknowledged within the timeout, the connection will be closed. Does + /// nothing if `hyper_http2_serverconn_options_keep_alive_interval` is disabled. + /// + /// Default is 20 seconds. + fn hyper_http2_serverconn_options_keep_alive_timeout( + opts: *mut hyper_http2_serverconn_options, + timeout_seconds: u64, + ) -> hyper_code { + let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; + opts.0.http2_keep_alive_timeout(std::time::Duration::from_secs(timeout_seconds)); + hyper_code::HYPERE_OK + } +} ffi_fn! { /// Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than From 7f147622aa98f812d933e0b29703f9e514ead806 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 2 Nov 2022 23:28:59 +0000 Subject: [PATCH 32/54] Demonstrate timer processing in example --- capi/examples/server.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index a86c8a6e3b..d627910022 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -295,13 +295,13 @@ int main(int argc, char *argv[]) { const hyper_executor *exec = hyper_executor_new(); // Configure the server HTTP/1 stack - hyper_http1_serverconn_options *http1_opts = hyper_http1_serverconn_options_new(); + hyper_http1_serverconn_options *http1_opts = hyper_http1_serverconn_options_new(exec); + hyper_http1_serverconn_options_header_read_timeout(http1_opts, 1000 * 5); // 5 seconds // Configure the server HTTP/2 stack hyper_http2_serverconn_options *http2_opts = hyper_http2_serverconn_options_new(exec); - - // Might have an error - hyper_error *err; + hyper_http2_serverconn_options_keep_alive_interval(http2_opts, 5); // 5 seconds + hyper_http2_serverconn_options_keep_alive_timeout(http2_opts, 5); // 5 seconds while (1) { while (1) { @@ -312,7 +312,7 @@ int main(int argc, char *argv[]) { if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("handshake error!\n"); - err = hyper_task_value(task); + hyper_error* err = hyper_task_value(task); printf("error code: %d\n", hyper_error_code(err)); uint8_t errbuf[256]; size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); @@ -345,11 +345,13 @@ int main(int argc, char *argv[]) { } } - printf("Processed all tasks - polling for events\n"); + int timeout = hyper_executor_next_timer_pop(exec); + + printf("Processed all tasks - polling for events (max %dms)\n", timeout); struct epoll_event events[MAX_EVENTS]; - int nevents = epoll_wait(epoll, events, MAX_EVENTS, -1); + int nevents = epoll_wait(epoll, events, MAX_EVENTS, timeout); if (nevents < 0) { perror("epoll"); return 1; From 42b2518be7d06f0e30956c5a20cd3d7291556e35 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 3 Nov 2022 00:01:03 +0000 Subject: [PATCH 33/54] More efficient timer heap --- src/ffi/task.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 5c9014a10e..462c60fdb2 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -209,11 +209,11 @@ impl hyper_executor { fn pop_timers(&self) { let mut heap = self.timers.lock().unwrap(); let now = std::time::Instant::now(); - while let Some(timer) = heap.pop() { + while let Some(timer) = heap.peek_mut() { if timer.wake_at < now { + let timer = std::collections::binary_heap::PeekMut::pop(timer); timer.waker.wake_by_ref(); } else { - heap.push(timer); break; } } From 7c3ba18776bb0cda566720c8174223467864aca9 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 4 Nov 2022 14:41:57 +0000 Subject: [PATCH 34/54] Allow timer cancellation --- src/ffi/task.rs | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 462c60fdb2..92a239353a 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -7,6 +7,7 @@ use std::sync::{ Arc, Mutex, Weak, }; use std::task::{Context, Poll}; +use std::collections::binary_heap::PeekMut; use futures_util::stream::{FuturesUnordered, Stream}; use libc::c_int; @@ -59,6 +60,7 @@ pub struct hyper_executor { struct Timer { waker: std::task::Waker, wake_at: std::time::Instant, + is_alive: Arc, } // Consistency with `Ord` requires us to report `Timer`s with the same `wake_at` as equal. @@ -210,9 +212,12 @@ impl hyper_executor { let mut heap = self.timers.lock().unwrap(); let now = std::time::Instant::now(); while let Some(timer) = heap.peek_mut() { - if timer.wake_at < now { - let timer = std::collections::binary_heap::PeekMut::pop(timer); + let is_alive = timer.is_alive.load(Ordering::Relaxed); + if timer.wake_at < now && is_alive { + let timer = PeekMut::pop(timer); timer.waker.wake_by_ref(); + } else if !is_alive { + let _ = PeekMut::pop(timer); } else { break; } @@ -255,22 +260,33 @@ where struct TimerFuture { exec: WeakExec, wake: std::time::Instant, + is_alive: Option>, } impl std::future::Future for TimerFuture { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { let now = std::time::Instant::now(); + // Allow millisecond-accurate timers, since that's what the FFI code sees if self.wake - now < std::time::Duration::from_millis(1) { return Poll::Ready(()); } + if let Some(is_alive) = &self.is_alive { + is_alive.store(false, Ordering::Relaxed); + } + if let Some(exec) = self.exec.0.upgrade() { + let is_alive = Arc::new(AtomicBool::new(true)); + self.is_alive = Some(Arc::clone(&is_alive)); + let mut heap = exec.timers.lock().unwrap(); let t = Timer { waker: cx.waker().clone(), wake_at: self.wake, + is_alive: is_alive, }; heap.push(t); } @@ -279,6 +295,14 @@ impl std::future::Future for TimerFuture { } } +impl std::ops::Drop for TimerFuture { + fn drop(&mut self) { + if let Some(is_alive) = &self.is_alive { + is_alive.store(false, Ordering::Relaxed); + } + } +} + impl crate::rt::Timer for WeakExec { fn sleep(&self, duration: std::time::Duration) -> Box { self.sleep_until(std::time::Instant::now() + duration) @@ -287,6 +311,7 @@ impl crate::rt::Timer for WeakExec { Box::new(TimerFuture { exec: self.clone(), wake: instant, + is_alive: None, }) } } @@ -343,15 +368,19 @@ ffi_fn! { /// and the executor doesn't need polling until there's IO work available. fn hyper_executor_next_timer_pop(exec: *const hyper_executor) -> std::ffi::c_int { let exec = non_null!(&*exec ?= -1); - match exec.timers.lock().unwrap().peek() { - Some(timer) => { + let mut heap = exec.timers.lock().unwrap(); + while let Some(timer) = heap.peek_mut() { + let is_alive = timer.is_alive.load(Ordering::Relaxed); + if is_alive { let duration = timer.wake_at - std::time::Instant::now(); - let micros = duration.as_micros() + 999; // Add "1000 - 1" to round up to next - // millisecond - (micros / 1000) as _ + let micros = duration.as_micros(); + return (micros + 999 / 1000) as _ + } else { + PeekMut::pop(timer); } - None => -1 } + + return -1; } } From 750e0317504f0bcc4caf3fb35fd7dafc14cc6dc2 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 8 Nov 2022 22:31:08 +0000 Subject: [PATCH 35/54] Reduce number of timer entries in timer heap --- src/ffi/task.rs | 148 ++++++++++++++++++++++++++---------------------- 1 file changed, 81 insertions(+), 67 deletions(-) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 92a239353a..1d5998b3a8 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -56,36 +56,6 @@ pub struct hyper_executor { timers: Mutex>, } -#[derive(Clone, Debug)] -struct Timer { - waker: std::task::Waker, - wake_at: std::time::Instant, - is_alive: Arc, -} - -// Consistency with `Ord` requires us to report `Timer`s with the same `wake_at` as equal. -impl std::cmp::PartialEq for Timer { - fn eq(&self, other: &Self) -> bool { - self.wake_at.eq(&other.wake_at) - } -} -impl std::cmp::Eq for Timer {} - -// BinaryHeap is a max-heap and we want the top of the heap to be the nearest to popping timer -// so we want the "bigger" timer to have the earlier `wake_at` time. We achieve this by flipping -// the sides of the comparisons in `Ord` and implementing `PartialOrd` in terms of `Ord`. -impl std::cmp::PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl std::cmp::Ord for Timer { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - // Note flipped order - other.wake_at.cmp(&self.wake_at) - } -} - #[derive(Clone)] pub(crate) struct WeakExec(Weak); @@ -208,19 +178,20 @@ impl hyper_executor { true } + // Walk the timer heap waking active timers and discarding cancelled ones. fn pop_timers(&self) { let mut heap = self.timers.lock().unwrap(); let now = std::time::Instant::now(); while let Some(timer) = heap.peek_mut() { - let is_alive = timer.is_alive.load(Ordering::Relaxed); - if timer.wake_at < now && is_alive { - let timer = PeekMut::pop(timer); - timer.waker.wake_by_ref(); - } else if !is_alive { - let _ = PeekMut::pop(timer); - } else { - break; + if let Some(waker) = &mut timer.shared.lock().unwrap().waker { + if timer.wake_at < now { + waker.wake_by_ref(); + } else { + break; + } } + // This time was for the past so pop it now. + let _ = PeekMut::pop(timer); } } } @@ -257,10 +228,48 @@ where } } +// The entry in the timer heap for a programmed timer. The heap should expire the timer at +// `wake_at` and wake any waker it finds in the `shared` state. +struct Timer { + shared: Arc>, + wake_at: std::time::Instant, +} + +// Consistency with `Ord` requires us to report `Timer`s with the same `wake_at` as equal. +impl std::cmp::PartialEq for Timer { + fn eq(&self, other: &Self) -> bool { + self.wake_at.eq(&other.wake_at) + } +} +impl std::cmp::Eq for Timer {} + +// BinaryHeap is a max-heap and we want the top of the heap to be the nearest to popping timer +// so we want the "bigger" timer to have the earlier `wake_at` time. We achieve this by flipping +// the sides of the comparisons in `Ord` and implementing `PartialOrd` in terms of `Ord`. +impl std::cmp::PartialOrd for Timer { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl std::cmp::Ord for Timer { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Note flipped order + other.wake_at.cmp(&self.wake_at) + } +} + +// A future that completes at `wake_at`. struct TimerFuture { exec: WeakExec, - wake: std::time::Instant, - is_alive: Option>, + wake_at: std::time::Instant, + // This is None when the timer isn't programmed in the heap + shared: Option>>, +} + +// Shared between the timer future and the timer heap. If the heap expires a timer is should wake +// the waker if one is present (which indicates that the timer has not been cancelled). +struct TimerShared { + waker: Option, } impl std::future::Future for TimerFuture { @@ -269,26 +278,30 @@ impl std::future::Future for TimerFuture { fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { let now = std::time::Instant::now(); - // Allow millisecond-accurate timers, since that's what the FFI code sees - if self.wake - now < std::time::Duration::from_millis(1) { + if self.wake_at <= now { return Poll::Ready(()); } - if let Some(is_alive) = &self.is_alive { - is_alive.store(false, Ordering::Relaxed); - } - - if let Some(exec) = self.exec.0.upgrade() { - let is_alive = Arc::new(AtomicBool::new(true)); - self.is_alive = Some(Arc::clone(&is_alive)); - - let mut heap = exec.timers.lock().unwrap(); - let t = Timer { - waker: cx.waker().clone(), - wake_at: self.wake, - is_alive: is_alive, - }; - heap.push(t); + match &self.shared { + Some(shared) => { + // Timer was already programmed, update the waker + shared.lock().unwrap().waker = Some(cx.waker().clone()); + } + None => { + // Need to program the timer into the heap + if let Some(exec) = self.exec.0.upgrade() { + let mut heap = exec.timers.lock().unwrap(); + let shared = Arc::new(Mutex::new(TimerShared { + waker: Some(cx.waker().clone()), + })); + let t = Timer { + shared: Arc::clone(&shared), + wake_at: self.wake_at, + }; + heap.push(t); + self.shared = Some(shared); + } + } } return Poll::Pending; @@ -297,8 +310,8 @@ impl std::future::Future for TimerFuture { impl std::ops::Drop for TimerFuture { fn drop(&mut self) { - if let Some(is_alive) = &self.is_alive { - is_alive.store(false, Ordering::Relaxed); + if let Some(shared) = &self.shared { + let _ = shared.lock().unwrap().waker.take(); } } } @@ -310,8 +323,8 @@ impl crate::rt::Timer for WeakExec { fn sleep_until(&self, instant: std::time::Instant) -> Box { Box::new(TimerFuture { exec: self.clone(), - wake: instant, - is_alive: None, + wake_at: instant, + shared: None, }) } } @@ -361,8 +374,8 @@ ffi_fn! { ffi_fn! { /// Returns the time until the executor will be able to make progress on tasks due to internal - /// timers popping. The executor should be polled soon after this time if not earlier due to - /// IO operations becoming available. + /// timers popping. The executor should be polled soon after this time (if not earlier due to + /// IO operations becoming available). /// /// Returns the time in milliseconds - a return value of -1 means there's no configured timers /// and the executor doesn't need polling until there's IO work available. @@ -370,11 +383,12 @@ ffi_fn! { let exec = non_null!(&*exec ?= -1); let mut heap = exec.timers.lock().unwrap(); while let Some(timer) = heap.peek_mut() { - let is_alive = timer.is_alive.load(Ordering::Relaxed); - if is_alive { - let duration = timer.wake_at - std::time::Instant::now(); + if timer.shared.lock().unwrap().waker.is_some() { + let now = std::time::Instant::now(); + let duration = timer.wake_at - now; + eprintln!("Next timer to pop - now={:?}, wake_at={:?}, micros={}", now, timer.wake_at, duration.as_micros()); let micros = duration.as_micros(); - return (micros + 999 / 1000) as _ + return ((micros + 999) / 1000) as _ } else { PeekMut::pop(timer); } From be0e6129046665888917179068fc278a736e3559 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 8 Nov 2022 23:27:13 +0000 Subject: [PATCH 36/54] Move timer management to its own module --- capi/include/hyper.h | 4 +- src/ffi/body.rs | 2 +- src/ffi/mod.rs | 1 + src/ffi/server.rs | 9 ++- src/ffi/task.rs | 152 +++++------------------------------------ src/ffi/time.rs | 158 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 184 insertions(+), 142 deletions(-) create mode 100644 src/ffi/time.rs diff --git a/capi/include/hyper.h b/capi/include/hyper.h index abcb2e04aa..137f9d5598 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -1091,8 +1091,8 @@ struct hyper_task *hyper_executor_poll(const struct hyper_executor *exec); /* Returns the time until the executor will be able to make progress on tasks due to internal - timers popping. The executor should be polled soon after this time if not earlier due to - IO operations becoming available. + timers popping. The executor should be polled soon after this time (if not earlier due to + IO operations becoming available). Returns the time in milliseconds - a return value of -1 means there's no configured timers and the executor doesn't need polling until there's IO work available. diff --git a/src/ffi/body.rs b/src/ffi/body.rs index 4cc3415f2b..def4101329 100644 --- a/src/ffi/body.rs +++ b/src/ffi/body.rs @@ -14,7 +14,7 @@ use crate::body::{Bytes, Frame, Incoming as IncomingBody}; pub struct hyper_body(pub(super) IncomingBody); /// A buffer of bytes that is sent or received on a `hyper_body`. -pub struct hyper_buf(pub(crate) Bytes); +pub struct hyper_buf(pub(super) Bytes); pub(crate) struct UserBody { data_func: hyper_body_data_callback, diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 9a17d148dc..08bd59df36 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -44,6 +44,7 @@ mod error; mod http_types; mod io; mod task; +mod time; pub use self::body::*; pub use self::client::*; diff --git a/src/ffi/server.rs b/src/ffi/server.rs index b7b03bf37e..bf6d37fb6c 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -50,11 +50,10 @@ ffi_fn! { exec: *const hyper_executor ) -> *mut hyper_http1_serverconn_options { let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; - let weak = hyper_executor::downgrade(&exec); + let mut builder = http1::Builder::new(); + builder.timer(Arc::clone(exec.timer_heap())); std::mem::forget(exec); // We never incremented the strong count in this function so can't // drop our Arc. - let mut builder = http1::Builder::new(); - builder.timer(weak); Box::into_raw(Box::new(hyper_http1_serverconn_options( builder ))) @@ -212,10 +211,10 @@ ffi_fn! { ) -> *mut hyper_http2_serverconn_options { let exec = non_null! { Arc::from_raw(exec) ?= ptr::null_mut() }; let weak = hyper_executor::downgrade(&exec); + let mut builder = http2::Builder::new(weak.clone()); + builder.timer(Arc::clone(exec.timer_heap())); std::mem::forget(exec); // We never incremented the strong count in this function so can't // drop our Arc. - let mut builder = http2::Builder::new(weak.clone()); - builder.timer(weak); Box::into_raw(Box::new(hyper_http2_serverconn_options( builder ))) diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 1d5998b3a8..ce1141a490 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -7,7 +7,6 @@ use std::sync::{ Arc, Mutex, Weak, }; use std::task::{Context, Poll}; -use std::collections::binary_heap::PeekMut; use futures_util::stream::{FuturesUnordered, Stream}; use libc::c_int; @@ -53,11 +52,11 @@ pub struct hyper_executor { /// The heap of programmed timers, these will be progressed at the start of /// `hyper_executor_poll` - timers: Mutex>, + timers: Arc>, } #[derive(Clone)] -pub(crate) struct WeakExec(Weak); +pub(super) struct WeakExec(Weak); struct ExecWaker(AtomicBool); @@ -95,11 +94,11 @@ pub enum hyper_task_return_type { HYPER_TASK_BUF, } -pub(crate) unsafe trait AsTaskType { +pub(super) unsafe trait AsTaskType { fn as_task_type(&self) -> hyper_task_return_type; } -pub(crate) trait IntoDynTaskType { +pub(super) trait IntoDynTaskType { fn into_dyn_task_type(self) -> BoxAny; } @@ -111,14 +110,18 @@ impl hyper_executor { driver: Mutex::new(FuturesUnordered::new()), spawn_queue: Mutex::new(Vec::new()), is_woken: Arc::new(ExecWaker(AtomicBool::new(false))), - timers: Mutex::new(std::collections::BinaryHeap::new()), + timers: Arc::new(Mutex::new(crate::ffi::time::TimerHeap::new())), }) } - pub(crate) fn downgrade(exec: &Arc) -> WeakExec { + pub(super) fn downgrade(exec: &Arc) -> WeakExec { WeakExec(Arc::downgrade(exec)) } + pub(super) fn timer_heap(&self) -> &Arc> { + &self.timers + } + fn spawn(&self, task: Box) { self.spawn_queue .lock() @@ -181,18 +184,7 @@ impl hyper_executor { // Walk the timer heap waking active timers and discarding cancelled ones. fn pop_timers(&self) { let mut heap = self.timers.lock().unwrap(); - let now = std::time::Instant::now(); - while let Some(timer) = heap.peek_mut() { - if let Some(waker) = &mut timer.shared.lock().unwrap().waker { - if timer.wake_at < now { - waker.wake_by_ref(); - } else { - break; - } - } - // This time was for the past so pop it now. - let _ = PeekMut::pop(timer); - } + heap.process_timers(); } } @@ -205,7 +197,7 @@ impl futures_util::task::ArcWake for ExecWaker { // ===== impl WeakExec ===== impl WeakExec { - pub(crate) fn new() -> Self { + pub(super) fn new() -> Self { WeakExec(Weak::new()) } } @@ -228,107 +220,6 @@ where } } -// The entry in the timer heap for a programmed timer. The heap should expire the timer at -// `wake_at` and wake any waker it finds in the `shared` state. -struct Timer { - shared: Arc>, - wake_at: std::time::Instant, -} - -// Consistency with `Ord` requires us to report `Timer`s with the same `wake_at` as equal. -impl std::cmp::PartialEq for Timer { - fn eq(&self, other: &Self) -> bool { - self.wake_at.eq(&other.wake_at) - } -} -impl std::cmp::Eq for Timer {} - -// BinaryHeap is a max-heap and we want the top of the heap to be the nearest to popping timer -// so we want the "bigger" timer to have the earlier `wake_at` time. We achieve this by flipping -// the sides of the comparisons in `Ord` and implementing `PartialOrd` in terms of `Ord`. -impl std::cmp::PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} -impl std::cmp::Ord for Timer { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - // Note flipped order - other.wake_at.cmp(&self.wake_at) - } -} - -// A future that completes at `wake_at`. -struct TimerFuture { - exec: WeakExec, - wake_at: std::time::Instant, - // This is None when the timer isn't programmed in the heap - shared: Option>>, -} - -// Shared between the timer future and the timer heap. If the heap expires a timer is should wake -// the waker if one is present (which indicates that the timer has not been cancelled). -struct TimerShared { - waker: Option, -} - -impl std::future::Future for TimerFuture { - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let now = std::time::Instant::now(); - - if self.wake_at <= now { - return Poll::Ready(()); - } - - match &self.shared { - Some(shared) => { - // Timer was already programmed, update the waker - shared.lock().unwrap().waker = Some(cx.waker().clone()); - } - None => { - // Need to program the timer into the heap - if let Some(exec) = self.exec.0.upgrade() { - let mut heap = exec.timers.lock().unwrap(); - let shared = Arc::new(Mutex::new(TimerShared { - waker: Some(cx.waker().clone()), - })); - let t = Timer { - shared: Arc::clone(&shared), - wake_at: self.wake_at, - }; - heap.push(t); - self.shared = Some(shared); - } - } - } - - return Poll::Pending; - } -} - -impl std::ops::Drop for TimerFuture { - fn drop(&mut self) { - if let Some(shared) = &self.shared { - let _ = shared.lock().unwrap().waker.take(); - } - } -} - -impl crate::rt::Timer for WeakExec { - fn sleep(&self, duration: std::time::Duration) -> Box { - self.sleep_until(std::time::Instant::now() + duration) - } - fn sleep_until(&self, instant: std::time::Instant) -> Box { - Box::new(TimerFuture { - exec: self.clone(), - wake_at: instant, - shared: None, - }) - } -} - ffi_fn! { /// Creates a new task executor. fn hyper_executor_new() -> *const hyper_executor { @@ -381,27 +272,20 @@ ffi_fn! { /// and the executor doesn't need polling until there's IO work available. fn hyper_executor_next_timer_pop(exec: *const hyper_executor) -> std::ffi::c_int { let exec = non_null!(&*exec ?= -1); - let mut heap = exec.timers.lock().unwrap(); - while let Some(timer) = heap.peek_mut() { - if timer.shared.lock().unwrap().waker.is_some() { - let now = std::time::Instant::now(); - let duration = timer.wake_at - now; - eprintln!("Next timer to pop - now={:?}, wake_at={:?}, micros={}", now, timer.wake_at, duration.as_micros()); + match exec.timers.lock().unwrap().next_timer_pop() { + Some(duration) => { let micros = duration.as_micros(); - return ((micros + 999) / 1000) as _ - } else { - PeekMut::pop(timer); + ((micros + 999) / 1000) as _ } + None => -1 } - - return -1; } } // ===== impl hyper_task ===== impl hyper_task { - pub(crate) fn boxed(fut: F) -> Box + pub(super) fn boxed(fut: F) -> Box where F: Future + Send + 'static, F::Output: IntoDynTaskType + Send + Sync + 'static, @@ -547,7 +431,7 @@ where // ===== impl hyper_context ===== impl hyper_context<'_> { - pub(crate) fn wrap<'a, 'b>(cx: &'a mut Context<'b>) -> &'a mut hyper_context<'b> { + pub(super) fn wrap<'a, 'b>(cx: &'a mut Context<'b>) -> &'a mut hyper_context<'b> { // A struct with only one field has the same layout as that field. unsafe { std::mem::transmute::<&mut Context<'_>, &mut hyper_context<'_>>(cx) } } diff --git a/src/ffi/time.rs b/src/ffi/time.rs new file mode 100644 index 0000000000..722e7cb72c --- /dev/null +++ b/src/ffi/time.rs @@ -0,0 +1,158 @@ +use std::pin::Pin; +use std::sync::{Arc, Mutex}; +use std::task::{Context, Poll, Waker}; +use std::time::{Instant, Duration}; +use std::collections::binary_heap::{BinaryHeap, PeekMut}; + +/// A heap of timer entries with their associated wakers, backing `TimerFuture` instances. +pub(super) struct TimerHeap(BinaryHeap); + +/// The entry in the timer heap for a programmed timer. The heap should expire the timer at +/// `wake_at` and wake any waker it finds in the `shared` state. +struct TimerEntry { + shared: Arc>, + wake_at: Instant, +} + +/// A future that completes at `wake_at`. Requires that the associated `TimerHeap` is driven +/// in order to make progress. +struct TimerFuture { + heap: Arc>, + wake_at: Instant, + // This is None when the timer isn't programmed in the heap + shared: Option>>, +} + +/// Shared between the timer future and the timer heap. If the heap expires a timer it should wake +/// the associated waker if one is present (if not, that indicates that the timer has been cancelled +/// and can be discarded). +struct TimerShared { + waker: Option, +} + +// ===== impl TimerEntry ===== + +// Consistency with `Ord` requires us to report `TimerEntry`s with the same `wake_at` as equal. +impl std::cmp::PartialEq for TimerEntry { + fn eq(&self, other: &Self) -> bool { + self.wake_at.eq(&other.wake_at) + } +} +impl std::cmp::Eq for TimerEntry {} + +// BinaryHeap is a max-heap and we want the top of the heap to be the nearest to popping timer +// so we want the "bigger" timer to have the earlier `wake_at` time. We achieve this by flipping +// the sides of the comparisons in `Ord` and implementing `PartialOrd` in terms of `Ord`. +impl std::cmp::PartialOrd for TimerEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl std::cmp::Ord for TimerEntry { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + // Note flipped order + other.wake_at.cmp(&self.wake_at) + } +} + +// ===== impl TimerFuture ===== + +impl std::future::Future for TimerFuture { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let now = Instant::now(); + + if self.wake_at <= now { + return Poll::Ready(()); + } + + match &self.shared { + Some(shared) => { + // Timer was already programmed, update the waker + shared.lock().unwrap().waker = Some(cx.waker().clone()); + } + None => { + // Need to program the timer into the heap + let shared = Arc::new(Mutex::new(TimerShared { + waker: Some(cx.waker().clone()), + })); + { + let mut heap = self.heap.lock().unwrap(); + let t = TimerEntry { + shared: Arc::clone(&shared), + wake_at: self.wake_at, + }; + heap.0.push(t); + } + self.shared = Some(shared); + } + } + + return Poll::Pending; + } +} + +impl std::ops::Drop for TimerFuture { + fn drop(&mut self) { + if let Some(shared) = &self.shared { + let _ = shared.lock().unwrap().waker.take(); + } + } +} + +// ===== impl TimerHeap ===== + +impl crate::rt::Timer for Arc> { + fn sleep(&self, duration: Duration) -> Box { + self.sleep_until(Instant::now() + duration) + } + + fn sleep_until(&self, instant: Instant) -> Box { + Box::new(TimerFuture { + heap: Arc::clone(self), + wake_at: instant, + shared: None, + }) + } +} + +impl TimerHeap { + pub(super) fn new() -> Self { + Self(BinaryHeap::new()) + } + + /// Walk the timer heap waking active timers and discarding cancelled ones. + pub(super) fn process_timers(&mut self) { + let now = Instant::now(); + while let Some(timer) = self.0.peek_mut() { + if let Some(waker) = &mut timer.shared.lock().unwrap().waker { + if timer.wake_at < now { + waker.wake_by_ref(); + } else { + break; + } + } + // This time was for the past so pop it now. + let _ = PeekMut::pop(timer); + } + } + + /// Returns the time until the executor will be able to make progress on tasks due to internal + /// timers popping. The executor should be polled soon after this time (if not earlier due to + /// IO operations becoming available). + /// + /// If no timers are currently programmed, returns `None`. + pub(super) fn next_timer_pop(&mut self) -> Option { + let now = Instant::now(); + while let Some(timer) = self.0.peek_mut() { + if timer.shared.lock().unwrap().waker.is_some() { + return Some(timer.wake_at - now); + } else { + PeekMut::pop(timer); + } + } + + return None; + } +} From 787dfe9cd14ba41743e0ecf0318802cdacc9e410 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 9 Dec 2022 01:54:06 +0000 Subject: [PATCH 37/54] Better C Makefile practices --- capi/examples/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/capi/examples/Makefile b/capi/examples/Makefile index 859b5b3f33..eb05e3c718 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -13,11 +13,11 @@ all: client upload server ${CC} -c -o $@ $< $(HYPER_CFLAGS) $(CFLAGS) client: client.o - $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) + $(CC) -o $@ $< $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) upload: upload.o - $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) + $(CC) -o $@ $< $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) server: server.o - $(CC) -o $@ $^ $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) + $(CC) -o $@ $< $(HYPER_LDFLAGS) $(LDFLAGS) $(LIBS) clean: rm -f *.o client server upload From c480ea885fcf4ec1a395443dd968173656c3ef47 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 14 Dec 2022 12:53:58 +0000 Subject: [PATCH 38/54] Document parameters for hyper_request_method --- capi/include/hyper.h | 5 +++++ src/ffi/http_types.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 137f9d5598..89180e2738 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -735,6 +735,11 @@ enum hyper_code hyper_request_set_method(struct hyper_request *req, /* Get the HTTP Method of the request. + + `method` must be a pointer to a buffer that this function will populate with the HTTP + method of the request. The `header_len` argument must be a pointer to a `size_t` which, on + call, is populated with the maximum length of the `method` buffer and, on successful + response, will be set to the actual length of the value written into the buffer. */ enum hyper_code hyper_request_method(const struct hyper_request *req, uint8_t *method, diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 36b225984b..fdbc2362eb 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -70,6 +70,11 @@ ffi_fn! { ffi_fn! { /// Get the HTTP Method of the request. + /// + /// `method` must be a pointer to a buffer that this function will populate with the HTTP + /// method of the request. The `header_len` argument must be a pointer to a `size_t` which, on + /// call, is populated with the maximum length of the `method` buffer and, on successful + /// response, will be set to the actual length of the value written into the buffer. fn hyper_request_method(req: *const hyper_request, method: *mut u8, method_len: *mut size_t) -> hyper_code { let req = non_null!(&*req ?= hyper_code::HYPERE_INVALID_ARG); if method.is_null() { From 3d2f3ce46eb4ea4b329197ad09f6569228f961e5 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 14 Dec 2022 18:26:52 +0000 Subject: [PATCH 39/54] Fix up test for new Sleep API --- benches/support/tokiort.rs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/benches/support/tokiort.rs b/benches/support/tokiort.rs index 67ae3a91aa..816f9a6a4e 100644 --- a/benches/support/tokiort.rs +++ b/benches/support/tokiort.rs @@ -8,7 +8,6 @@ use std::{ use futures_util::Future; use hyper::rt::{Sleep, Timer}; -use pin_project_lite::pin_project; #[derive(Clone)] /// An Executor that uses the tokio runtime. @@ -31,15 +30,11 @@ pub struct TokioTimer; impl Timer for TokioTimer { fn sleep(&self, duration: Duration) -> Pin> { - Box::pin(TokioSleep { - inner: tokio::time::sleep(duration), - }) + Box::pin(tokio::time::sleep(duration)) } fn sleep_until(&self, deadline: Instant) -> Pin> { - Box::pin(TokioSleep { - inner: tokio::time::sleep_until(deadline.into()), - }) + Box::pin(tokio::time::sleep_until(deadline.into())) } } @@ -57,25 +52,3 @@ where self.inner.as_mut().poll(context) } } - -// Use TokioSleep to get tokio::time::Sleep to implement Unpin. -// see https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html -pin_project! { - pub(crate) struct TokioSleep { - #[pin] - pub(crate) inner: tokio::time::Sleep, - } -} - -impl Future for TokioSleep { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().inner.poll(cx) - } -} - -// Use HasSleep to get tokio::time::Sleep to implement Unpin. -// see https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html - -impl Sleep for TokioSleep {} From df7386e677d0ea70662897b59cd18b8e42d4f5e6 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 14 Dec 2022 18:27:13 +0000 Subject: [PATCH 40/54] Install cargo-expand in CI for gen_header --- .github/workflows/CI.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 7683bf8438..8e4b7451b8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -232,6 +232,12 @@ jobs: command: install args: cbindgen + - name: Install cargo-expand + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-expand + - name: Build FFI uses: actions-rs/cargo@v1 env: From 0d9b99bf9f855d0ab63a37ace9fb6ba0bf9e1d0b Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 14 Dec 2022 18:37:48 +0000 Subject: [PATCH 41/54] Fix up CI test for FFI builds --- .github/workflows/CI.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 8e4b7451b8..ad40fcef7e 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -185,22 +185,16 @@ jobs: toolchain: stable override: true - - name: Install cbindgen - uses: actions-rs/cargo@v1 - with: - command: install - args: cbindgen - - name: Build FFI uses: actions-rs/cargo@v1 env: RUSTFLAGS: --cfg hyper_unstable_ffi with: command: rustc - args: --features client,http1,http2,ffi --crate-type cdylib + args: --features ffi --crate-type cdylib --release - name: Make Examples - run: cd capi/examples && make client + run: cd capi/examples && make - name: Run FFI unit tests uses: actions-rs/cargo@v1 @@ -208,7 +202,7 @@ jobs: RUSTFLAGS: --cfg hyper_unstable_ffi with: command: test - args: --features full,ffi --lib + args: --features ffi --lib ffi-header: name: Verify hyper.h is up to date From 1de0922f2fd2c41dfceb6e9371a0d0bf461c3f98 Mon Sep 17 00:00:00 2001 From: Martin Howarth Date: Wed, 21 Dec 2022 01:23:41 +0000 Subject: [PATCH 42/54] Fix header handling for requests --- capi/examples/server.c | 23 ++++++++++++ src/ffi/client.rs | 10 +++--- src/ffi/http_types.rs | 81 +++++++++++++++++++++++++++++------------- src/ffi/server.rs | 17 ++++----- 4 files changed, 91 insertions(+), 40 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index d627910022..2c88ee8042 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -212,9 +212,17 @@ static void free_conn_data(int epoll, conn_data *conn) { free(conn); } +static int print_each_header( + void *userdata, const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len +) { + printf("%.*s: %.*s\n", (int)name_len, name, (int)value_len, value); + return HYPER_ITER_CONTINUE; +} + static void server_callback( void *userdata, hyper_request *request, hyper_response_channel *channel ) { + // Print out various properties of the request. unsigned char scheme[16]; size_t scheme_len = sizeof(scheme); unsigned char authority[16]; @@ -242,9 +250,24 @@ static void server_callback( printf("Request method was %.*s\n", (int)method_len, method); } + // Print out all the headers from the request + hyper_headers *req_headers = hyper_request_headers(request); + hyper_headers_foreach(req_headers, print_each_header, NULL); hyper_request_free(request); + + // Build a response hyper_response *response = hyper_response_new(); hyper_response_set_status(response, 404); + hyper_headers* rsp_headers = hyper_response_headers(response); + hyper_headers_set( + rsp_headers, + (unsigned char*)"Cache-Control", + 13, + (unsigned char*)"no-cache", + 8 + ); + + // And send the response, completing the transaction hyper_response_channel_send(channel, response); } diff --git a/src/ffi/client.rs b/src/ffi/client.rs index c4da61950e..665a86aae0 100644 --- a/src/ffi/client.rs +++ b/src/ffi/client.rs @@ -91,18 +91,18 @@ ffi_fn! { /// Returns a task that needs to be polled until it is ready. When ready, the /// task yields a `hyper_response *`. fn hyper_clientconn_send(conn: *mut hyper_clientconn, req: *mut hyper_request) -> *mut hyper_task { - let mut req = non_null! { Box::from_raw(req) ?= ptr::null_mut() }; + let req = non_null! { Box::from_raw(req) ?= ptr::null_mut() }; // Update request with original-case map of headers - req.finalize_request(); + let req = (*req).finalize(); let fut = match non_null! { &mut *conn ?= ptr::null_mut() }.tx { - Tx::Http1(ref mut tx) => futures_util::future::Either::Left(tx.send_request(req.0)), - Tx::Http2(ref mut tx) => futures_util::future::Either::Right(tx.send_request(req.0)), + Tx::Http1(ref mut tx) => futures_util::future::Either::Left(tx.send_request(req)), + Tx::Http2(ref mut tx) => futures_util::future::Either::Right(tx.send_request(req)), }; let fut = async move { - fut.await.map(hyper_response::wrap) + fut.await.map(hyper_response::from) }; Box::into_raw(hyper_task::boxed(fut)) diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index fdbc2362eb..986e2c80c7 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -12,10 +12,10 @@ use crate::header::{HeaderName, HeaderValue}; use crate::{HeaderMap, Method, Request, Response, Uri}; /// An HTTP request. -pub struct hyper_request(pub(super) Request); +pub struct hyper_request(Request); /// An HTTP response. -pub struct hyper_response(pub(super) Response); +pub struct hyper_response(Response); /// An HTTP header map. /// @@ -38,7 +38,7 @@ type hyper_request_on_informational_callback = extern "C" fn(*mut c_void, *mut h ffi_fn! { /// Construct a new HTTP request. fn hyper_request_new() -> *mut hyper_request { - Box::into_raw(Box::new(hyper_request(Request::new(IncomingBody::empty())))) + Box::into_raw(Box::new(hyper_request::from(Request::new(IncomingBody::empty())))) } ?= std::ptr::null_mut() } @@ -92,7 +92,6 @@ ffi_fn! { } } - ffi_fn! { /// Set the URI of the request. /// @@ -350,12 +349,34 @@ ffi_fn! { } impl hyper_request { - pub(super) fn finalize_request(&mut self) { + pub(super) fn finalize(mut self) -> Request { if let Some(headers) = self.0.extensions_mut().remove::() { *self.0.headers_mut() = headers.headers; self.0.extensions_mut().insert(headers.orig_casing); self.0.extensions_mut().insert(headers.orig_order); } + self.0 + } +} + +impl From> for hyper_request { + fn from(mut req: Request) -> Self { + let headers = std::mem::take(req.headers_mut()); + let orig_casing = req + .extensions_mut() + .remove::() + .unwrap_or_else(HeaderCaseMap::default); + let orig_order = req + .extensions_mut() + .remove::() + .unwrap_or_else(OriginalHeaderOrder::default); + req.extensions_mut().insert(hyper_headers { + headers, + orig_casing, + orig_order, + }); + + hyper_request(req) } } @@ -498,38 +519,50 @@ ffi_fn! { } impl hyper_response { - pub(super) fn wrap(mut resp: Response) -> hyper_response { - let headers = std::mem::take(resp.headers_mut()); - let orig_casing = resp + fn reason_phrase(&self) -> &[u8] { + if let Some(reason) = self.0.extensions().get::() { + return reason.as_bytes(); + } + + if let Some(reason) = self.0.status().canonical_reason() { + return reason.as_bytes(); + } + + &[] + } + + pub(super) fn finalize(mut self) -> Response { + if let Some(headers) = self.0.extensions_mut().remove::() { + *self.0.headers_mut() = headers.headers; + self.0.extensions_mut().insert(headers.orig_casing); + self.0.extensions_mut().insert(headers.orig_order); + } + self.0 + } +} + +impl From> for hyper_response { + fn from(mut rsp: Response) -> Self { + let headers = std::mem::take(rsp.headers_mut()); + let orig_casing = rsp .extensions_mut() .remove::() .unwrap_or_else(HeaderCaseMap::default); - let orig_order = resp + let orig_order = rsp .extensions_mut() .remove::() .unwrap_or_else(OriginalHeaderOrder::default); - resp.extensions_mut().insert(hyper_headers { + rsp.extensions_mut().insert(hyper_headers { headers, orig_casing, orig_order, }); - hyper_response(resp) - } - - fn reason_phrase(&self) -> &[u8] { - if let Some(reason) = self.0.extensions().get::() { - return reason.as_bytes(); - } - - if let Some(reason) = self.0.status().canonical_reason() { - return reason.as_bytes(); - } - - &[] + hyper_response(rsp) } } + unsafe impl AsTaskType for hyper_response { fn as_task_type(&self) -> hyper_task_return_type { hyper_task_return_type::HYPER_TASK_RESPONSE @@ -690,7 +723,7 @@ unsafe fn raw_name_value( impl OnInformational { pub(crate) fn call(&mut self, resp: Response) { - let mut resp = hyper_response::wrap(resp); + let mut resp = hyper_response::from(resp); (self.func)(self.data.0, &mut resp); } } diff --git a/src/ffi/server.rs b/src/ffi/server.rs index bf6d37fb6c..0293097158 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -507,7 +507,7 @@ impl crate::service::Service> for hyper_service { >; fn call(&mut self, req: crate::Request) -> Self::Future { - let req_ptr = Box::into_raw(Box::new(hyper_request(req))); + let req_ptr = Box::into_raw(Box::new(hyper_request::from(req))); let (tx, rx) = futures_channel::oneshot::channel(); let rsp_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); @@ -516,7 +516,7 @@ impl crate::service::Service> for hyper_service { Box::pin(async move { let rsp = rx.await.expect("Channel closed?"); - Ok(rsp.0) + Ok(rsp.finalize()) }) } } @@ -527,23 +527,18 @@ where { // The internals are in an `Option` so they can be extracted during H1->H2 fallback. Otherwise // this must always be `Some(h1, h2)` (and code is allowed to panic if that's not true). - H1( - Option<( - http1::Connection, - http2::Builder, - )>, - ), + H1(Option<(http1::Connection, http2::Builder)>), H2(http2::Connection, Serv, Exec>), } - impl std::future::Future for AutoConnection where IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static, - Serv: crate::service::HttpService, + Serv: crate::service::HttpService, Exec: crate::common::exec::ConnStreamExec + Unpin, http1::Connection: std::future::Future> + Unpin, - http2::Connection, Serv, Exec>: std::future::Future> + Unpin, + http2::Connection, Serv, Exec>: + std::future::Future> + Unpin, { type Output = crate::Result<()>; From 69e146bed5ad133f6b86876a2518162ab3d2c0e3 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Mon, 27 Feb 2023 19:03:53 +0000 Subject: [PATCH 43/54] Allow passing a drop callback for IO userdata --- capi/examples/client.c | 2 +- capi/examples/server.c | 31 +++++++++++++++---------------- capi/examples/upload.c | 2 +- capi/include/hyper.h | 10 +++++++++- src/ffi/io.rs | 29 +++++++++++++++++++++++++++-- 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/capi/examples/client.c b/capi/examples/client.c index 7f1efa3edf..ff06c694ad 100644 --- a/capi/examples/client.c +++ b/capi/examples/client.c @@ -170,7 +170,7 @@ int main(int argc, char *argv[]) { // Hookup the IO hyper_io *io = hyper_io_new(); - hyper_io_set_userdata(io, (void *)conn); + hyper_io_set_userdata(io, (void *)conn, NULL); hyper_io_set_read(io, read_cb); hyper_io_set_write(io, write_cb); diff --git a/capi/examples/server.c b/capi/examples/server.c index 2c88ee8042..bf65828119 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -18,6 +18,7 @@ static const int MAX_EVENTS = 128; typedef struct conn_data_s { int fd; + int epoll_fd; hyper_waker *read_waker; hyper_waker *write_waker; } conn_data; @@ -173,25 +174,18 @@ static conn_data *create_conn_data(int epoll, int fd) { } conn->fd = fd; + conn->epoll_fd = epoll; conn->read_waker = NULL; conn->write_waker = NULL; return conn; } -static hyper_io *create_io(conn_data *conn) { - // Hookup the IO - hyper_io *io = hyper_io_new(); - hyper_io_set_userdata(io, (void *)conn); - hyper_io_set_read(io, read_cb); - hyper_io_set_write(io, write_cb); +static void free_conn_data(void *userdata) { + conn_data* conn = (conn_data*)userdata; - return io; -} - -static void free_conn_data(int epoll, conn_data *conn) { // Disassociate with the epoll - if (epoll_ctl(epoll, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { + if (epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { perror("epoll_ctl (transport, delete)"); } @@ -212,6 +206,16 @@ static void free_conn_data(int epoll, conn_data *conn) { free(conn); } +static hyper_io *create_io(conn_data *conn) { + // Hookup the IO + hyper_io *io = hyper_io_new(); + hyper_io_set_userdata(io, (void *)conn, free_conn_data); + hyper_io_set_read(io, read_cb); + hyper_io_set_write(io, write_cb); + + return io; +} + static int print_each_header( void *userdata, const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len ) { @@ -345,10 +349,6 @@ int main(int argc, char *argv[]) { hyper_error_free(err); // clean up the task - conn_data *conn = hyper_task_userdata(task); - if (conn) { - free_conn_data(epoll, conn); - } hyper_task_free(task); continue; @@ -358,7 +358,6 @@ int main(int argc, char *argv[]) { conn_data *conn = hyper_task_userdata(task); if (conn) { printf("server connection complete\n"); - free_conn_data(epoll, conn); } else { printf("internal hyper task complete\n"); } diff --git a/capi/examples/upload.c b/capi/examples/upload.c index bdd82e8ea6..5aafbb2e74 100644 --- a/capi/examples/upload.c +++ b/capi/examples/upload.c @@ -210,7 +210,7 @@ int main(int argc, char *argv[]) { // Hookup the IO hyper_io *io = hyper_io_new(); - hyper_io_set_userdata(io, (void *)conn); + hyper_io_set_userdata(io, (void *)conn, NULL); hyper_io_set_read(io, read_cb); hyper_io_set_write(io, write_cb); diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 89180e2738..de752f762c 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -252,6 +252,8 @@ typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_resp typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t); +typedef void (*hyper_io_userdata_drop_callback)(void*); + typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); typedef size_t (*hyper_io_write_callback)(void*, struct hyper_context*, const uint8_t*, size_t); @@ -1017,8 +1019,14 @@ void hyper_io_free(struct hyper_io *io); Set the user data pointer for this IO to some value. This value is passed as an argument to the read and write callbacks. + + If passed, the `drop_func` will be called on the `userdata` when the + `hyper_io` is destroyed (either explicitely by `hyper_io_free` or + implicitely by an associated hyper task completing). */ -void hyper_io_set_userdata(struct hyper_io *io, void *data); +void hyper_io_set_userdata(struct hyper_io *io, + void *data, + hyper_io_userdata_drop_callback drop_func); /* Get the user data pointer for this IO value. diff --git a/src/ffi/io.rs b/src/ffi/io.rs index 03b8e9dad0..b0e4ca7627 100644 --- a/src/ffi/io.rs +++ b/src/ffi/io.rs @@ -18,12 +18,14 @@ type hyper_io_read_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *mut u8, size_t) -> size_t; type hyper_io_write_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *const u8, size_t) -> size_t; +type hyper_io_userdata_drop_callback = extern "C" fn(*mut c_void); /// An IO object used to represent a socket or similar concept. pub struct hyper_io { read: hyper_io_read_callback, write: hyper_io_write_callback, userdata: *mut c_void, + userdata_drop: Option, } ffi_fn! { @@ -36,6 +38,7 @@ ffi_fn! { read: read_noop, write: write_noop, userdata: std::ptr::null_mut(), + userdata_drop: None, })) } ?= std::ptr::null_mut() } @@ -54,8 +57,22 @@ ffi_fn! { /// Set the user data pointer for this IO to some value. /// /// This value is passed as an argument to the read and write callbacks. - fn hyper_io_set_userdata(io: *mut hyper_io, data: *mut c_void) { - non_null!(&mut *io ?= ()).userdata = data; + /// + /// If passed, the `drop_func` will be called on the `userdata` when the + /// `hyper_io` is destroyed (either explicitely by `hyper_io_free` or + /// implicitely by an associated hyper task completing). + fn hyper_io_set_userdata( + io: *mut hyper_io, + data: *mut c_void, + drop_func: hyper_io_userdata_drop_callback, + ) { + let io = non_null!(&mut *io? = ()); + io.userdata = data; + io.userdata_drop = if (drop_func as *const c_void).is_null() { + None + } else { + Some(drop_func) + } } } @@ -131,6 +148,14 @@ extern "C" fn write_noop( 0 } +impl Drop for hyper_io { + fn drop(&mut self) { + if let Some(drop_fn) = self.userdata_drop { + drop_fn(self.userdata) + } + } +} + impl AsyncRead for hyper_io { fn poll_read( self: Pin<&mut Self>, From f78a3242dd6e9e43cf46b525119b4b9daf4c8933 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 28 Feb 2023 20:50:59 +0000 Subject: [PATCH 44/54] ...and a drop callback for service userdata --- Cargo.toml | 7 +---- capi/examples/server.c | 49 ++++++++++++++++++++++---------- capi/include/hyper.h | 31 ++++++++++++++++++--- src/ffi/server.rs | 63 +++++++++++++++++++++++++++++++++++------- src/ffi/task.rs | 2 ++ 5 files changed, 117 insertions(+), 35 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 28294b7ef3..bb9c40253c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,12 +70,7 @@ url = "2.2" default = [] # Easily turn it all on -full = [ - "client", - "http1", - "http2", - "server", -] +full = ["client", "http1", "http2", "server"] # HTTP versions http1 = [] diff --git a/capi/examples/server.c b/capi/examples/server.c index bf65828119..587ca828d9 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -216,6 +216,20 @@ static hyper_io *create_io(conn_data *conn) { return io; } +typedef struct service_userdata_s { + char host[128]; + char port[8]; +} service_userdata; + +static service_userdata* create_service_userdata() { + return (service_userdata*)calloc(1, sizeof(service_userdata)); +} + +static void free_service_userdata(void* userdata) { + service_userdata* cast_userdata = (service_userdata*)userdata; + free(cast_userdata); +} + static int print_each_header( void *userdata, const uint8_t *name, size_t name_len, const uint8_t *value, size_t value_len ) { @@ -226,6 +240,9 @@ static int print_each_header( static void server_callback( void *userdata, hyper_request *request, hyper_response_channel *channel ) { + service_userdata* service_data = (service_userdata*)userdata; + printf("Request from %s:%s\n", service_data->host, service_data->port); + // Print out various properties of the request. unsigned char scheme[16]; size_t scheme_len = sizeof(scheme); @@ -336,8 +353,9 @@ int main(int argc, char *argv[]) { if (!task) { break; } + if (hyper_task_type(task) == HYPER_TASK_ERROR) { - printf("handshake error!\n"); + printf("hyper task failed with error!\n"); hyper_error* err = hyper_task_value(task); printf("error code: %d\n", hyper_error_code(err)); @@ -355,12 +373,14 @@ int main(int argc, char *argv[]) { } if (hyper_task_type(task) == HYPER_TASK_EMPTY) { - conn_data *conn = hyper_task_userdata(task); - if (conn) { - printf("server connection complete\n"); - } else { - printf("internal hyper task complete\n"); - } + printf("internal hyper task complete\n"); + hyper_task_free(task); + + continue; + } + + if (hyper_task_type(task) == HYPER_TASK_SERVERCONN) { + printf("server connection task complete\n"); hyper_task_free(task); continue; @@ -391,21 +411,20 @@ int main(int argc, char *argv[]) { while ((new_fd = accept( listen_fd, (struct sockaddr *)&remote_addr_storage, &remote_addr_len )) >= 0) { - char remote_host[128]; - char remote_port[8]; + service_userdata *userdata = create_service_userdata(); if (getnameinfo( remote_addr, remote_addr_len, - remote_host, - sizeof(remote_host), - remote_port, - sizeof(remote_port), + userdata->host, + sizeof(userdata->host), + userdata->port, + sizeof(userdata->port), NI_NUMERICHOST | NI_NUMERICSERV ) < 0) { perror("getnameinfo"); printf("New incoming connection from (unknown)\n"); } else { - printf("New incoming connection from (%s:%s)\n", remote_host, remote_port); + printf("New incoming connection from (%s:%s)\n", userdata->host, userdata->port); } // Set non-blocking @@ -426,9 +445,9 @@ int main(int argc, char *argv[]) { // Ask hyper to drive this connection hyper_service *service = hyper_service_new(server_callback); + hyper_service_set_userdata(service, userdata, free_service_userdata); hyper_task *serverconn = hyper_serve_httpX_connection(http1_opts, http2_opts, io, service); - hyper_task_set_userdata(serverconn, conn); hyper_executor_push(exec, serverconn); } diff --git a/capi/include/hyper.h b/capi/include/hyper.h index de752f762c..5dbd3c3ff8 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -134,6 +134,10 @@ typedef enum hyper_task_return_type { The value of this task is `hyper_buf *`. */ HYPER_TASK_BUF, + /* + The value of this task is null (the task was a server-side connection task) + */ + HYPER_TASK_SERVERCONN, } hyper_task_return_type; /* @@ -248,6 +252,14 @@ typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyp */ typedef void (*hyper_service_callback)(void*, struct hyper_request*, struct hyper_response_channel*); +/* + Since a hyper_service owns the userdata passed to it the calling code can register a cleanup + function to be called when the service is dropped. This function may be omitted/a no-op if + the calling code wants to manage memory lifetimes itself (e.g. if the userdata is a static + global) + */ +typedef void (*hyper_service_userdata_drop_callback)(void*); + typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*); typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t); @@ -641,12 +653,23 @@ enum hyper_code hyper_http2_serverconn_options_max_header_list_size(struct hyper struct hyper_service *hyper_service_new(hyper_service_callback service_fn); /* - Register opaque userdata with the `hyper_service`. + Register opaque userdata with the `hyper_service`. This userdata must be `Send` in a rust + sense (i.e. can be passed between threads) though it doesn't have to be thread-safe (it + won't be accessed from multiple thread concurrently). + + The service takes ownership of the userdata and will call the `drop_userdata` callback when + the service task is complete. If the `drop_userdata` callback is `NULL` then the service + will instead forget the userdata when the associated task is completed and the calling code + is responsible for cleaning up the userdata through some other mechanism. + */ +void hyper_service_set_userdata(struct hyper_service *service, + void *userdata, + hyper_service_userdata_drop_callback drop_func); - The service borrows the userdata until the service is driven on a connection and the - associated task completes. +/* + Frees a hyper_service object if no longer needed */ -void hyper_service_set_userdata(struct hyper_service *service, void *userdata); +void hyper_service_free(struct hyper_service *service); /* Serve the provided `hyper_service *` as an HTTP/1 endpoint over the provided `hyper_io *` diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 520654dc58..f5115f069a 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -21,6 +21,7 @@ pub struct hyper_http2_serverconn_options(http2::Builder); pub struct hyper_service { service_fn: hyper_service_callback, userdata: UserDataPointer, + userdata_drop: Option, } /// A channel on which to send back a response to complete a transaction for a service. @@ -42,6 +43,12 @@ pub struct hyper_response_channel(futures_channel::oneshot::Sender> for hyper_service { } } +impl Drop for hyper_service { + fn drop(&mut self) { + if let Some(drop_func) = self.userdata_drop { + drop_func(self.userdata.0); + } + } +} + enum AutoConnection where Serv: crate::service::HttpService, @@ -531,16 +563,27 @@ where H2(http2::Connection, Serv, Exec>), } +// Marker type so the `hyper_task` can be distinguished from internal timer/h2 tasks. +struct ServerConn; + +unsafe impl crate::ffi::task::AsTaskType for ServerConn { + fn as_task_type(&self) -> crate::ffi::task::hyper_task_return_type { + crate::ffi::task::hyper_task_return_type::HYPER_TASK_SERVERCONN + } +} + impl std::future::Future for AutoConnection where IO: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + 'static, Serv: crate::service::HttpService, - Exec: crate::rt::Executor> + Unpin + Clone, + Exec: crate::rt::Executor> + + Unpin + + Clone, http1::Connection: std::future::Future> + Unpin, http2::Connection, Serv, Exec>: std::future::Future> + Unpin, { - type Output = crate::Result<()>; + type Output = crate::Result; fn poll( self: std::pin::Pin<&mut Self>, @@ -550,7 +593,7 @@ where let (h1, h2) = match zelf { AutoConnection::H1(inner) => { match ready!(std::pin::Pin::new(&mut inner.as_mut().unwrap().0).poll(cx)) { - Ok(()) => return std::task::Poll::Ready(Ok(())), + Ok(()) => return std::task::Poll::Ready(Ok(ServerConn)), Err(e) => { let kind = e.kind(); if matches!( @@ -571,7 +614,7 @@ where } } AutoConnection::H2(h2) => match ready!(std::pin::Pin::new(h2).poll(cx)) { - Ok(()) => return std::task::Poll::Ready(Ok(())), + Ok(()) => return std::task::Poll::Ready(Ok(ServerConn)), Err(e) => return std::task::Poll::Ready(Err(e)), }, }; diff --git a/src/ffi/task.rs b/src/ffi/task.rs index bb01becfd1..6ac126b79b 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -92,6 +92,8 @@ pub enum hyper_task_return_type { HYPER_TASK_RESPONSE, /// The value of this task is `hyper_buf *`. HYPER_TASK_BUF, + /// The value of this task is null (the task was a server-side connection task) + HYPER_TASK_SERVERCONN, } pub(super) unsafe trait AsTaskType { From b414b409535dd7da5c998bf885e4370cd062b07b Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 14 Mar 2023 17:11:44 +0000 Subject: [PATCH 45/54] Add common handling for userdata drop/borrow semantics everywhere --- capi/examples/client.c | 8 +++--- capi/examples/upload.c | 12 ++++----- capi/include/hyper.h | 45 ++++++++++++++++++--------------- src/ffi/body.rs | 19 +++++++------- src/ffi/http_types.rs | 11 ++++---- src/ffi/io.rs | 31 ++++++----------------- src/ffi/mod.rs | 10 +------- src/ffi/server.rs | 38 +++++++--------------------- src/ffi/task.rs | 17 ++++++------- src/ffi/userdata.rs | 57 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 133 insertions(+), 115 deletions(-) create mode 100644 src/ffi/userdata.rs diff --git a/capi/examples/client.c b/capi/examples/client.c index ff06c694ad..273369dc98 100644 --- a/capi/examples/client.c +++ b/capi/examples/client.c @@ -184,7 +184,7 @@ int main(int argc, char *argv[]) { hyper_clientconn_options_exec(opts, exec); hyper_task *handshake = hyper_clientconn_handshake(io, opts); - hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE); + hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE, NULL); // Let's wait for the handshake to finish... hyper_executor_push(exec, handshake); @@ -230,7 +230,7 @@ int main(int argc, char *argv[]) { // Send it! hyper_task *send = hyper_clientconn_send(client, req); - hyper_task_set_userdata(send, (void *)EXAMPLE_SEND); + hyper_task_set_userdata(send, (void *)EXAMPLE_SEND, NULL); printf("sending ...\n"); hyper_executor_push(exec, send); @@ -261,8 +261,8 @@ int main(int argc, char *argv[]) { printf("\n"); hyper_body *resp_body = hyper_response_body(resp); - hyper_task *foreach = hyper_body_foreach(resp_body, print_each_chunk, NULL); - hyper_task_set_userdata(foreach, (void *)EXAMPLE_RESP_BODY); + hyper_task *foreach = hyper_body_foreach(resp_body, print_each_chunk, NULL, NULL); + hyper_task_set_userdata(foreach, (void *)EXAMPLE_RESP_BODY, NULL); hyper_executor_push(exec, foreach); // No longer need the response diff --git a/capi/examples/upload.c b/capi/examples/upload.c index 5aafbb2e74..4da690f735 100644 --- a/capi/examples/upload.c +++ b/capi/examples/upload.c @@ -224,7 +224,7 @@ int main(int argc, char *argv[]) { hyper_clientconn_options_exec(opts, exec); hyper_task *handshake = hyper_clientconn_handshake(io, opts); - hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE); + hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE, NULL); // Let's wait for the handshake to finish... hyper_executor_push(exec, handshake); @@ -274,17 +274,17 @@ int main(int argc, char *argv[]) { // the body is sent immediately. This will just print if any // informational headers are received. printf(" with expect-continue ...\n"); - hyper_request_on_informational(req, print_informational, NULL); + hyper_request_on_informational(req, print_informational, NULL, NULL); // Prepare the req body hyper_body *body = hyper_body_new(); - hyper_body_set_userdata(body, &upload); + hyper_body_set_userdata(body, &upload, NULL); hyper_body_set_data_func(body, poll_req_upload); hyper_request_set_body(req, body); // Send it! hyper_task *send = hyper_clientconn_send(client, req); - hyper_task_set_userdata(send, (void *)EXAMPLE_SEND); + hyper_task_set_userdata(send, (void *)EXAMPLE_SEND, NULL); printf("sending ...\n"); hyper_executor_push(exec, send); @@ -315,7 +315,7 @@ int main(int argc, char *argv[]) { // Set us up to peel data from the body a chunk at a time hyper_task *body_data = hyper_body_data(resp_body); - hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY); + hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY, NULL); hyper_executor_push(exec, body_data); // No longer need the response @@ -335,7 +335,7 @@ int main(int argc, char *argv[]) { hyper_task_free(task); hyper_task *body_data = hyper_body_data(resp_body); - hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY); + hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY, NULL); hyper_executor_push(exec, body_data); break; diff --git a/capi/include/hyper.h b/capi/include/hyper.h index 5dbd3c3ff8..a162d7699f 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -233,6 +233,20 @@ typedef struct hyper_waker hyper_waker; typedef int (*hyper_body_foreach_callback)(void*, const struct hyper_buf*); +/* + Many hyper entites can be given userdata to allow user callbacks to corellate work together. + Since much of hyper is asychronous it's often useful to treat these userdata objects as "owned" + by the hyper entity (and hence to be cleaned up when that entity is dropped). + + To acheive this a `hyepr_userdata_drop` callback is passed by calling code alongside the + userdata to register a cleanup function. + + This function may be provided as NULL if the calling code wants to manage memory lifetimes + itself, in which case the hyper object will logically consider the userdata "borrowed" until + the hyper entity is dropped. + */ +typedef void (*hyper_userdata_drop)(void*); + typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**); /* @@ -252,20 +266,10 @@ typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyp */ typedef void (*hyper_service_callback)(void*, struct hyper_request*, struct hyper_response_channel*); -/* - Since a hyper_service owns the userdata passed to it the calling code can register a cleanup - function to be called when the service is dropped. This function may be omitted/a no-op if - the calling code wants to manage memory lifetimes itself (e.g. if the userdata is a static - global) - */ -typedef void (*hyper_service_userdata_drop_callback)(void*); - typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*); typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t); -typedef void (*hyper_io_userdata_drop_callback)(void*); - typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t); typedef size_t (*hyper_io_write_callback)(void*, struct hyper_context*, const uint8_t*, size_t); @@ -319,12 +323,13 @@ struct hyper_task *hyper_body_data(struct hyper_body *body); */ struct hyper_task *hyper_body_foreach(struct hyper_body *body, hyper_body_foreach_callback func, - void *userdata); + void *userdata, + hyper_userdata_drop drop); /* Set userdata on this body, which will be passed to callback functions. */ -void hyper_body_set_userdata(struct hyper_body *body, void *userdata); +void hyper_body_set_userdata(struct hyper_body *body, void *userdata, hyper_userdata_drop drop); /* Set the data callback for this body. @@ -659,12 +664,13 @@ struct hyper_service *hyper_service_new(hyper_service_callback service_fn); The service takes ownership of the userdata and will call the `drop_userdata` callback when the service task is complete. If the `drop_userdata` callback is `NULL` then the service - will instead forget the userdata when the associated task is completed and the calling code - is responsible for cleaning up the userdata through some other mechanism. + will instead borrow the userdata and forget it when the associated task is completed and + thus the calling code is responsible for cleaning up the userdata through some other + mechanism. */ void hyper_service_set_userdata(struct hyper_service *service, void *userdata, - hyper_service_userdata_drop_callback drop_func); + hyper_userdata_drop drop); /* Frees a hyper_service object if no longer needed @@ -896,7 +902,8 @@ struct hyper_body *hyper_request_body(struct hyper_request *req); */ enum hyper_code hyper_request_on_informational(struct hyper_request *req, hyper_request_on_informational_callback callback, - void *data); + void *data, + hyper_userdata_drop drop); /* Construct a new HTTP 200 Ok response @@ -1047,9 +1054,7 @@ void hyper_io_free(struct hyper_io *io); `hyper_io` is destroyed (either explicitely by `hyper_io_free` or implicitely by an associated hyper task completing). */ -void hyper_io_set_userdata(struct hyper_io *io, - void *data, - hyper_io_userdata_drop_callback drop_func); +void hyper_io_set_userdata(struct hyper_io *io, void *data, hyper_userdata_drop drop_func); /* Get the user data pointer for this IO value. @@ -1161,7 +1166,7 @@ enum hyper_task_return_type hyper_task_type(struct hyper_task *task); This value will be passed to task callbacks, and can be checked later with `hyper_task_userdata`. */ -void hyper_task_set_userdata(struct hyper_task *task, void *userdata); +void hyper_task_set_userdata(struct hyper_task *task, void *userdata, hyper_userdata_drop drop); /* Retrieve the userdata that has been set via `hyper_task_set_userdata`. diff --git a/src/ffi/body.rs b/src/ffi/body.rs index 5e17a572d8..d72223835c 100644 --- a/src/ffi/body.rs +++ b/src/ffi/body.rs @@ -7,7 +7,8 @@ use http_body_util::BodyExt as _; use libc::{c_int, size_t}; use super::task::{hyper_context, hyper_task, hyper_task_return_type, AsTaskType}; -use super::{UserDataPointer, HYPER_ITER_CONTINUE}; +use super::HYPER_ITER_CONTINUE; +use super::userdata::{Userdata, hyper_userdata_drop}; use crate::body::{Bytes, Frame, Incoming as IncomingBody}; /// A streaming HTTP body. @@ -18,7 +19,7 @@ pub struct hyper_buf(pub(super) Bytes); pub(crate) struct UserBody { data_func: hyper_body_data_callback, - userdata: *mut c_void, + userdata: Userdata, } // ===== Body ===== @@ -88,15 +89,15 @@ ffi_fn! { /// chunks as they are received, or `HYPER_ITER_BREAK` to cancel. /// /// This will consume the `hyper_body *`, you shouldn't use it anymore or free it. - fn hyper_body_foreach(body: *mut hyper_body, func: hyper_body_foreach_callback, userdata: *mut c_void) -> *mut hyper_task { + fn hyper_body_foreach(body: *mut hyper_body, func: hyper_body_foreach_callback, userdata: *mut c_void, drop: hyper_userdata_drop) -> *mut hyper_task { let mut body = non_null!(Box::from_raw(body) ?= ptr::null_mut()); - let userdata = UserDataPointer(userdata); + let userdata = Userdata::new(userdata, drop); Box::into_raw(hyper_task::boxed(async move { while let Some(item) = body.0.frame().await { let frame = item?; if let Ok(chunk) = frame.into_data() { - if HYPER_ITER_CONTINUE != func(userdata.0, &hyper_buf(chunk)) { + if HYPER_ITER_CONTINUE != func(userdata.as_ptr(), &hyper_buf(chunk)) { return Err(crate::Error::new_user_aborted_by_callback()); } } @@ -108,9 +109,9 @@ ffi_fn! { ffi_fn! { /// Set userdata on this body, which will be passed to callback functions. - fn hyper_body_set_userdata(body: *mut hyper_body, userdata: *mut c_void) { + fn hyper_body_set_userdata(body: *mut hyper_body, userdata: *mut c_void, drop: hyper_userdata_drop) { let b = non_null!(&mut *body ?= ()); - b.0.as_ffi_mut().userdata = userdata; + b.0.as_ffi_mut().userdata = Userdata::new(userdata, drop); } } @@ -146,7 +147,7 @@ impl UserBody { pub(crate) fn new() -> UserBody { UserBody { data_func: data_noop, - userdata: std::ptr::null_mut(), + userdata: Userdata::default(), } } @@ -155,7 +156,7 @@ impl UserBody { cx: &mut Context<'_>, ) -> Poll>>> { let mut out = std::ptr::null_mut(); - match (self.data_func)(self.userdata, hyper_context::wrap(cx), &mut out) { + match (self.data_func)(self.userdata.as_ptr(), hyper_context::wrap(cx), &mut out) { super::task::HYPER_POLL_READY => { if out.is_null() { Poll::Ready(None) diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 986e2c80c7..2aba01681c 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -5,7 +5,8 @@ use std::ffi::c_void; use super::body::hyper_body; use super::error::hyper_code; use super::task::{hyper_task_return_type, AsTaskType}; -use super::{UserDataPointer, HYPER_ITER_CONTINUE}; +use super::HYPER_ITER_CONTINUE; +use super::userdata::{Userdata, hyper_userdata_drop}; use crate::body::Incoming as IncomingBody; use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase}; use crate::header::{HeaderName, HeaderValue}; @@ -28,7 +29,7 @@ pub struct hyper_headers { pub(crate) struct OnInformational { func: hyper_request_on_informational_callback, - data: UserDataPointer, + userdata: Userdata, } type hyper_request_on_informational_callback = extern "C" fn(*mut c_void, *mut hyper_response); @@ -337,10 +338,10 @@ ffi_fn! { /// NOTE: The `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 { + fn hyper_request_on_informational(req: *mut hyper_request, callback: hyper_request_on_informational_callback, data: *mut c_void, drop: hyper_userdata_drop) -> hyper_code { let ext = OnInformational { func: callback, - data: UserDataPointer(data), + userdata: Userdata::new(data, drop), }; let req = non_null!(&mut *req ?= hyper_code::HYPERE_INVALID_ARG); req.0.extensions_mut().insert(ext); @@ -724,7 +725,7 @@ unsafe fn raw_name_value( impl OnInformational { pub(crate) fn call(&mut self, resp: Response) { let mut resp = hyper_response::from(resp); - (self.func)(self.data.0, &mut resp); + (self.func)(self.userdata.as_ptr(), &mut resp); } } diff --git a/src/ffi/io.rs b/src/ffi/io.rs index b0e4ca7627..2347c025c2 100644 --- a/src/ffi/io.rs +++ b/src/ffi/io.rs @@ -6,6 +6,7 @@ use libc::size_t; use tokio::io::{AsyncRead, AsyncWrite}; use super::task::hyper_context; +use super::userdata::Userdata; /// Sentinel value to return from a read or write callback that the operation /// is pending. @@ -18,14 +19,12 @@ type hyper_io_read_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *mut u8, size_t) -> size_t; type hyper_io_write_callback = extern "C" fn(*mut c_void, *mut hyper_context<'_>, *const u8, size_t) -> size_t; -type hyper_io_userdata_drop_callback = extern "C" fn(*mut c_void); /// An IO object used to represent a socket or similar concept. pub struct hyper_io { read: hyper_io_read_callback, write: hyper_io_write_callback, - userdata: *mut c_void, - userdata_drop: Option, + userdata: Userdata, } ffi_fn! { @@ -37,8 +36,7 @@ ffi_fn! { Box::into_raw(Box::new(hyper_io { read: read_noop, write: write_noop, - userdata: std::ptr::null_mut(), - userdata_drop: None, + userdata: Userdata::default(), })) } ?= std::ptr::null_mut() } @@ -64,15 +62,10 @@ ffi_fn! { fn hyper_io_set_userdata( io: *mut hyper_io, data: *mut c_void, - drop_func: hyper_io_userdata_drop_callback, + drop_func: super::userdata::hyper_userdata_drop, ) { let io = non_null!(&mut *io? = ()); - io.userdata = data; - io.userdata_drop = if (drop_func as *const c_void).is_null() { - None - } else { - Some(drop_func) - } + io.userdata = Userdata::new(data, drop_func); } } @@ -83,7 +76,7 @@ ffi_fn! { /// /// Returns NULL if no userdata has been set. fn hyper_io_get_userdata(io: *mut hyper_io) -> *mut c_void { - non_null!(&mut *io ?= std::ptr::null_mut()).userdata + non_null!(&mut *io ?= std::ptr::null_mut()).userdata.as_ptr() } } @@ -148,14 +141,6 @@ extern "C" fn write_noop( 0 } -impl Drop for hyper_io { - fn drop(&mut self) { - if let Some(drop_fn) = self.userdata_drop { - drop_fn(self.userdata) - } - } -} - impl AsyncRead for hyper_io { fn poll_read( self: Pin<&mut Self>, @@ -165,7 +150,7 @@ impl AsyncRead for hyper_io { let buf_ptr = unsafe { buf.unfilled_mut() }.as_mut_ptr() as *mut u8; let buf_len = buf.remaining(); - match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) { + match (self.read)(self.userdata.as_ptr(), hyper_context::wrap(cx), buf_ptr, buf_len) { HYPER_IO_PENDING => Poll::Pending, HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::Other, @@ -191,7 +176,7 @@ impl AsyncWrite for hyper_io { let buf_ptr = buf.as_ptr(); let buf_len = buf.len(); - match (self.write)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) { + match (self.write)(self.userdata.as_ptr(), hyper_context::wrap(cx), buf_ptr, buf_len) { HYPER_IO_PENDING => Poll::Pending, HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::Other, diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 08bd59df36..d484f1e8a7 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -45,6 +45,7 @@ mod http_types; mod io; mod task; mod time; +mod userdata; pub use self::body::*; pub use self::client::*; @@ -57,7 +58,6 @@ pub use self::task::*; /// Return in iter functions to continue iterating. pub const HYPER_ITER_CONTINUE: libc::c_int = 0; /// Return in iter functions to stop iterating. -#[allow(unused)] pub const HYPER_ITER_BREAK: libc::c_int = 1; /// An HTTP Version that is unspecified. @@ -69,14 +69,6 @@ pub const HYPER_HTTP_VERSION_1_1: libc::c_int = 11; /// The HTTP/2 version. pub const HYPER_HTTP_VERSION_2: libc::c_int = 20; -#[derive(Clone, Copy)] -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/ffi/server.rs b/src/ffi/server.rs index f5115f069a..8a9afcb8c0 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -7,7 +7,7 @@ use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::io::hyper_io; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; -use crate::ffi::UserDataPointer; +use crate::ffi::userdata::{Userdata, hyper_userdata_drop}; use crate::server::conn::http1; use crate::server::conn::http2; @@ -20,8 +20,7 @@ pub struct hyper_http2_serverconn_options(http2::Builder); /// A service that can serve a single server connection. pub struct hyper_service { service_fn: hyper_service_callback, - userdata: UserDataPointer, - userdata_drop: Option, + userdata: Userdata, } /// A channel on which to send back a response to complete a transaction for a service. @@ -43,12 +42,6 @@ pub struct hyper_response_channel(futures_channel::oneshot::Sender *mut hyper_service { Box::into_raw(Box::new(hyper_service { service_fn: service_fn, - userdata: UserDataPointer(ptr::null_mut()), - userdata_drop: None, + userdata: Userdata::default(), })) } ?= ptr::null_mut() } @@ -415,16 +407,12 @@ ffi_fn! { /// /// The service takes ownership of the userdata and will call the `drop_userdata` callback when /// the service task is complete. If the `drop_userdata` callback is `NULL` then the service - /// will instead forget the userdata when the associated task is completed and the calling code - /// is responsible for cleaning up the userdata through some other mechanism. - fn hyper_service_set_userdata(service: *mut hyper_service, userdata: *mut c_void, drop_func: hyper_service_userdata_drop_callback) { + /// will instead borrow the userdata and forget it when the associated task is completed and + /// thus the calling code is responsible for cleaning up the userdata through some other + /// mechanism. + fn hyper_service_set_userdata(service: *mut hyper_service, userdata: *mut c_void, drop: hyper_userdata_drop) { let s = non_null! { &mut *service ?= () }; - s.userdata = UserDataPointer(userdata); - s.userdata_drop = if (drop_func as *const c_void).is_null() { - None - } else { - Some(drop_func) - } + s.userdata = Userdata::new(userdata, drop); } } @@ -536,7 +524,7 @@ impl crate::service::Service> for hyper_service { let (tx, rx) = futures_channel::oneshot::channel(); let rsp_channel = Box::into_raw(Box::new(hyper_response_channel(tx))); - (self.service_fn)(self.userdata.0, req_ptr, rsp_channel); + (self.service_fn)(self.userdata.as_ptr(), req_ptr, rsp_channel); Box::pin(async move { let rsp = rx.await.expect("Channel closed?"); @@ -545,14 +533,6 @@ impl crate::service::Service> for hyper_service { } } -impl Drop for hyper_service { - fn drop(&mut self) { - if let Some(drop_func) = self.userdata_drop { - drop_func(self.userdata.0); - } - } -} - enum AutoConnection where Serv: crate::service::HttpService, diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 6ac126b79b..d226b01bb7 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -12,7 +12,7 @@ use futures_util::stream::{FuturesUnordered, Stream}; use libc::c_int; use super::error::hyper_code; -use super::UserDataPointer; +use super::userdata::{Userdata, hyper_userdata_drop}; use crate::proto::h2::server::H2Stream; @@ -64,7 +64,7 @@ struct ExecWaker(AtomicBool); pub struct hyper_task { future: BoxFuture, output: Option, - userdata: UserDataPointer, + userdata: Userdata, } struct TaskFuture { @@ -295,7 +295,7 @@ impl hyper_task { Box::new(hyper_task { future: Box::pin(async move { fut.await.into_dyn_task_type() }), output: None, - userdata: UserDataPointer(ptr::null_mut()), + userdata: Userdata::default(), }) } @@ -367,19 +367,16 @@ ffi_fn! { /// /// This value will be passed to task callbacks, and can be checked later /// with `hyper_task_userdata`. - fn hyper_task_set_userdata(task: *mut hyper_task, userdata: *mut c_void) { - if task.is_null() { - return; - } - - unsafe { (*task).userdata = UserDataPointer(userdata) }; + fn hyper_task_set_userdata(task: *mut hyper_task, userdata: *mut c_void, drop: hyper_userdata_drop) { + let task = non_null!(&mut*task ?= ()); + task.userdata = Userdata::new(userdata, drop); } } ffi_fn! { /// Retrieve the userdata that has been set via `hyper_task_set_userdata`. fn hyper_task_userdata(task: *mut hyper_task) -> *mut c_void { - non_null!(&*task ?= ptr::null_mut()).userdata.0 + non_null!(&*task ?= ptr::null_mut()).userdata.as_ptr() } ?= ptr::null_mut() } diff --git a/src/ffi/userdata.rs b/src/ffi/userdata.rs new file mode 100644 index 0000000000..c0d73494ca --- /dev/null +++ b/src/ffi/userdata.rs @@ -0,0 +1,57 @@ +use std::ffi::c_void; + +/// Many hyper entites can be given userdata to allow user callbacks to corellate work together. +/// Since much of hyper is asychronous it's often useful to treat these userdata objects as "owned" +/// by the hyper entity (and hence to be cleaned up when that entity is dropped). +/// +/// To acheive this a `hyepr_userdata_drop` callback is passed by calling code alongside the +/// userdata to register a cleanup function. +/// +/// This function may be provided as NULL if the calling code wants to manage memory lifetimes +/// itself, in which case the hyper object will logically consider the userdata "borrowed" until +/// the hyper entity is dropped. +pub type hyper_userdata_drop = extern "C" fn(*mut c_void); + +/// A handle to a user-provided arbitrary object, along with an optional drop callback for the +/// object. +pub(crate) struct Userdata { + data: *mut c_void, + drop: Option, +} + +impl Userdata { + pub(crate) fn new(data: *mut c_void, drop: hyper_userdata_drop) -> Self { + Self { + data, + drop: if (drop as *const c_void).is_null() { + None + } else { + Some(drop) + } + } + } + + pub(crate) fn as_ptr(&self) -> *mut c_void { + self.data + } +} + +impl Default for Userdata { + fn default() -> Self { + Self { + data: std::ptr::null_mut(), + drop: None, + } + } +} + +unsafe impl Sync for Userdata {} +unsafe impl Send for Userdata {} + +impl Drop for Userdata { + fn drop(&mut self) { + if let Some(drop) = self.drop { + drop(self.data); + } + } +} From 9116dbc1c82e840412b997f5991d1c8ca26a3b44 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Fri, 17 Mar 2023 17:43:49 +0000 Subject: [PATCH 46/54] Make hyper_userdata_drop visible in rust as well as C code --- src/ffi/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index d484f1e8a7..37183cac3c 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -54,6 +54,7 @@ pub use self::http_types::*; pub use self::io::*; pub use self::server::*; pub use self::task::*; +pub use self::userdata::hyper_userdata_drop; /// Return in iter functions to continue iterating. pub const HYPER_ITER_CONTINUE: libc::c_int = 0; From d2e23153cf23a612e7e937a00ffaa21f5cc2fb4d Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Wed, 12 Apr 2023 17:55:41 +0100 Subject: [PATCH 47/54] Synchronize epoll event masks with hyper IO intents --- capi/examples/server.c | 71 +++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 587ca828d9..7aff83125a 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -19,6 +19,7 @@ static const int MAX_EVENTS = 128; typedef struct conn_data_s { int fd; int epoll_fd; + uint32_t event_mask; hyper_waker *read_waker; hyper_waker *write_waker; } conn_data; @@ -62,6 +63,7 @@ static int listen_on(const char *host, const char *port) { if (bind(sock, resp->ai_addr, resp->ai_addrlen) == 0) { break; } + perror("bind"); // Failed, tidy up close(sock); @@ -108,6 +110,7 @@ static int register_signal_handler() { perror("signalfd"); return 1; } + sigaddset(&mask, SIGPIPE); if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) { perror("sigprocmask"); return 1; @@ -116,6 +119,19 @@ static int register_signal_handler() { return signal_fd; } +// Register connection FD with epoll, associated with this `conn` +static bool update_conn_data_registrations(conn_data* conn, bool create) { + struct epoll_event transport_event; + transport_event.events = conn->event_mask; + transport_event.data.ptr = conn; + if (epoll_ctl(conn->epoll_fd, create ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, conn->fd, &transport_event) < 0) { + perror("epoll_ctl (transport)"); + return false; + } else { + return true; + } +} + static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) { conn_data *conn = (conn_data *)userdata; ssize_t ret = read(conn->fd, buf, buf_len); @@ -134,6 +150,14 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b if (conn->read_waker != NULL) { hyper_waker_free(conn->read_waker); } + + if (!(conn->event_mask & EPOLLIN)) { + conn->event_mask |= EPOLLIN; + if (!update_conn_data_registrations(conn, false)) { + return HYPER_IO_ERROR; + } + } + conn->read_waker = hyper_context_waker(ctx); return HYPER_IO_PENDING; } @@ -156,28 +180,31 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s if (conn->write_waker != NULL) { hyper_waker_free(conn->write_waker); } + + if (!(conn->event_mask & EPOLLOUT)) { + conn->event_mask |= EPOLLOUT; + if (!update_conn_data_registrations(conn, false)) { + return HYPER_IO_ERROR; + } + } + conn->write_waker = hyper_context_waker(ctx); return HYPER_IO_PENDING; } static conn_data *create_conn_data(int epoll, int fd) { conn_data *conn = malloc(sizeof(conn_data)); - - // Add fd to epoll set, associated with this `conn` - struct epoll_event transport_event; - transport_event.events = EPOLLIN; - transport_event.data.ptr = conn; - if (epoll_ctl(epoll, EPOLL_CTL_ADD, fd, &transport_event) < 0) { - perror("epoll_ctl (transport, add)"); - free(conn); - return NULL; - } - conn->fd = fd; conn->epoll_fd = epoll; + conn->event_mask = 0; conn->read_waker = NULL; conn->write_waker = NULL; + if (!update_conn_data_registrations(conn, true)) { + free(conn); + return NULL; + } + return conn; } @@ -477,13 +504,27 @@ int main(int argc, char *argv[]) { } else { // Existing transport socket, poke the wakers or close the socket conn_data *conn = events[n].data.ptr; - if ((events[n].events & EPOLLIN) && conn->read_waker) { + if (events[n].events & EPOLLIN) { + if (conn->read_waker) { hyper_waker_wake(conn->read_waker); conn->read_waker = NULL; + } else { + conn->event_mask &= ~EPOLLIN; + if (!update_conn_data_registrations(conn, false)) { + epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + } + } } - if ((events[n].events & EPOLLOUT) && conn->write_waker) { - hyper_waker_wake(conn->write_waker); - conn->write_waker = NULL; + if (events[n].events & EPOLLOUT) { + if (conn->read_waker) { + hyper_waker_wake(conn->read_waker); + conn->read_waker = NULL; + } else { + conn->event_mask &= ~EPOLLOUT; + if (!update_conn_data_registrations(conn, false)) { + epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + } + } } } } From c73a994856e302071cbb2168c3db3024a713cc43 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 13 Apr 2023 15:07:42 +0100 Subject: [PATCH 48/54] Fix copy-paste error --- capi/examples/server.c | 51 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 7aff83125a..39f089f8f8 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -246,6 +246,7 @@ static hyper_io *create_io(conn_data *conn) { typedef struct service_userdata_s { char host[128]; char port[8]; + const hyper_executor* executor; } service_userdata; static service_userdata* create_service_userdata() { @@ -264,6 +265,26 @@ static int print_each_header( return HYPER_ITER_CONTINUE; } +static int print_body_chunk(void *userdata, const hyper_buf *chunk) { + const uint8_t *buf = hyper_buf_bytes(chunk); + size_t len = hyper_buf_len(chunk); + write(1, buf, len); + return HYPER_ITER_CONTINUE; +} + +static int send_each_body_chunk(void* userdata, hyper_context* ctx, hyper_buf **chunk) { + int* chunk_count = (int*)userdata; + if (*chunk_count > 0) { + unsigned char data[4096]; + memset(data, '0' + (*chunk_count % 10), sizeof(data)); + *chunk = hyper_buf_copy(data, sizeof(data)); + (*chunk_count)--; + } else { + *chunk = NULL; + } + return HYPER_POLL_READY; +} + static void server_callback( void *userdata, hyper_request *request, hyper_response_channel *channel ) { @@ -301,11 +322,20 @@ static void server_callback( // Print out all the headers from the request hyper_headers *req_headers = hyper_request_headers(request); hyper_headers_foreach(req_headers, print_each_header, NULL); + + if (!strcmp((char*)method, "POST") || !strcmp((char*)method, "PUT")) { + // ...consume the request body + hyper_body* body = hyper_request_body(request); + hyper_task* task = hyper_body_foreach(body, print_body_chunk, NULL, NULL); + hyper_executor_push(service_data->executor, task); + } + + // Tidy up hyper_request_free(request); // Build a response hyper_response *response = hyper_response_new(); - hyper_response_set_status(response, 404); + hyper_response_set_status(response, 200); hyper_headers* rsp_headers = hyper_response_headers(response); hyper_headers_set( rsp_headers, @@ -315,7 +345,17 @@ static void server_callback( 8 ); - // And send the response, completing the transaction + if (!strcmp((char*)method, "GET")) { + // ...add a body + hyper_body* body = hyper_body_new(); + hyper_body_set_data_func(body, send_each_body_chunk); + int* chunk_count = (int*)malloc(sizeof(int)); + *chunk_count = 1000; + hyper_body_set_userdata(body, (void*)chunk_count, free); + hyper_response_set_body(response, body); + } + + // ...and send the response, completing the transaction hyper_response_channel_send(channel, response); } @@ -439,6 +479,7 @@ int main(int argc, char *argv[]) { listen_fd, (struct sockaddr *)&remote_addr_storage, &remote_addr_len )) >= 0) { service_userdata *userdata = create_service_userdata(); + userdata->executor = exec; if (getnameinfo( remote_addr, remote_addr_len, @@ -516,9 +557,9 @@ int main(int argc, char *argv[]) { } } if (events[n].events & EPOLLOUT) { - if (conn->read_waker) { - hyper_waker_wake(conn->read_waker); - conn->read_waker = NULL; + if (conn->write_waker) { + hyper_waker_wake(conn->write_waker); + conn->write_waker = NULL; } else { conn->event_mask &= ~EPOLLOUT; if (!update_conn_data_registrations(conn, false)) { From caafc3ec2e2bd161917a02e66b28b9ba15d653c5 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 12 Oct 2023 19:36:06 +0100 Subject: [PATCH 49/54] Appease the formatting gods --- src/ffi/body.rs | 2 +- src/ffi/client.rs | 2 +- src/ffi/http_types.rs | 3 +-- src/ffi/io.rs | 16 +++++++++++++--- src/ffi/macros.rs | 4 +++- src/ffi/mod.rs | 2 +- src/ffi/server.rs | 2 +- src/ffi/task.rs | 2 +- src/ffi/time.rs | 4 ++-- src/ffi/userdata.rs | 5 +---- 10 files changed, 25 insertions(+), 17 deletions(-) diff --git a/src/ffi/body.rs b/src/ffi/body.rs index 89f1ad3dd3..fa99e61803 100644 --- a/src/ffi/body.rs +++ b/src/ffi/body.rs @@ -7,8 +7,8 @@ use http_body_util::BodyExt as _; use libc::{c_int, size_t}; use super::task::{hyper_context, hyper_task, hyper_task_return_type, AsTaskType}; +use super::userdata::{hyper_userdata_drop, Userdata}; use super::HYPER_ITER_CONTINUE; -use super::userdata::{Userdata, hyper_userdata_drop}; use crate::body::{Bytes, Frame, Incoming as IncomingBody}; /// A streaming HTTP body. diff --git a/src/ffi/client.rs b/src/ffi/client.rs index d1c078cadc..d58a0e2449 100644 --- a/src/ffi/client.rs +++ b/src/ffi/client.rs @@ -1,6 +1,6 @@ +use std::pin::Pin; use std::ptr; use std::sync::Arc; -use std::pin::Pin; use libc::c_int; diff --git a/src/ffi/http_types.rs b/src/ffi/http_types.rs index 22f2cd700f..ae4f3f7ea9 100644 --- a/src/ffi/http_types.rs +++ b/src/ffi/http_types.rs @@ -5,8 +5,8 @@ use std::ffi::c_void; use super::body::hyper_body; use super::error::hyper_code; use super::task::{hyper_task_return_type, AsTaskType}; +use super::userdata::{hyper_userdata_drop, Userdata}; use super::HYPER_ITER_CONTINUE; -use super::userdata::{Userdata, hyper_userdata_drop}; use crate::body::Incoming as IncomingBody; use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase}; use crate::header::{HeaderName, HeaderValue}; @@ -574,7 +574,6 @@ impl From> for hyper_response { } } - unsafe impl AsTaskType for hyper_response { fn as_task_type(&self) -> hyper_task_return_type { hyper_task_return_type::HYPER_TASK_RESPONSE diff --git a/src/ffi/io.rs b/src/ffi/io.rs index 0d297f8c15..c368cdcc9a 100644 --- a/src/ffi/io.rs +++ b/src/ffi/io.rs @@ -6,7 +6,7 @@ use crate::rt::{Read, Write}; use libc::size_t; use super::task::hyper_context; -use super::userdata::{Userdata, hyper_userdata_drop}; +use super::userdata::{hyper_userdata_drop, Userdata}; /// Sentinel value to return from a read or write callback that the operation /// is pending. @@ -153,7 +153,12 @@ impl Read for hyper_io { let buf_ptr = unsafe { buf.as_mut() }.as_mut_ptr() as *mut u8; let buf_len = buf.remaining(); - match (self.read)(self.userdata.as_ptr(), hyper_context::wrap(cx), buf_ptr, buf_len) { + match (self.read)( + self.userdata.as_ptr(), + hyper_context::wrap(cx), + buf_ptr, + buf_len, + ) { HYPER_IO_PENDING => Poll::Pending, HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::Other, @@ -178,7 +183,12 @@ impl Write for hyper_io { let buf_ptr = buf.as_ptr(); let buf_len = buf.len(); - match (self.write)(self.userdata.as_ptr(), hyper_context::wrap(cx), buf_ptr, buf_len) { + match (self.write)( + self.userdata.as_ptr(), + hyper_context::wrap(cx), + buf_ptr, + buf_len, + ) { HYPER_IO_PENDING => Poll::Pending, HYPER_IO_ERROR => Poll::Ready(Err(std::io::Error::new( std::io::ErrorKind::Other, diff --git a/src/ffi/macros.rs b/src/ffi/macros.rs index 5147522943..b8937b0aa2 100644 --- a/src/ffi/macros.rs +++ b/src/ffi/macros.rs @@ -37,7 +37,9 @@ macro_rules! non_null { return $err; } #[allow(unused_unsafe)] - unsafe { $eval } + unsafe { + $eval + } }}; (*$ptr:ident ?= $err:expr) => {{ non_null!($ptr, *$ptr, $err) diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 6213b85e6f..e8a261c261 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -39,10 +39,10 @@ mod macros; mod body; mod client; -mod server; mod error; mod http_types; mod io; +mod server; mod task; mod time; mod userdata; diff --git a/src/ffi/server.rs b/src/ffi/server.rs index 22e656e34d..9ece78652c 100644 --- a/src/ffi/server.rs +++ b/src/ffi/server.rs @@ -7,7 +7,7 @@ use crate::ffi::error::hyper_code; use crate::ffi::http_types::{hyper_request, hyper_response}; use crate::ffi::io::hyper_io; use crate::ffi::task::{hyper_executor, hyper_task, WeakExec}; -use crate::ffi::userdata::{Userdata, hyper_userdata_drop}; +use crate::ffi::userdata::{hyper_userdata_drop, Userdata}; use crate::server::conn::http1; use crate::server::conn::http2; diff --git a/src/ffi/task.rs b/src/ffi/task.rs index 3afdf419c8..5a020e071d 100644 --- a/src/ffi/task.rs +++ b/src/ffi/task.rs @@ -12,7 +12,7 @@ use futures_util::stream::{FuturesUnordered, Stream}; use libc::c_int; use super::error::hyper_code; -use super::userdata::{Userdata, hyper_userdata_drop}; +use super::userdata::{hyper_userdata_drop, Userdata}; type BoxFuture = Pin + Send>>; type BoxAny = Box; diff --git a/src/ffi/time.rs b/src/ffi/time.rs index bf9b19dede..f3f2747752 100644 --- a/src/ffi/time.rs +++ b/src/ffi/time.rs @@ -1,8 +1,8 @@ +use std::collections::binary_heap::{BinaryHeap, PeekMut}; use std::pin::Pin; use std::sync::{Arc, Mutex}; use std::task::{Context, Poll, Waker}; -use std::time::{Instant, Duration}; -use std::collections::binary_heap::{BinaryHeap, PeekMut}; +use std::time::{Duration, Instant}; /// A heap of timer entries with their associated wakers, backing `TimerFuture` instances. pub(super) struct TimerHeap(BinaryHeap); diff --git a/src/ffi/userdata.rs b/src/ffi/userdata.rs index 9197e407dd..78132cd176 100644 --- a/src/ffi/userdata.rs +++ b/src/ffi/userdata.rs @@ -21,10 +21,7 @@ pub(crate) struct Userdata { impl Userdata { pub(crate) fn new(data: *mut c_void, drop: hyper_userdata_drop) -> Self { - Self { - data, - drop, - } + Self { data, drop } } pub(crate) fn as_ptr(&self) -> *mut c_void { From 0abf7e6c9ea0d393d53c6dc333ca7804f8a6685b Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 12 Oct 2023 19:40:26 +0100 Subject: [PATCH 50/54] Apparently the new cbindgen uses a different ordering --- capi/include/hyper.h | 812 +++++++++++++++++++++---------------------- 1 file changed, 406 insertions(+), 406 deletions(-) diff --git a/capi/include/hyper.h b/capi/include/hyper.h index cc97c84e3c..8cc7b40e89 100644 --- a/capi/include/hyper.h +++ b/capi/include/hyper.h @@ -249,6 +249,14 @@ typedef void (*hyper_userdata_drop)(void*); typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**); +typedef void (*hyper_request_on_informational_callback)(void*, 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); + +typedef size_t (*hyper_io_write_callback)(void*, struct hyper_context*, const uint8_t*, size_t); + /* The main definition of a service. This callback will be invoked for each transaction on the connection. @@ -266,14 +274,6 @@ typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyp */ typedef void (*hyper_service_callback)(void*, struct hyper_request*, struct hyper_response_channel*); -typedef void (*hyper_request_on_informational_callback)(void*, 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); - -typedef size_t (*hyper_io_write_callback)(void*, struct hyper_context*, const uint8_t*, size_t); - #ifdef __cplusplus extern "C" { #endif // __cplusplus @@ -501,661 +501,661 @@ enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hy int enabled); /* - Create a new HTTP/1 serverconn options object. + Frees a `hyper_error`. + + This should be used for any error once it is no longer needed. */ -struct hyper_http1_serverconn_options *hyper_http1_serverconn_options_new(const struct hyper_executor *exec); +void hyper_error_free(struct hyper_error *err); /* - Free a `hyper_http1_serverconn_options*`. + Get an equivalent `hyper_code` from this error. */ -void hyper_http1_serverconn_options_free(struct hyper_http1_serverconn_options *opts); +enum hyper_code hyper_error_code(const struct hyper_error *err); /* - Set whether HTTP/1 connections should support half-closures. + Print the details of this error to a buffer. - Clients can chose to shutdown their write-side while waiting for the server to respond. - Setting this to true will prevent closing the connection immediately if read detects an EOF - in the middle of a request. + The `dst_len` value must be the maximum length that the buffer can + store. - Default is `false` + The return value is number of bytes that were written to `dst`. */ -enum hyper_code hyper_http1_serverconn_options_half_close(struct hyper_http1_serverconn_options *opts, - bool enabled); +size_t hyper_error_print(const struct hyper_error *err, uint8_t *dst, size_t dst_len); /* - Enables or disables HTTP/1 keep-alive. + Construct a new HTTP request. - Default is `true`. + To avoid a memory leak, the request must eventually be consumed by + `hyper_request_free` or `hyper_clientconn_send`. */ -enum hyper_code hyper_http1_serverconn_options_keep_alive(struct hyper_http1_serverconn_options *opts, - bool enabled); +struct hyper_request *hyper_request_new(void); /* - Set whether HTTP/1 connections will write header names as title case at the socket level. + Free an HTTP request. - Default is `false`. + This should only be used if the request isn't consumed by + `hyper_clientconn_send`. */ -enum hyper_code hyper_http1_serverconn_options_title_case_headers(struct hyper_http1_serverconn_options *opts, - bool enabled); +void hyper_request_free(struct hyper_request *req); /* - Set whether to support preserving original header cases. - - Currently, this will record the original cases received, and store them in a private - extension on the Request. It will also look for and use such an extension in any provided - Response. - - Since the relevant extension is still private, there is no way to interact with the - original cases. The only effect this can have now is to forward the cases in a proxy-like - fashion. - - Default is `false`. + Set the HTTP Method of the request. */ -enum hyper_code hyper_http1_serverconn_options_preserve_header_case(struct hyper_http1_serverconn_options *opts, - bool enabled); +enum hyper_code hyper_request_set_method(struct hyper_request *req, + const uint8_t *method, + size_t method_len); /* - Set a timeout for reading client request headers. If a client does not - transmit the entire header within this time, the connection is closed. + Get the HTTP Method of the request. - Default is to have no timeout. + `method` must be a pointer to a buffer that this function will populate with the HTTP + method of the request. The `header_len` argument must be a pointer to a `size_t` which, on + call, is populated with the maximum length of the `method` buffer and, on successful + response, will be set to the actual length of the value written into the buffer. */ -enum hyper_code hyper_http1_serverconn_options_header_read_timeout(struct hyper_http1_serverconn_options *opts, - uint64_t millis); +enum hyper_code hyper_request_method(const struct hyper_request *req, + uint8_t *method, + size_t *method_len); /* - Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a - single buffer. + Set the URI of the request. - Note that setting this to false may mean more copies of body data, but may also improve - performance when an IO transport doesn’t support vectored writes well, such as most TLS - implementations. + The request's URI is best described as the `request-target` from the RFCs. So in HTTP/1, + whatever is set will get sent as-is in the first line (GET $uri HTTP/1.1). It + supports the 4 defined variants, origin-form, absolute-form, authority-form, and + asterisk-form. - Setting this to true will force hyper to use queued strategy which may eliminate - unnecessary cloning on some TLS backends. + The underlying type was built to efficiently support HTTP/2 where the request-target is + split over :scheme, :authority, and :path. As such, each part can be set explicitly, or the + type can parse a single contiguous string and if a scheme is found, that slot is "set". If + the string just starts with a path, only the path portion is set. All pseudo headers that + have been parsed/set are sent when the connection type is HTTP/2. - Default is to automatically guess which mode to use, this function overrides the heuristic. + To set each slot explicitly, use `hyper_request_set_uri_parts`. */ -enum hyper_code hyper_http1_serverconn_options_writev(struct hyper_http1_serverconn_options *opts, - bool enabled); +enum hyper_code hyper_request_set_uri(struct hyper_request *req, + const uint8_t *uri, + size_t uri_len); /* - Set the maximum buffer size for the HTTP/1 connection. Must be no lower `8192`. + Set the URI of the request with separate scheme, authority, and + path/query strings. - Default is a sensible value. + Each of `scheme`, `authority`, and `path_and_query` should either be + null, to skip providing a component, or point to a UTF-8 encoded + string. If any string pointer argument is non-null, its corresponding + `len` parameter must be set to the string's length. */ -enum hyper_code hyper_http1_serverconn_options_max_buf_size(struct hyper_http1_serverconn_options *opts, - uintptr_t max_buf_size); +enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req, + const uint8_t *scheme, + size_t scheme_len, + const uint8_t *authority, + size_t authority_len, + const uint8_t *path_and_query, + size_t path_and_query_len); /* - Aggregates flushes to better support pipelined responses. + Get the URI of the request split into scheme, authority and path/query strings. - Experimental, may have bugs. + Each of `scheme`, `authority` and `path_and_query` may be pointers to buffers that this + function will populate with the appropriate values from the request. If one of these + pointers is non-NULL then the associated `_len` field must be a pointer to a `size_t` + which, on call, is populated with the maximum length of the buffer and, on successful + response, will be set to the actual length of the value written into the buffer. - Default is `false`. - */ -enum hyper_code hyper_http1_serverconn_options_pipeline_flush(struct hyper_http1_serverconn_options *opts, - bool enabled); + If a buffer is passed as `NULL` then the `_len` field will be ignored and that component + will be skipped. -/* - Create a new HTTP/2 serverconn options object bound to the provided executor. + This function may fail with `HYPERE_INSUFFICIENT_SPACE` if one of the provided buffers is + not long enough to hold the value from the request. */ -struct hyper_http2_serverconn_options *hyper_http2_serverconn_options_new(const struct hyper_executor *exec); +enum hyper_code hyper_request_uri_parts(const struct hyper_request *req, + uint8_t *scheme, + size_t *scheme_len, + uint8_t *authority, + size_t *authority_len, + uint8_t *path_and_query, + size_t *path_and_query_len); /* - Free a `hyper_http2_serverconn_options*`. - */ -void hyper_http2_serverconn_options_free(struct hyper_http2_serverconn_options *opts); + Set the preferred HTTP version of the request. -/* - Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. + The version value should be one of the `HYPER_HTTP_VERSION_` constants. - Passing `0` instructs hyper to use a sensible default value. + Note that this won't change the major HTTP version of the connection, + since that is determined at the handshake step. */ -enum hyper_code hyper_http2_serverconn_options_initial_stream_window_size(struct hyper_http2_serverconn_options *opts, - unsigned int window_size); +enum hyper_code hyper_request_set_version(struct hyper_request *req, int version); /* - Sets the max connection-level flow control for HTTP/2. + Get the HTTP version used by this request. - Passing `0` instructs hyper to use a sensible default value. + The returned value could be: + + - `HYPER_HTTP_VERSION_1_0` + - `HYPER_HTTP_VERSION_1_1` + - `HYPER_HTTP_VERSION_2` + - `HYPER_HTTP_VERSION_NONE` if newer (or older). */ -enum hyper_code hyper_http2_serverconn_options_initial_connection_window_size(struct hyper_http2_serverconn_options *opts, - unsigned int window_size); +int hyper_request_version(const struct hyper_request *resp); /* - Sets whether to use an adaptive flow control. - - Enabling this will override the limits set in http2_initial_stream_window_size and - http2_initial_connection_window_size. + Gets a reference to the HTTP headers of this request - Default is `false`. + This is not an owned reference, so it should not be accessed after the + `hyper_request` has been consumed. */ -enum hyper_code hyper_http2_serverconn_options_adaptive_window(struct hyper_http2_serverconn_options *opts, - bool enabled); +struct hyper_headers *hyper_request_headers(struct hyper_request *req); /* - Sets the maximum frame size to use for HTTP/2. + Set the body of the request. - Passing `0` instructs hyper to use a sensible default value. + The default is an empty body. + + This takes ownership of the `hyper_body *`, you must not use it or + free it after setting it on the request. */ -enum hyper_code hyper_http2_serverconn_options_max_frame_size(struct hyper_http2_serverconn_options *opts, - unsigned int frame_size); +enum hyper_code hyper_request_set_body(struct hyper_request *req, struct hyper_body *body); /* - Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. + Take ownership of the body of this request. - Default is no limit (`std::u32::MAX`). Passing `0` will use this default. + It is safe to free the request even after taking ownership of its body. */ -enum hyper_code hyper_http2_serverconn_options_max_concurrent_streams(struct hyper_http2_serverconn_options *opts, - unsigned int max_streams); +struct hyper_body *hyper_request_body(struct hyper_request *req); /* - Sets an interval for HTTP/2 Ping frames should be sent to keep a connection alive. + Set an informational (1xx) response callback. - Default is to not use keep-alive pings. Passing `0` will use this default. - */ -enum hyper_code hyper_http2_serverconn_options_keep_alive_interval(struct hyper_http2_serverconn_options *opts, - uint64_t interval_seconds); + The callback is called each time hyper receives an informational (1xx) + response for this request. -/* - Sets a timeout for receiving an acknowledgement of the keep-alive ping. + The third argument is an opaque user data pointer, which is passed to + the callback each time. - If the ping is not acknowledged within the timeout, the connection will be closed. Does - nothing if `hyper_http2_serverconn_options_keep_alive_interval` is disabled. + 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. - Default is 20 seconds. + NOTE: The `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_http2_serverconn_options_keep_alive_timeout(struct hyper_http2_serverconn_options *opts, - uint64_t timeout_seconds); +enum hyper_code hyper_request_on_informational(struct hyper_request *req, + hyper_request_on_informational_callback callback, + void *data, + hyper_userdata_drop drop); /* - Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than - `u32::MAX`. - - Default is a sensible value. + Construct a new HTTP 200 Ok response */ -enum hyper_code hyper_http2_serverconn_options_max_send_buf_size(struct hyper_http2_serverconn_options *opts, - uintptr_t max_buf_size); +struct hyper_response *hyper_response_new(void); /* - Enables the extended `CONNECT` protocol. + Free an HTTP response. + + This should be used for any response once it is no longer needed. */ -enum hyper_code hyper_http2_serverconn_options_enable_connect_protocol(struct hyper_http2_serverconn_options *opts); +void hyper_response_free(struct hyper_response *resp); /* - Sets the max size of received header frames. + Get the HTTP-Status code of this response. - Default is a sensible value. + It will always be within the range of 100-599. */ -enum hyper_code hyper_http2_serverconn_options_max_header_list_size(struct hyper_http2_serverconn_options *opts, - uint32_t max); +uint16_t hyper_response_status(const struct hyper_response *resp); /* - Create a service from a wrapped callback function. + Set the HTTP Status-Code of this response. */ -struct hyper_service *hyper_service_new(hyper_service_callback service_fn); +void hyper_response_set_status(struct hyper_response *resp, uint16_t status); /* - Register opaque userdata with the `hyper_service`. This userdata must be `Send` in a rust - sense (i.e. can be passed between threads) though it doesn't have to be thread-safe (it - won't be accessed from multiple thread concurrently). + Get a pointer to the reason-phrase of this response. - The service takes ownership of the userdata and will call the `drop_userdata` callback when - the service task is complete. If the `drop_userdata` callback is `NULL` then the service - will instead borrow the userdata and forget it when the associated task is completed and - thus the calling code is responsible for cleaning up the userdata through some other - mechanism. + This buffer is not null-terminated. + + This buffer is owned by the response, and should not be used after + the response has been freed. + + Use `hyper_response_reason_phrase_len()` to get the length of this + buffer. */ -void hyper_service_set_userdata(struct hyper_service *service, - void *userdata, - hyper_userdata_drop drop); +const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp); /* - Frees a hyper_service object if no longer needed + Get the length of the reason-phrase of this response. + + Use `hyper_response_reason_phrase()` to get the buffer pointer. */ -void hyper_service_free(struct hyper_service *service); +size_t hyper_response_reason_phrase_len(const struct hyper_response *resp); /* - Serve the provided `hyper_service *` as an HTTP/1 endpoint over the provided `hyper_io *` - and configured as per the `hyper_http1_serverconn_options *`. + Set the preferred HTTP version of the response. - Returns a `hyper_task*` which must be given to an executor to make progress. + The version value should be one of the `HYPER_HTTP_VERSION_` constants. - This function consumes the IO and Service objects and thus they should not be accessed - after this function is called. + Note that this won't change the major HTTP version of the connection, + since that is determined at the handshake step. */ -struct hyper_task *hyper_serve_http1_connection(struct hyper_http1_serverconn_options *serverconn_options, - struct hyper_io *io, - struct hyper_service *service); +enum hyper_code hyper_response_set_version(struct hyper_response *req, int version); /* - Serve the provided `hyper_service *` as an HTTP/2 endpoint over the provided `hyper_io *` - and configured as per the `hyper_http2_serverconn_options *`. + Get the HTTP version used by this response. - Returns a `hyper_task*` which must be given to an executor to make progress. + The returned value could be: - This function consumes the IO and Service objects and thus they should not be accessed - after this function is called. + - `HYPER_HTTP_VERSION_1_0` + - `HYPER_HTTP_VERSION_1_1` + - `HYPER_HTTP_VERSION_2` + - `HYPER_HTTP_VERSION_NONE` if newer (or older). */ -struct hyper_task *hyper_serve_http2_connection(struct hyper_http2_serverconn_options *serverconn_options, - struct hyper_io *io, - struct hyper_service *service); +int hyper_response_version(const struct hyper_response *resp); /* - Serve the provided `hyper_service *` as either an HTTP/1 or HTTP/2 (depending on what the - client requests) endpoint over the provided `hyper_io *` and configured as per the - appropriate `hyper_httpX_serverconn_options *`. - - Returns a `hyper_task*` which must be given to an executor to make progress. + Gets a reference to the HTTP headers of this response. - This function consumes the IO and Service objects and thus they should not be accessed - after this function is called. + This is not an owned reference, so it should not be accessed after the + `hyper_response` has been freed. */ -struct hyper_task *hyper_serve_httpX_connection(struct hyper_http1_serverconn_options *http1_serverconn_options, - struct hyper_http2_serverconn_options *http2_serverconn_options, - struct hyper_io *io, - struct hyper_service *service); +struct hyper_headers *hyper_response_headers(struct hyper_response *resp); /* - Sends a `hyper_response*` back to the client. This function consumes the response and the - channel. + Set the body of the response. - See [hyper_service_callback] for details. + The default is an empty body. + + This takes ownership of the `hyper_body *`, you must not use it or + free it after setting it on the request. */ -void hyper_response_channel_send(struct hyper_response_channel *channel, - struct hyper_response *response); +enum hyper_code hyper_response_set_body(struct hyper_response *rsp, struct hyper_body *body); /* - Frees a `hyper_error`. + Take ownership of the body of this response. - This should be used for any error once it is no longer needed. + It is safe to free the response even after taking ownership of its body. + + To avoid a memory leak, the body must eventually be consumed by + `hyper_body_free`, `hyper_body_foreach`, or `hyper_request_set_body`. */ -void hyper_error_free(struct hyper_error *err); +struct hyper_body *hyper_response_body(struct hyper_response *resp); /* - Get an equivalent `hyper_code` from this error. + Iterates the headers passing each name and value pair to the callback. + + The `userdata` pointer is also passed to the callback. + + The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or + `HYPER_ITER_BREAK` to stop. */ -enum hyper_code hyper_error_code(const struct hyper_error *err); +void hyper_headers_foreach(const struct hyper_headers *headers, + hyper_headers_foreach_callback func, + void *userdata); /* - Print the details of this error to a buffer. + Sets the header with the provided name to the provided value. - The `dst_len` value must be the maximum length that the buffer can - store. + This overwrites any previous value set for the header. + */ +enum hyper_code hyper_headers_set(struct hyper_headers *headers, + const uint8_t *name, + size_t name_len, + const uint8_t *value, + size_t value_len); - The return value is number of bytes that were written to `dst`. +/* + Adds the provided value to the list of the provided name. + + If there were already existing values for the name, this will append the + new value to the internal list. */ -size_t hyper_error_print(const struct hyper_error *err, uint8_t *dst, size_t dst_len); +enum hyper_code hyper_headers_add(struct hyper_headers *headers, + const uint8_t *name, + size_t name_len, + const uint8_t *value, + size_t value_len); /* - Construct a new HTTP request. + Create a new IO type used to represent a transport. - To avoid a memory leak, the request must eventually be consumed by - `hyper_request_free` or `hyper_clientconn_send`. + The read and write functions of this transport should be set with + `hyper_io_set_read` and `hyper_io_set_write`. + + To avoid a memory leak, the IO handle must eventually be consumed by + `hyper_io_free` or `hyper_clientconn_handshake`. */ -struct hyper_request *hyper_request_new(void); +struct hyper_io *hyper_io_new(void); /* - Free an HTTP request. + Free an IO handle. This should only be used if the request isn't consumed by - `hyper_clientconn_send`. + `hyper_clientconn_handshake`. */ -void hyper_request_free(struct hyper_request *req); +void hyper_io_free(struct hyper_io *io); /* - Set the HTTP Method of the request. + Set the user data pointer for this IO to some value. + + This value is passed as an argument to the read and write callbacks. + + If passed, the `drop_func` will be called on the `userdata` when the + `hyper_io` is destroyed (either explicitly by `hyper_io_free` or + implicitly by an associated hyper task completing). */ -enum hyper_code hyper_request_set_method(struct hyper_request *req, - const uint8_t *method, - size_t method_len); +void hyper_io_set_userdata(struct hyper_io *io, void *data, hyper_userdata_drop drop_func); /* - Get the HTTP Method of the request. + Get the user data pointer for this IO value. - `method` must be a pointer to a buffer that this function will populate with the HTTP - method of the request. The `header_len` argument must be a pointer to a `size_t` which, on - call, is populated with the maximum length of the `method` buffer and, on successful - response, will be set to the actual length of the value written into the buffer. + The userdata is still owned by the IO so must be treated as "borrowed" + + Returns NULL if no userdata has been set. */ -enum hyper_code hyper_request_method(const struct hyper_request *req, - uint8_t *method, - size_t *method_len); +void *hyper_io_get_userdata(struct hyper_io *io); /* - Set the URI of the request. + Set the read function for this IO transport. - The request's URI is best described as the `request-target` from the RFCs. So in HTTP/1, - whatever is set will get sent as-is in the first line (GET $uri HTTP/1.1). It - supports the 4 defined variants, origin-form, absolute-form, authority-form, and - asterisk-form. + Data that is read from the transport should be put in the `buf` pointer, + up to `buf_len` bytes. The number of bytes read should be the return value. - The underlying type was built to efficiently support HTTP/2 where the request-target is - split over :scheme, :authority, and :path. As such, each part can be set explicitly, or the - type can parse a single contiguous string and if a scheme is found, that slot is "set". If - the string just starts with a path, only the path portion is set. All pseudo headers that - have been parsed/set are sent when the connection type is HTTP/2. + It is undefined behavior to try to access the bytes in the `buf` pointer, + unless you have already written them yourself. It is also undefined behavior + to return that more bytes have been written than actually set on the `buf`. - To set each slot explicitly, use `hyper_request_set_uri_parts`. + If there is no data currently available, a waker should be claimed from + the `ctx` and registered with whatever polling mechanism is used to signal + when data is available later on. The return value should be + `HYPER_IO_PENDING`. + + If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` + should be the return value. */ -enum hyper_code hyper_request_set_uri(struct hyper_request *req, - const uint8_t *uri, - size_t uri_len); +void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func); /* - Set the URI of the request with separate scheme, authority, and - path/query strings. + Set the write function for this IO transport. - Each of `scheme`, `authority`, and `path_and_query` should either be - null, to skip providing a component, or point to a UTF-8 encoded - string. If any string pointer argument is non-null, its corresponding - `len` parameter must be set to the string's length. - */ -enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req, - const uint8_t *scheme, - size_t scheme_len, - const uint8_t *authority, - size_t authority_len, - const uint8_t *path_and_query, - size_t path_and_query_len); + Data from the `buf` pointer should be written to the transport, up to + `buf_len` bytes. The number of bytes written should be the return value. -/* - Get the URI of the request split into scheme, authority and path/query strings. + If no data can currently be written, the `waker` should be cloned and + registered with whatever polling mechanism is used to signal when data + is available later on. The return value should be `HYPER_IO_PENDING`. - Each of `scheme`, `authority` and `path_and_query` may be pointers to buffers that this - function will populate with the appropriate values from the request. If one of these - pointers is non-NULL then the associated `_len` field must be a pointer to a `size_t` - which, on call, is populated with the maximum length of the buffer and, on successful - response, will be set to the actual length of the value written into the buffer. + Yeet. - If a buffer is passed as `NULL` then the `_len` field will be ignored and that component - will be skipped. + If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` + should be the return value. + */ +void hyper_io_set_write(struct hyper_io *io, hyper_io_write_callback func); - This function may fail with `HYPERE_INSUFFICIENT_SPACE` if one of the provided buffers is - not long enough to hold the value from the request. +/* + Create a new HTTP/1 serverconn options object. */ -enum hyper_code hyper_request_uri_parts(const struct hyper_request *req, - uint8_t *scheme, - size_t *scheme_len, - uint8_t *authority, - size_t *authority_len, - uint8_t *path_and_query, - size_t *path_and_query_len); +struct hyper_http1_serverconn_options *hyper_http1_serverconn_options_new(const struct hyper_executor *exec); /* - Set the preferred HTTP version of the request. + Free a `hyper_http1_serverconn_options*`. + */ +void hyper_http1_serverconn_options_free(struct hyper_http1_serverconn_options *opts); - The version value should be one of the `HYPER_HTTP_VERSION_` constants. +/* + Set whether HTTP/1 connections should support half-closures. - Note that this won't change the major HTTP version of the connection, - since that is determined at the handshake step. + Clients can chose to shutdown their write-side while waiting for the server to respond. + Setting this to true will prevent closing the connection immediately if read detects an EOF + in the middle of a request. + + Default is `false` */ -enum hyper_code hyper_request_set_version(struct hyper_request *req, int version); +enum hyper_code hyper_http1_serverconn_options_half_close(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Get the HTTP version used by this request. - - The returned value could be: + Enables or disables HTTP/1 keep-alive. - - `HYPER_HTTP_VERSION_1_0` - - `HYPER_HTTP_VERSION_1_1` - - `HYPER_HTTP_VERSION_2` - - `HYPER_HTTP_VERSION_NONE` if newer (or older). + Default is `true`. */ -int hyper_request_version(const struct hyper_request *resp); +enum hyper_code hyper_http1_serverconn_options_keep_alive(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Gets a reference to the HTTP headers of this request + Set whether HTTP/1 connections will write header names as title case at the socket level. - This is not an owned reference, so it should not be accessed after the - `hyper_request` has been consumed. + Default is `false`. */ -struct hyper_headers *hyper_request_headers(struct hyper_request *req); +enum hyper_code hyper_http1_serverconn_options_title_case_headers(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Set the body of the request. + Set whether to support preserving original header cases. - The default is an empty body. + Currently, this will record the original cases received, and store them in a private + extension on the Request. It will also look for and use such an extension in any provided + Response. - This takes ownership of the `hyper_body *`, you must not use it or - free it after setting it on the request. + Since the relevant extension is still private, there is no way to interact with the + original cases. The only effect this can have now is to forward the cases in a proxy-like + fashion. + + Default is `false`. */ -enum hyper_code hyper_request_set_body(struct hyper_request *req, struct hyper_body *body); +enum hyper_code hyper_http1_serverconn_options_preserve_header_case(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Take ownership of the body of this request. + Set a timeout for reading client request headers. If a client does not + transmit the entire header within this time, the connection is closed. - It is safe to free the request even after taking ownership of its body. + Default is to have no timeout. */ -struct hyper_body *hyper_request_body(struct hyper_request *req); +enum hyper_code hyper_http1_serverconn_options_header_read_timeout(struct hyper_http1_serverconn_options *opts, + uint64_t millis); /* - Set an informational (1xx) response callback. - - The callback is called each time hyper receives an informational (1xx) - response for this request. + Set whether HTTP/1 connections should try to use vectored writes, or always flatten into a + single buffer. - The third argument is an opaque user data pointer, which is passed to - the callback each time. + Note that setting this to false may mean more copies of body data, but may also improve + performance when an IO transport doesn’t support vectored writes well, such as most TLS + implementations. - 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. + Setting this to true will force hyper to use queued strategy which may eliminate + unnecessary cloning on some TLS backends. - NOTE: The `hyper_response *` is just borrowed data, and will not - be valid after the callback finishes. You must copy any data you wish - to persist. + Default is to automatically guess which mode to use, this function overrides the heuristic. */ -enum hyper_code hyper_request_on_informational(struct hyper_request *req, - hyper_request_on_informational_callback callback, - void *data, - hyper_userdata_drop drop); +enum hyper_code hyper_http1_serverconn_options_writev(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Construct a new HTTP 200 Ok response + Set the maximum buffer size for the HTTP/1 connection. Must be no lower `8192`. + + Default is a sensible value. */ -struct hyper_response *hyper_response_new(void); +enum hyper_code hyper_http1_serverconn_options_max_buf_size(struct hyper_http1_serverconn_options *opts, + uintptr_t max_buf_size); /* - Free an HTTP response. + Aggregates flushes to better support pipelined responses. - This should be used for any response once it is no longer needed. + Experimental, may have bugs. + + Default is `false`. */ -void hyper_response_free(struct hyper_response *resp); +enum hyper_code hyper_http1_serverconn_options_pipeline_flush(struct hyper_http1_serverconn_options *opts, + bool enabled); /* - Get the HTTP-Status code of this response. - - It will always be within the range of 100-599. + Create a new HTTP/2 serverconn options object bound to the provided executor. */ -uint16_t hyper_response_status(const struct hyper_response *resp); +struct hyper_http2_serverconn_options *hyper_http2_serverconn_options_new(const struct hyper_executor *exec); /* - Set the HTTP Status-Code of this response. + Free a `hyper_http2_serverconn_options*`. */ -void hyper_response_set_status(struct hyper_response *resp, uint16_t status); +void hyper_http2_serverconn_options_free(struct hyper_http2_serverconn_options *opts); /* - Get a pointer to the reason-phrase of this response. - - This buffer is not null-terminated. - - This buffer is owned by the response, and should not be used after - the response has been freed. + Sets the `SETTINGS_INITIAL_WINDOW_SIZE` option for HTTP/2 stream-level flow control. - Use `hyper_response_reason_phrase_len()` to get the length of this - buffer. + Passing `0` instructs hyper to use a sensible default value. */ -const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp); +enum hyper_code hyper_http2_serverconn_options_initial_stream_window_size(struct hyper_http2_serverconn_options *opts, + unsigned int window_size); /* - Get the length of the reason-phrase of this response. + Sets the max connection-level flow control for HTTP/2. - Use `hyper_response_reason_phrase()` to get the buffer pointer. + Passing `0` instructs hyper to use a sensible default value. */ -size_t hyper_response_reason_phrase_len(const struct hyper_response *resp); +enum hyper_code hyper_http2_serverconn_options_initial_connection_window_size(struct hyper_http2_serverconn_options *opts, + unsigned int window_size); /* - Set the preferred HTTP version of the response. + Sets whether to use an adaptive flow control. - The version value should be one of the `HYPER_HTTP_VERSION_` constants. + Enabling this will override the limits set in http2_initial_stream_window_size and + http2_initial_connection_window_size. - Note that this won't change the major HTTP version of the connection, - since that is determined at the handshake step. + Default is `false`. */ -enum hyper_code hyper_response_set_version(struct hyper_response *req, int version); +enum hyper_code hyper_http2_serverconn_options_adaptive_window(struct hyper_http2_serverconn_options *opts, + bool enabled); /* - Get the HTTP version used by this response. - - The returned value could be: + Sets the maximum frame size to use for HTTP/2. - - `HYPER_HTTP_VERSION_1_0` - - `HYPER_HTTP_VERSION_1_1` - - `HYPER_HTTP_VERSION_2` - - `HYPER_HTTP_VERSION_NONE` if newer (or older). + Passing `0` instructs hyper to use a sensible default value. */ -int hyper_response_version(const struct hyper_response *resp); +enum hyper_code hyper_http2_serverconn_options_max_frame_size(struct hyper_http2_serverconn_options *opts, + unsigned int frame_size); /* - Gets a reference to the HTTP headers of this response. + Sets the `SETTINGS_MAX_CONCURRENT_STREAMS` option for HTTP2 connections. - This is not an owned reference, so it should not be accessed after the - `hyper_response` has been freed. + Default is no limit (`std::u32::MAX`). Passing `0` will use this default. */ -struct hyper_headers *hyper_response_headers(struct hyper_response *resp); +enum hyper_code hyper_http2_serverconn_options_max_concurrent_streams(struct hyper_http2_serverconn_options *opts, + unsigned int max_streams); /* - Set the body of the response. - - The default is an empty body. + Sets an interval for HTTP/2 Ping frames should be sent to keep a connection alive. - This takes ownership of the `hyper_body *`, you must not use it or - free it after setting it on the request. + Default is to not use keep-alive pings. Passing `0` will use this default. */ -enum hyper_code hyper_response_set_body(struct hyper_response *rsp, struct hyper_body *body); +enum hyper_code hyper_http2_serverconn_options_keep_alive_interval(struct hyper_http2_serverconn_options *opts, + uint64_t interval_seconds); /* - Take ownership of the body of this response. + Sets a timeout for receiving an acknowledgement of the keep-alive ping. - It is safe to free the response even after taking ownership of its body. + If the ping is not acknowledged within the timeout, the connection will be closed. Does + nothing if `hyper_http2_serverconn_options_keep_alive_interval` is disabled. - To avoid a memory leak, the body must eventually be consumed by - `hyper_body_free`, `hyper_body_foreach`, or `hyper_request_set_body`. + Default is 20 seconds. */ -struct hyper_body *hyper_response_body(struct hyper_response *resp); +enum hyper_code hyper_http2_serverconn_options_keep_alive_timeout(struct hyper_http2_serverconn_options *opts, + uint64_t timeout_seconds); /* - Iterates the headers passing each name and value pair to the callback. - - The `userdata` pointer is also passed to the callback. + Set the maximum write buffer size for each HTTP/2 stream. Must be no larger than + `u32::MAX`. - The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or - `HYPER_ITER_BREAK` to stop. + Default is a sensible value. */ -void hyper_headers_foreach(const struct hyper_headers *headers, - hyper_headers_foreach_callback func, - void *userdata); +enum hyper_code hyper_http2_serverconn_options_max_send_buf_size(struct hyper_http2_serverconn_options *opts, + uintptr_t max_buf_size); /* - Sets the header with the provided name to the provided value. - - This overwrites any previous value set for the header. + Enables the extended `CONNECT` protocol. */ -enum hyper_code hyper_headers_set(struct hyper_headers *headers, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len); +enum hyper_code hyper_http2_serverconn_options_enable_connect_protocol(struct hyper_http2_serverconn_options *opts); /* - Adds the provided value to the list of the provided name. + Sets the max size of received header frames. - If there were already existing values for the name, this will append the - new value to the internal list. + Default is a sensible value. */ -enum hyper_code hyper_headers_add(struct hyper_headers *headers, - const uint8_t *name, - size_t name_len, - const uint8_t *value, - size_t value_len); +enum hyper_code hyper_http2_serverconn_options_max_header_list_size(struct hyper_http2_serverconn_options *opts, + uint32_t max); /* - Create a new IO type used to represent a transport. - - The read and write functions of this transport should be set with - `hyper_io_set_read` and `hyper_io_set_write`. - - To avoid a memory leak, the IO handle must eventually be consumed by - `hyper_io_free` or `hyper_clientconn_handshake`. + Create a service from a wrapped callback function. */ -struct hyper_io *hyper_io_new(void); +struct hyper_service *hyper_service_new(hyper_service_callback service_fn); /* - Free an IO handle. + Register opaque userdata with the `hyper_service`. This userdata must be `Send` in a rust + sense (i.e. can be passed between threads) though it doesn't have to be thread-safe (it + won't be accessed from multiple thread concurrently). - This should only be used if the request isn't consumed by - `hyper_clientconn_handshake`. + The service takes ownership of the userdata and will call the `drop_userdata` callback when + the service task is complete. If the `drop_userdata` callback is `NULL` then the service + will instead borrow the userdata and forget it when the associated task is completed and + thus the calling code is responsible for cleaning up the userdata through some other + mechanism. */ -void hyper_io_free(struct hyper_io *io); +void hyper_service_set_userdata(struct hyper_service *service, + void *userdata, + hyper_userdata_drop drop); /* - Set the user data pointer for this IO to some value. - - This value is passed as an argument to the read and write callbacks. - - If passed, the `drop_func` will be called on the `userdata` when the - `hyper_io` is destroyed (either explicitly by `hyper_io_free` or - implicitly by an associated hyper task completing). + Frees a hyper_service object if no longer needed */ -void hyper_io_set_userdata(struct hyper_io *io, void *data, hyper_userdata_drop drop_func); +void hyper_service_free(struct hyper_service *service); /* - Get the user data pointer for this IO value. + Serve the provided `hyper_service *` as an HTTP/1 endpoint over the provided `hyper_io *` + and configured as per the `hyper_http1_serverconn_options *`. - The userdata is still owned by the IO so must be treated as "borrowed" + Returns a `hyper_task*` which must be given to an executor to make progress. - Returns NULL if no userdata has been set. + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. */ -void *hyper_io_get_userdata(struct hyper_io *io); +struct hyper_task *hyper_serve_http1_connection(struct hyper_http1_serverconn_options *serverconn_options, + struct hyper_io *io, + struct hyper_service *service); /* - Set the read function for this IO transport. - - Data that is read from the transport should be put in the `buf` pointer, - up to `buf_len` bytes. The number of bytes read should be the return value. - - It is undefined behavior to try to access the bytes in the `buf` pointer, - unless you have already written them yourself. It is also undefined behavior - to return that more bytes have been written than actually set on the `buf`. + Serve the provided `hyper_service *` as an HTTP/2 endpoint over the provided `hyper_io *` + and configured as per the `hyper_http2_serverconn_options *`. - If there is no data currently available, a waker should be claimed from - the `ctx` and registered with whatever polling mechanism is used to signal - when data is available later on. The return value should be - `HYPER_IO_PENDING`. + Returns a `hyper_task*` which must be given to an executor to make progress. - If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` - should be the return value. + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. */ -void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func); +struct hyper_task *hyper_serve_http2_connection(struct hyper_http2_serverconn_options *serverconn_options, + struct hyper_io *io, + struct hyper_service *service); /* - Set the write function for this IO transport. + Serve the provided `hyper_service *` as either an HTTP/1 or HTTP/2 (depending on what the + client requests) endpoint over the provided `hyper_io *` and configured as per the + appropriate `hyper_httpX_serverconn_options *`. - Data from the `buf` pointer should be written to the transport, up to - `buf_len` bytes. The number of bytes written should be the return value. + Returns a `hyper_task*` which must be given to an executor to make progress. - If no data can currently be written, the `waker` should be cloned and - registered with whatever polling mechanism is used to signal when data - is available later on. The return value should be `HYPER_IO_PENDING`. + This function consumes the IO and Service objects and thus they should not be accessed + after this function is called. + */ +struct hyper_task *hyper_serve_httpX_connection(struct hyper_http1_serverconn_options *http1_serverconn_options, + struct hyper_http2_serverconn_options *http2_serverconn_options, + struct hyper_io *io, + struct hyper_service *service); - Yeet. +/* + Sends a `hyper_response*` back to the client. This function consumes the response and the + channel. - If there is an irrecoverable error reading data, then `HYPER_IO_ERROR` - should be the return value. + See [hyper_service_callback] for details. */ -void hyper_io_set_write(struct hyper_io *io, hyper_io_write_callback func); +void hyper_response_channel_send(struct hyper_response_channel *channel, + struct hyper_response *response); /* Creates a new task executor. From 1ada63b9b043635bff0f4a89fa8b1088a6d4a879 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 12 Oct 2023 20:23:20 +0100 Subject: [PATCH 51/54] Fix up doctests and bench builds --- benches/support/tokiort.rs | 27 ++------------------------- src/rt/timer.rs | 34 ++++------------------------------ 2 files changed, 6 insertions(+), 55 deletions(-) diff --git a/benches/support/tokiort.rs b/benches/support/tokiort.rs index b6f32ff733..e3a898c1e8 100644 --- a/benches/support/tokiort.rs +++ b/benches/support/tokiort.rs @@ -38,7 +38,7 @@ impl Timer for TokioTimer { } fn reset(&self, sleep: &mut Pin>, new_deadline: Instant) { - if let Some(sleep) = sleep.as_mut().downcast_mut_pin::() { + if let Some(sleep) = sleep.as_mut().downcast_mut_pin::() { sleep.reset(new_deadline.into()) } } @@ -59,30 +59,7 @@ where } } -// Use TokioSleep to get tokio::time::Sleep to implement Unpin. -// see https://docs.rs/tokio/latest/tokio/time/struct.Sleep.html -pin_project! { - pub(crate) struct TokioSleep { - #[pin] - pub(crate) inner: tokio::time::Sleep, - } -} - -impl Future for TokioSleep { - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().inner.poll(cx) - } -} - -impl TokioSleep { - pub fn reset(self: Pin<&mut Self>, deadline: Instant) { - self.project().inner.as_mut().reset(deadline.into()); - } -} - -pin_project! { +pin_project_lite::pin_project! { #[derive(Debug)] pub struct TokioIo { #[pin] diff --git a/src/rt/timer.rs b/src/rt/timer.rs index c79f3bfc15..e52e2f6f26 100644 --- a/src/rt/timer.rs +++ b/src/rt/timer.rs @@ -1,6 +1,7 @@ //! Provides a timer trait with timer-like functions //! //! Example using tokio timer: +//! //! ```rust //! use std::{ //! future::Future, @@ -17,46 +18,19 @@ //! //! impl Timer for TokioTimer { //! fn sleep(&self, duration: Duration) -> Pin> { -//! Box::pin(TokioSleep { -//! inner: tokio::time::sleep(duration), -//! }) +//! Box::pin(tokio::time::sleep(duration)) //! } //! //! fn sleep_until(&self, deadline: Instant) -> Pin> { -//! Box::pin(TokioSleep { -//! inner: tokio::time::sleep_until(deadline.into()), -//! }) +//! Box::pin(tokio::time::sleep_until(deadline.into())) //! } //! //! fn reset(&self, sleep: &mut Pin>, new_deadline: Instant) { -//! if let Some(sleep) = sleep.as_mut().downcast_mut_pin::() { +//! if let Some(sleep) = sleep.as_mut().downcast_mut_pin::() { //! sleep.reset(new_deadline.into()) //! } //! } //! } -//! -//! pin_project! { -//! pub(crate) struct TokioSleep { -//! #[pin] -//! pub(crate) inner: tokio::time::Sleep, -//! } -//! } -//! -//! impl Future for TokioSleep { -//! type Output = (); -//! -//! fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { -//! self.project().inner.poll(cx) -//! } -//! } -//! -//! impl Sleep for TokioSleep {} -//! -//! impl TokioSleep { -//! pub fn reset(self: Pin<&mut Self>, deadline: Instant) { -//! self.project().inner.as_mut().reset(deadline.into()); -//! } -//! } //! ```` use std::{ From 293ca8c8dbec10bc513d57334196e89cf9c653c6 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Thu, 12 Oct 2023 22:25:58 +0100 Subject: [PATCH 52/54] Format server.c with clang-format --- capi/examples/server.c | 130 ++++++++++++++++++++--------------------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index ad06228fce..2f16821643 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -120,11 +120,13 @@ static int register_signal_handler() { } // Register connection FD with epoll, associated with this `conn` -static bool update_conn_data_registrations(conn_data* conn, bool create) { +static bool update_conn_data_registrations(conn_data *conn, bool create) { struct epoll_event transport_event; transport_event.events = conn->event_mask; transport_event.data.ptr = conn; - if (epoll_ctl(conn->epoll_fd, create ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, conn->fd, &transport_event) < 0) { + if (epoll_ctl( + conn->epoll_fd, create ? EPOLL_CTL_ADD : EPOLL_CTL_MOD, conn->fd, &transport_event + ) < 0) { perror("epoll_ctl (transport)"); return false; } else { @@ -152,10 +154,10 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b } if (!(conn->event_mask & EPOLLIN)) { - conn->event_mask |= EPOLLIN; - if (!update_conn_data_registrations(conn, false)) { - return HYPER_IO_ERROR; - } + conn->event_mask |= EPOLLIN; + if (!update_conn_data_registrations(conn, false)) { + return HYPER_IO_ERROR; + } } conn->read_waker = hyper_context_waker(ctx); @@ -182,10 +184,10 @@ static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, s } if (!(conn->event_mask & EPOLLOUT)) { - conn->event_mask |= EPOLLOUT; - if (!update_conn_data_registrations(conn, false)) { - return HYPER_IO_ERROR; - } + conn->event_mask |= EPOLLOUT; + if (!update_conn_data_registrations(conn, false)) { + return HYPER_IO_ERROR; + } } conn->write_waker = hyper_context_waker(ctx); @@ -201,15 +203,15 @@ static conn_data *create_conn_data(int epoll, int fd) { conn->write_waker = NULL; if (!update_conn_data_registrations(conn, true)) { - free(conn); - return NULL; + free(conn); + return NULL; } return conn; } static void free_conn_data(void *userdata) { - conn_data* conn = (conn_data*)userdata; + conn_data *conn = (conn_data *)userdata; // Disassociate with the epoll if (epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL) < 0) { @@ -244,18 +246,18 @@ static hyper_io *create_io(conn_data *conn) { } typedef struct service_userdata_s { - char host[128]; - char port[8]; - const hyper_executor* executor; + char host[128]; + char port[8]; + const hyper_executor *executor; } service_userdata; -static service_userdata* create_service_userdata() { - return (service_userdata*)calloc(1, sizeof(service_userdata)); +static service_userdata *create_service_userdata() { + return (service_userdata *)calloc(1, sizeof(service_userdata)); } -static void free_service_userdata(void* userdata) { - service_userdata* cast_userdata = (service_userdata*)userdata; - free(cast_userdata); +static void free_service_userdata(void *userdata) { + service_userdata *cast_userdata = (service_userdata *)userdata; + free(cast_userdata); } static int print_each_header( @@ -272,23 +274,23 @@ static int print_body_chunk(void *userdata, const hyper_buf *chunk) { return HYPER_ITER_CONTINUE; } -static int send_each_body_chunk(void* userdata, hyper_context* ctx, hyper_buf **chunk) { - int* chunk_count = (int*)userdata; - if (*chunk_count > 0) { - unsigned char data[4096]; - memset(data, '0' + (*chunk_count % 10), sizeof(data)); - *chunk = hyper_buf_copy(data, sizeof(data)); - (*chunk_count)--; - } else { - *chunk = NULL; - } - return HYPER_POLL_READY; +static int send_each_body_chunk(void *userdata, hyper_context *ctx, hyper_buf **chunk) { + int *chunk_count = (int *)userdata; + if (*chunk_count > 0) { + unsigned char data[4096]; + memset(data, '0' + (*chunk_count % 10), sizeof(data)); + *chunk = hyper_buf_copy(data, sizeof(data)); + (*chunk_count)--; + } else { + *chunk = NULL; + } + return HYPER_POLL_READY; } static void server_callback( void *userdata, hyper_request *request, hyper_response_channel *channel ) { - service_userdata* service_data = (service_userdata*)userdata; + service_userdata *service_data = (service_userdata *)userdata; printf("Request from %s:%s\n", service_data->host, service_data->port); // Print out various properties of the request. @@ -323,10 +325,10 @@ static void server_callback( hyper_headers *req_headers = hyper_request_headers(request); hyper_headers_foreach(req_headers, print_each_header, NULL); - if (!strcmp((char*)method, "POST") || !strcmp((char*)method, "PUT")) { + if (!strcmp((char *)method, "POST") || !strcmp((char *)method, "PUT")) { // ...consume the request body - hyper_body* body = hyper_request_body(request); - hyper_task* task = hyper_body_foreach(body, print_body_chunk, NULL, NULL); + hyper_body *body = hyper_request_body(request); + hyper_task *task = hyper_body_foreach(body, print_body_chunk, NULL, NULL); hyper_executor_push(service_data->executor, task); } @@ -336,22 +338,18 @@ static void server_callback( // Build a response hyper_response *response = hyper_response_new(); hyper_response_set_status(response, 200); - hyper_headers* rsp_headers = hyper_response_headers(response); + hyper_headers *rsp_headers = hyper_response_headers(response); hyper_headers_set( - rsp_headers, - (unsigned char*)"Cache-Control", - 13, - (unsigned char*)"no-cache", - 8 + rsp_headers, (unsigned char *)"Cache-Control", 13, (unsigned char *)"no-cache", 8 ); - if (!strncmp((char*)method, "GET", method_len)) { + if (!strncmp((char *)method, "GET", method_len)) { // ...add a body - hyper_body* body = hyper_body_new(); + hyper_body *body = hyper_body_new(); hyper_body_set_data_func(body, send_each_body_chunk); - int* chunk_count = (int*)malloc(sizeof(int)); + int *chunk_count = (int *)malloc(sizeof(int)); *chunk_count = 1000; - hyper_body_set_userdata(body, (void*)chunk_count, free); + hyper_body_set_userdata(body, (void *)chunk_count, free); hyper_response_set_body(response, body); } @@ -412,7 +410,7 @@ int main(int argc, char *argv[]) { // Configure the server HTTP/2 stack hyper_http2_serverconn_options *http2_opts = hyper_http2_serverconn_options_new(exec); hyper_http2_serverconn_options_keep_alive_interval(http2_opts, 5); // 5 seconds - hyper_http2_serverconn_options_keep_alive_timeout(http2_opts, 5); // 5 seconds + hyper_http2_serverconn_options_keep_alive_timeout(http2_opts, 5); // 5 seconds while (1) { while (1) { @@ -424,7 +422,7 @@ int main(int argc, char *argv[]) { if (hyper_task_type(task) == HYPER_TASK_ERROR) { printf("hyper task failed with error!\n"); - hyper_error* err = hyper_task_value(task); + hyper_error *err = hyper_task_value(task); printf("error code: %d\n", hyper_error_code(err)); uint8_t errbuf[256]; size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf)); @@ -492,7 +490,9 @@ int main(int argc, char *argv[]) { perror("getnameinfo"); printf("New incoming connection from (unknown)\n"); } else { - printf("New incoming connection from (%s:%s)\n", userdata->host, userdata->port); + printf( + "New incoming connection from (%s:%s)\n", userdata->host, userdata->port + ); } // Set non-blocking @@ -546,26 +546,26 @@ int main(int argc, char *argv[]) { // Existing transport socket, poke the wakers or close the socket conn_data *conn = events[n].data.ptr; if (events[n].events & EPOLLIN) { - if (conn->read_waker) { - hyper_waker_wake(conn->read_waker); - conn->read_waker = NULL; - } else { - conn->event_mask &= ~EPOLLIN; - if (!update_conn_data_registrations(conn, false)) { - epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + if (conn->read_waker) { + hyper_waker_wake(conn->read_waker); + conn->read_waker = NULL; + } else { + conn->event_mask &= ~EPOLLIN; + if (!update_conn_data_registrations(conn, false)) { + epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + } } - } } if (events[n].events & EPOLLOUT) { - if (conn->write_waker) { - hyper_waker_wake(conn->write_waker); - conn->write_waker = NULL; - } else { - conn->event_mask &= ~EPOLLOUT; - if (!update_conn_data_registrations(conn, false)) { - epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + if (conn->write_waker) { + hyper_waker_wake(conn->write_waker); + conn->write_waker = NULL; + } else { + conn->event_mask &= ~EPOLLOUT; + if (!update_conn_data_registrations(conn, false)) { + epoll_ctl(conn->epoll_fd, EPOLL_CTL_DEL, conn->fd, NULL); + } } - } } } } From f5fa9df35e2131080aa688940ac8540cba7074cf Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 22 Oct 2024 12:49:01 +0100 Subject: [PATCH 53/54] Reduce body size in server example --- capi/examples/server.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capi/examples/server.c b/capi/examples/server.c index 2f16821643..d442bce39e 100644 --- a/capi/examples/server.c +++ b/capi/examples/server.c @@ -348,7 +348,7 @@ static void server_callback( hyper_body *body = hyper_body_new(); hyper_body_set_data_func(body, send_each_body_chunk); int *chunk_count = (int *)malloc(sizeof(int)); - *chunk_count = 1000; + *chunk_count = 10; hyper_body_set_userdata(body, (void *)chunk_count, free); hyper_response_set_body(response, body); } From ca2aaf631f4f8d716e20905fbc2a0e51ba4d8405 Mon Sep 17 00:00:00 2001 From: Andy Caldwell Date: Tue, 22 Oct 2024 12:49:19 +0100 Subject: [PATCH 54/54] Build server example with optimizations --- capi/examples/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/capi/examples/Makefile b/capi/examples/Makefile index eb05e3c718..c668d43043 100644 --- a/capi/examples/Makefile +++ b/capi/examples/Makefile @@ -3,7 +3,7 @@ # RPATH=$(PWD)/../../target/release -HYPER_CFLAGS += -I../include -ggdb3 +HYPER_CFLAGS += -I../include -ggdb3 -O2 HYPER_LDFLAGS += -L$(RPATH) -Wl,-rpath,$(RPATH) LIBS = -lhyper