From 3e4c7c993bbbe2bdeb563fa888b900d01c4be4a1 Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Mon, 6 Oct 2025 01:23:20 +0200 Subject: Fix design issues with async responses, add async example struct http_response did not provide users any void * that could be used to maintain a state between calls to an asynchronous HTTP response. On the other hand, the user pointer could not be used for this purpose, since it is shared among all HTTP clients for a given struct handler instance. Moreover, the length callback was still not supporting this feature, which in fact might be required by some users. Implementing this was particularly challenging, as this broke the current assumption that all bytes on a call to http_read were being processed. Now, since a client request can only be partially processed because of the length callback, http_read must take this into account so that the remaining bytes are still available for future calls, before reading again from the file descriptor. --- examples/async/main.c | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 examples/async/main.c (limited to 'examples/async/main.c') diff --git a/examples/async/main.c b/examples/async/main.c new file mode 100644 index 0000000..dbf69a8 --- /dev/null +++ b/examples/async/main.c @@ -0,0 +1,232 @@ +/* As of FreeBSD 13.2, sigaction(2) still conforms to IEEE Std + * 1003.1-1990 (POSIX.1), which did not define SA_RESTART. + * FreeBSD supports it as an extension, but then _POSIX_C_SOURCE must + * not be defined. */ +#ifndef __FreeBSD__ +#define _POSIX_C_SOURCE 200809L +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int send_response(const unsigned cnt, struct http_response *const r) +{ + int ret = -1; + + struct html_node *html = html_node_alloc("html"), *body, *p; + struct dynstr d, text; + + dynstr_init(&d); + dynstr_init(&text); + + if (!html) + { + fprintf(stderr, "%s: html_node_alloc failed\n", __func__); + goto end; + } + else if (!(body = html_node_add_child(html, "body"))) + { + fprintf(stderr, "%s: html_node_add_child body failed\n", __func__); + goto end; + } + else if (!(p = html_node_add_child(body, "p"))) + { + fprintf(stderr, "%s: html_node_add_child p failed\n", __func__); + goto end; + } + else if (dynstr_append(&text, "It took %u iterations to generate " + "this response!", cnt)) + { + fprintf(stderr, "%s: dynstr_append failed\n", __func__); + goto end; + } + else if (html_node_set_value(p, text.str)) + { + fprintf(stderr, "%s: html_node_set_value p failed\n", __func__); + goto end; + } + else if (html_serialize(html, &d)) + { + fprintf(stderr, "%s: html_serialize failed\n", __func__); + goto end; + } + + *r = (const struct http_response) + { + .status = HTTP_STATUS_OK, + .buf.rw = d.str, + .n = d.len, + .free = free + }; + + if (http_response_add_header(r, "Content-Type", "text/html")) + { + fprintf(stderr, "%s: http_response_add_header failed\n", __func__); + goto end; + } + + ret = 0; + +end: + dynstr_free(&text); + html_node_free(html); + + if (ret) + dynstr_free(&d); + + return ret; +} + +static int step(const struct http_payload *const pl, + struct http_response *const r, void *const user, void *step_args) +{ + unsigned *const cnt = step_args; + const unsigned max = 10; + + fprintf(stderr, "%s: step %u\n", __func__, *cnt); + + if (++(*cnt) >= max) + return send_response(*cnt, r); + + return 0; +} + +static int hello(const struct http_payload *const pl, + struct http_response *const r, void *const user) +{ + unsigned *const cnt = malloc(sizeof *cnt); + + if (!cnt) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + return -1; + } + + *r = (const struct http_response) + { + .step.payload = step, + .step_args = cnt + }; + + *cnt = 0; + return 0; +} + +static int on_length(const unsigned long long len, + const struct http_cookie *const c, struct http_response *const r, + void *const user) +{ + *r = (const struct http_response) + { + .status = HTTP_STATUS_FORBIDDEN + }; + + return 1; +} + +struct handler *handler; + +static void handle_signal(const int signum) +{ + switch (signum) + { + case SIGINT: + /* Fall through. */ + case SIGTERM: + handler_notify_close(handler); + break; + + default: + break; + } +} + +static int init_signals(void) +{ + struct sigaction sa = + { + .sa_handler = handle_signal, + .sa_flags = SA_RESTART + }; + + sigemptyset(&sa.sa_mask); + + static const struct signal + { + int signal; + const char *name; + } signals[] = + { + {.signal = SIGINT, .name = "SIGINT"}, + {.signal = SIGTERM, .name = "SIGTERM"}, + {.signal = SIGPIPE, .name = "SIGPIPE"} + }; + + for (size_t i = 0; i < sizeof signals / sizeof *signals; i++) + { + const struct signal *const s = &signals[i]; + + if (sigaction(s->signal, &sa, NULL)) + { + fprintf(stderr, "%s: sigaction(2) %s: %s\n", + __func__, s->name, strerror(errno)); + return -1; + } + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE; + const struct handler_cfg cfg = + { + .length = on_length + }; + + static const char *const urls[] = {"/", "/index.html"}; + + if (!(handler = handler_alloc(&cfg))) + { + fprintf(stderr, "%s: handler_alloc failed\n", __func__); + goto end; + } + + for (size_t i = 0; i < sizeof urls / sizeof *urls; i++) + if (handler_add(handler, urls[i], HTTP_OP_GET, hello, NULL)) + { + fprintf(stderr, "%s: handler_add failed\n", __func__); + goto end; + } + + unsigned short outport; + + if (handler_listen(handler, 0, &outport)) + { + fprintf(stderr, "%s: handler_listen failed\n", __func__); + goto end; + } + + printf("Listening on port %hu\n", outport); + + if (handler_loop(handler)) + { + fprintf(stderr, "%s: handler_loop failed\n", __func__); + goto end; + } + + ret = EXIT_SUCCESS; + +end: + handler_free(handler); + return ret; +} -- cgit v1.2.3