aboutsummaryrefslogtreecommitdiff
path: root/examples/async
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 01:23:20 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-10-06 15:51:00 +0200
commit3e4c7c993bbbe2bdeb563fa888b900d01c4be4a1 (patch)
treebda2c376c19b11f8f76ef6aad84dea067f491934 /examples/async
parenta0f5f7509bb9040752fa61fe0fdb447608e22b1c (diff)
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.
Diffstat (limited to 'examples/async')
-rw-r--r--examples/async/CMakeLists.txt4
-rw-r--r--examples/async/Makefile29
-rw-r--r--examples/async/README.md14
-rw-r--r--examples/async/main.c232
4 files changed, 279 insertions, 0 deletions
diff --git a/examples/async/CMakeLists.txt b/examples/async/CMakeLists.txt
new file mode 100644
index 0000000..4afd5ba
--- /dev/null
+++ b/examples/async/CMakeLists.txt
@@ -0,0 +1,4 @@
+cmake_minimum_required(VERSION 3.13)
+project(async C)
+add_executable(${PROJECT_NAME} main.c)
+target_link_libraries(${PROJECT_NAME} PRIVATE web)
diff --git a/examples/async/Makefile b/examples/async/Makefile
new file mode 100644
index 0000000..d0a42c2
--- /dev/null
+++ b/examples/async/Makefile
@@ -0,0 +1,29 @@
+.POSIX:
+
+PROJECT = async
+DEPS = \
+ main.o
+LIBWEB = ../../libweb.a
+DYNSTR = ../../dynstr/libdynstr.a
+CFLAGS = -I ../../include -I ../../dynstr/include
+LIBWEB_FLAGS = -L ../../ -l web
+DYNSTR_FLAGS = -L ../../dynstr -l dynstr
+
+all: $(PROJECT)
+
+clean:
+ rm -f $(DEPS)
+
+distclean: clean
+ rm -f $(PROJECT)
+
+FORCE:
+
+$(PROJECT): $(DEPS) $(LIBWEB) $(DYNSTR)
+ $(CC) $(LDFLAGS) $(DEPS) $(LIBWEB_FLAGS) $(DYNSTR_FLAGS) -o $@
+
+$(LIBWEB): FORCE
+ +cd ../../ && $(MAKE)
+
+$(DYNSTR): FORCE
+ +cd ../../dynstr && $(MAKE)
diff --git a/examples/async/README.md b/examples/async/README.md
new file mode 100644
index 0000000..ece15e7
--- /dev/null
+++ b/examples/async/README.md
@@ -0,0 +1,14 @@
+# Asynchronous HTTP response example
+
+This example shows how to generate HTTP responses asynchronously.
+
+## How to build
+
+If using `make(1)`, just run `make` from this directory.
+
+If using CMake, examples are built by default when configuring the project
+from [the top-level `CMakeLists.txt`](../../CMakeLists.txt).
+
+## How to run
+
+Run the executable without any command line arguments.
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 <dynstr.h>
+#include <libweb/handler.h>
+#include <libweb/html.h>
+#include <libweb/http.h>
+#include <errno.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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;
+}