diff options
| -rw-r--r-- | doc/man7/libweb_http.7 | 50 | ||||
| -rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | examples/Makefile | 1 | ||||
| -rw-r--r-- | examples/async/CMakeLists.txt | 4 | ||||
| -rw-r--r-- | examples/async/Makefile | 29 | ||||
| -rw-r--r-- | examples/async/README.md | 14 | ||||
| -rw-r--r-- | examples/async/main.c | 232 | ||||
| -rw-r--r-- | handler.c | 33 | ||||
| -rw-r--r-- | http.c | 152 | ||||
| -rw-r--r-- | include/libweb/http.h | 10 |
10 files changed, 470 insertions, 56 deletions
diff --git a/doc/man7/libweb_http.7 b/doc/man7/libweb_http.7 index c3bec8b..a8e315d 100644 --- a/doc/man7/libweb_http.7 +++ b/doc/man7/libweb_http.7 @@ -562,7 +562,13 @@ struct http_response unsigned long long \fIn\fP; size_t \fIn_headers\fP; void (*\fIfree\fP)(void *); - int (*\fIstep\fP)(const struct http_payload *, struct http_response *, void *); + void *\fIstep_args\fP; + + union http_step + { + int (*\fIlength\fP)(unsigned long long \fIlen\fP, const struct http_cookie *\fIc\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP); + int (*\fIpayload\fP)(const struct http_payload *\fIp\fP, struct http_response *\fIr\fP, void *\fIuser\fP, void *\fIstep_args\fP); + } \fIstep\fP; }; .EE .in @@ -671,22 +677,54 @@ is a valid pointer. Otherwise, must be a null pointer. .I step -allows implementations to generate a response in a non-blocking manner. -When a response is not immediately available when a payload is received, +allows implementations to deal with responses asynchronously +i.e., without blocking other clients. +For example, this can be useful to +generate a response in a non-blocking manner. +In other words, when a response is not immediately available +when a payload is received, .I step must be assigned to a function that can generate it later. .I libweb shall then call this function immediately later, without blocking other clients. + Assigning -.I step +.I step.length +or +.I step.payload to a null pointer falls back to the default behaviour i.e., a response is returned immediately. Note that a non-null -.I step -shall always take priority, thus ignoring any other information inside the +.I step.length +or +.I step.payload +shall always take priority, +.B thus ignoring any other information inside the .I "struct http_response" instance. +.I step +can be configured under the following situations: + +.IP \(bu 2 +Inside the +.I length +callback defined by the +.I struct http_cfg +instance. In this case, +.I step.length +must be assigned to a function to be later called by +.IR libweb . + +.IP \(bu 2 +Inside the +.I payload +callback defined by the +.I struct http_cfg +instance. In this case, +.I step.payload +must be assigned to a function to be later called by +.IR libweb . .SS Transport Layer Security (TLS) By design, diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 3370acb..b64430c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.13) +add_subdirectory(async) add_subdirectory(form) add_subdirectory(headers) add_subdirectory(hello) diff --git a/examples/Makefile b/examples/Makefile index b06f0a5..688895a 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,6 +1,7 @@ .POSIX: DIRS = \ + async \ form \ headers \ hello \ 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; +} @@ -28,7 +28,8 @@ struct handler struct handler *h; struct server_client *c; struct http_ctx *http; - int (*fn)(const struct http_payload *, struct http_response *, void *); + union http_step step; + void *step_args; struct client *next; } *clients; @@ -61,15 +62,19 @@ static int on_payload(const struct http_payload *const p, if (e->op == p->op && !wildcard_cmp(p->resource, e->url, true)) { + union http_step *const s = &c->step; int ret; - if (c->fn) - ret = c->fn(p, r, e->user); + if (s->payload) + ret = s->payload(p, r, e->user, c->step_args); else ret = e->f(p, r, e->user); if (!ret) - c->fn = r->step; + { + s->payload = r->step.payload; + c->step_args = r->step_args; + } return ret; } @@ -92,10 +97,24 @@ static int on_length(const unsigned long long len, struct client *const cl = user; struct handler *const h = cl->h; - if (h->cfg.length) - return h->cfg.length(len, c, r, h->cfg.user); + if (!h->cfg.length) + return 0; - return 0; + union http_step *const s = &cl->step; + int ret; + + if (s->length) + ret = s->length(len, c, r, h->cfg.user, cl->step_args); + else + ret = h->cfg.length(len, c, r, h->cfg.user); + + if (!ret) + { + s->length = r->step.length; + cl->step_args = r->step_args; + } + + return ret; } static struct client *find_or_alloc_client(struct handler *const h, @@ -103,6 +103,8 @@ struct http_ctx struct http_header *headers; bool has_length, expect_continue; int (*response)(struct http_ctx *); + void *buf; + size_t buflen; } ctx; struct write_ctx @@ -653,6 +655,7 @@ static void ctx_free(struct ctx *const c) free(c->headers); free(c->args); + free(c->buf); *c = (const struct ctx){0}; } @@ -1220,7 +1223,7 @@ static int process_payload(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { h->wctx.op = c->op; ctx_free(c); @@ -1475,29 +1478,15 @@ static int check_length(struct http_ctx *const h) return h->cfg.length(c->u2.payload.len, &cookie, &h->wctx.r, h->cfg.user); } -static int process_expect(struct http_ctx *const h) +static int process_payload_expect(struct http_ctx *const h) { struct ctx *const c = &h->ctx; struct write_ctx *const w = &h->wctx; - const int res = check_length(h); - - if (res) - { - if (res < 0) - { - fprintf(stderr, "%s: check_length failed\n", __func__); - return res; - } - - w->close = true; - return start_response(h); - } - + struct http_response *const r = &h->wctx.r; struct http_payload p = ctx_to_payload(c); p.expect_continue = true; - struct http_response *const r = &h->wctx.r; const int ret = h->cfg.payload(&p, r, h->cfg.user); if (ret) @@ -1505,7 +1494,7 @@ static int process_expect(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { w->op = c->op; c->state = BODY_LINE; @@ -1515,6 +1504,30 @@ static int process_expect(struct http_ctx *const h) return 0; } +static int process_expect(struct http_ctx *const h) +{ + struct ctx *const c = &h->ctx; + struct write_ctx *const w = &h->wctx; + struct http_response *const r = &h->wctx.r; + const int res = check_length(h); + + if (res) + { + if (res < 0) + { + fprintf(stderr, "%s: check_length failed\n", __func__); + return res; + } + + w->close = true; + return start_response(h); + } + else if (!r->step.length) + c->response = process_payload_expect; + + return 0; +} + static int process_get(struct http_ctx *const h) { int ret; @@ -1530,7 +1543,7 @@ static int process_get(struct http_ctx *const h) ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { w->op = c->op; ctx_free(c); @@ -1544,6 +1557,34 @@ static int process_get(struct http_ctx *const h) return 0; } +static int process_boundary(struct http_ctx *const h) +{ + struct ctx *const c = &h->ctx; + struct write_ctx *const w = &h->wctx; + const int res = check_length(h); + + if (res) + { + if (res < 0) + { + fprintf(stderr, "%s: check_length failed\n", + __func__); + return res; + } + + w->close = true; + ctx_free(c); + return start_response(h); + } + else if (!w->r.step.length) + { + c->response = NULL; + c->state = BODY_LINE; + } + + return 0; +} + static int header_cr_line(struct http_ctx *const h) { const char *const line = (const char *)h->line; @@ -1575,20 +1616,8 @@ static int header_cr_line(struct http_ctx *const h) } else if (c->boundary) { - const int res = check_length(h); - - if (res) - { - if (res < 0) - { - fprintf(stderr, "%s: check_length failed\n", - __func__); - return res; - } - - h->wctx.close = true; - return start_response(h); - } + c->response = process_boundary; + return 0; } c->state = BODY_LINE; @@ -1640,7 +1669,7 @@ static int send_payload(struct http_ctx *const h, ctx_free(c); return ret; } - else if (!r->step) + else if (!r->step.payload) { ctx_free(c); return start_response(h); @@ -2524,28 +2553,67 @@ static int read_buf(struct http_ctx *const h, const char *const buf, return -1; } +static int storebuf(struct http_ctx *const h, const void *const src, + const size_t n) +{ + struct ctx *const c = &h->ctx; + void *const dst = malloc(n); + + if (!dst) + { + fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); + return -1; + } + + memcpy((c->buf = dst), src, (c->buflen = n)); + return 0; +} + +static int loadbuf(struct http_ctx *const h, void *const dst, const size_t n) +{ + struct ctx *const c = &h->ctx; + + memcpy(dst, c->buf, c->buflen); + free(c->buf); + c->buf = NULL; + return c->buflen; +} + int http_read(struct http_ctx *const h, bool *const close) { + struct ctx *const c = &h->ctx; char buf[BUFSIZ]; - const int r = h->cfg.read(buf, sizeof buf, h->cfg.user); + int ret, r; - if (r <= 0) + if (c->buf) + r = loadbuf(h, buf, sizeof buf); + else if ((r = h->cfg.read(buf, sizeof buf, h->cfg.user)) <= 0) return rw_error(r, close); size_t rem = r; while (rem) { - const int ret = read_buf(h, &buf[r - rem], &rem); - - if (ret) + if ((ret = read_buf(h, &buf[r - rem], &rem))) + goto failure; + else if (c->response && rem) { - ctx_free(&h->ctx); - return ret; + if (storebuf(h, &buf[r - rem], rem)) + { + fprintf(stderr, "%s: storebuf failed\n", __func__); + ret = -1; + goto failure; + } + + break; } } return 0; + +failure: + ctx_free(&h->ctx); + return ret; } static int append_expire(struct dynstr *const d) @@ -2615,7 +2683,7 @@ int http_update(struct http_ctx *const h, bool *const write, bool *const close) else ret = w->pending ? http_write(h, close) : http_read(h, close); - *write = c->response || w->pending; + *write = c->buf || c->response || w->pending; return ret; } diff --git a/include/libweb/http.h b/include/libweb/http.h index f18b5c4..87edf8a 100644 --- a/include/libweb/http.h +++ b/include/libweb/http.h @@ -109,7 +109,15 @@ struct http_response unsigned long long n; size_t n_headers; void (*free)(void *); - int (*step)(const struct http_payload *, struct http_response *, void *); + void *step_args; + + union http_step + { + int (*length)(unsigned long long len, const struct http_cookie *c, + struct http_response *r, void *user, void *step_args); + int (*payload)(const struct http_payload *p, struct http_response *r, + void *user, void *step_args); + } step; }; struct http_cfg |
