diff options
| -rw-r--r-- | doc/man3/handler_notify_close.3 | 51 | ||||
| -rw-r--r-- | examples/headers/main.c | 75 | ||||
| -rw-r--r-- | examples/hello/main.c | 77 | ||||
| -rw-r--r-- | examples/put/main.c | 80 | ||||
| -rw-r--r-- | handler.c | 5 | ||||
| -rw-r--r-- | include/libweb/handler.h | 1 | ||||
| -rw-r--r-- | include/libweb/server.h | 1 | ||||
| -rw-r--r-- | server.c | 163 |
8 files changed, 345 insertions, 108 deletions
diff --git a/doc/man3/handler_notify_close.3 b/doc/man3/handler_notify_close.3 new file mode 100644 index 0000000..3cc4130 --- /dev/null +++ b/doc/man3/handler_notify_close.3 @@ -0,0 +1,51 @@ +.TH HANDLER_NOTIFY_CLOSE 3 2024-07-13 0.4.0 "libweb Library Reference" + +.SH NAME +handler_notify_close \- tell a web server handler object to close gracefully + +.SH SYNOPSIS +.LP +.nf +#include <libweb/handler.h> +.P +int handler_notify_close(struct handler *\fIh\fP); +.fi + +.SH DESCRIPTION +The +.IR handler_notify_close (3) +function notifies +.I h +to exit the loop entered to by +.IR handler_loop (3). +.IR h +must have been returned by a previous call to +.IR handler_alloc (3). + +This function is +.IR async-signal-safe , +so it can be safely called from a signal handler. This is intentional, +as applications would typically want to close the server on specific +signals, such as +.I SIGINT +or +.IR SIGTERM . + +.SH RETURN VALUE +On success, zero is returned. On error, a negative integer is returned. + +.SH ERRORS +No errors are defined. + +.SH SEE ALSO +.BR handler_alloc (3), +.BR handler_loop (3), +.BR libweb_handler (7). + +.SH COPYRIGHT +Copyright (C) 2023-2024 libweb contributors +.P +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. diff --git a/examples/headers/main.c b/examples/headers/main.c index 51d622a..a4eb5e6 100644 --- a/examples/headers/main.c +++ b/examples/headers/main.c @@ -1,10 +1,21 @@ +/* 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 const size_t max_headers = 5; @@ -41,6 +52,59 @@ static int on_length(const unsigned long long len, 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; @@ -50,17 +114,16 @@ int main(int argc, char *argv[]) .max_headers = max_headers }; - struct handler *const h = handler_alloc(&cfg); static const char *const urls[] = {"/", "/index.html"}; - if (!h) + 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(h, urls[i], HTTP_OP_GET, hello, NULL)) + if (handler_add(handler, urls[i], HTTP_OP_GET, hello, NULL)) { fprintf(stderr, "%s: handler_add failed\n", __func__); goto end; @@ -68,7 +131,7 @@ int main(int argc, char *argv[]) unsigned short port; - if (handler_listen(h, 0, &port)) + if (handler_listen(handler, 0, &port)) { fprintf(stderr, "%s: handler_listen failed\n", __func__); goto end; @@ -76,7 +139,7 @@ int main(int argc, char *argv[]) printf("Listening on port %hu\n", port); - if (handler_loop(h)) + if (handler_loop(handler)) { fprintf(stderr, "%s: handler_loop failed\n", __func__); goto end; @@ -85,6 +148,6 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; end: - handler_free(h); + handler_free(handler); return ret; } diff --git a/examples/hello/main.c b/examples/hello/main.c index 62e2a9e..8ddf691 100644 --- a/examples/hello/main.c +++ b/examples/hello/main.c @@ -1,10 +1,21 @@ +/* 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 hello(const struct http_payload *const pl, struct http_response *const r, void *const user) @@ -25,7 +36,7 @@ static int hello(const struct http_payload *const pl, fprintf(stderr, "%s: html_node_add_child body failed\n", __func__); goto end; } - else if (!(p = html_node_add_child(html, "p"))) + else if (!(p = html_node_add_child(body, "p"))) { fprintf(stderr, "%s: html_node_add_child p failed\n", __func__); goto end; @@ -78,6 +89,59 @@ static int on_length(const unsigned long long len, 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; @@ -86,17 +150,16 @@ int main(int argc, char *argv[]) .length = on_length }; - struct handler *const h = handler_alloc(&cfg); static const char *const urls[] = {"/", "/index.html"}; - if (!h) + 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(h, urls[i], HTTP_OP_GET, hello, NULL)) + if (handler_add(handler, urls[i], HTTP_OP_GET, hello, NULL)) { fprintf(stderr, "%s: handler_add failed\n", __func__); goto end; @@ -104,7 +167,7 @@ int main(int argc, char *argv[]) unsigned short outport; - if (handler_listen(h, 0, &outport)) + if (handler_listen(handler, 0, &outport)) { fprintf(stderr, "%s: handler_listen failed\n", __func__); goto end; @@ -112,7 +175,7 @@ int main(int argc, char *argv[]) printf("Listening on port %hu\n", outport); - if (handler_loop(h)) + if (handler_loop(handler)) { fprintf(stderr, "%s: handler_loop failed\n", __func__); goto end; @@ -121,6 +184,6 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; end: - handler_free(h); + handler_free(handler); return ret; } diff --git a/examples/put/main.c b/examples/put/main.c index cf38248..3d6454d 100644 --- a/examples/put/main.c +++ b/examples/put/main.c @@ -1,10 +1,21 @@ +/* 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 on_put(const struct http_payload *const pl, struct http_response *const r, void *const user) @@ -41,6 +52,59 @@ static int on_length(const unsigned long long len, return 0; } +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; @@ -50,17 +114,21 @@ int main(int argc, char *argv[]) .length = on_length }; - struct handler *const h = handler_alloc(&cfg); static const char *const urls[] = {"/*"}; - if (!h) + if (!(handler = handler_alloc(&cfg))) { fprintf(stderr, "%s: handler_alloc failed\n", __func__); goto end; } + else if (init_signals()) + { + fprintf(stderr, "%s: init_signals failed\n", __func__); + goto end; + } for (size_t i = 0; i < sizeof urls / sizeof *urls; i++) - if (handler_add(h, urls[i], HTTP_OP_PUT, on_put, NULL)) + if (handler_add(handler, urls[i], HTTP_OP_PUT, on_put, NULL)) { fprintf(stderr, "%s: handler_add failed\n", __func__); goto end; @@ -68,7 +136,7 @@ int main(int argc, char *argv[]) unsigned short outport; - if (handler_listen(h, 0, &outport)) + if (handler_listen(handler, 0, &outport)) { fprintf(stderr, "%s: handler_listen failed\n", __func__); goto end; @@ -76,7 +144,7 @@ int main(int argc, char *argv[]) printf("Listening on port %hu\n", outport); - if (handler_loop(h)) + if (handler_loop(handler)) { fprintf(stderr, "%s: handler_loop failed\n", __func__); goto end; @@ -85,6 +153,6 @@ int main(int argc, char *argv[]) ret = EXIT_SUCCESS; end: - handler_free(h); + handler_free(handler); return ret; } @@ -180,6 +180,11 @@ static int remove_client_from_list(struct handler *const h, return ret; } +int handler_notify_close(struct handler *const h) +{ + return server_notify_close(h->server); +} + int handler_listen(struct handler *const h, const unsigned short port, unsigned short *const outport) { diff --git a/include/libweb/handler.h b/include/libweb/handler.h index 72ef1e0..7e0b601 100644 --- a/include/libweb/handler.h +++ b/include/libweb/handler.h @@ -24,5 +24,6 @@ int handler_add(struct handler *h, const char *url, enum http_op op, int handler_listen(struct handler *h, unsigned short port, unsigned short *outport); int handler_loop(struct handler *h); +int handler_notify_close(struct handler *h); #endif /* HANDLER_H */ diff --git a/include/libweb/server.h b/include/libweb/server.h index b3691aa..08e0f34 100644 --- a/include/libweb/server.h +++ b/include/libweb/server.h @@ -11,5 +11,6 @@ int server_write(const void *buf, size_t n, struct server_client *c); int server_close(struct server *s); int server_client_close(struct server *s, struct server_client *c); void server_client_write_pending(struct server_client *c, bool write); +int server_notify_close(struct server *s); #endif /* SERVER_H */ @@ -1,11 +1,3 @@ -/* 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 "libweb/server.h" #include <fcntl.h> #include <sys/socket.h> @@ -13,7 +5,6 @@ #include <poll.h> #include <unistd.h> #include <errno.h> -#include <signal.h> #include <stdbool.h> #include <stddef.h> #include <stdio.h> @@ -22,7 +13,7 @@ struct server { - int fd; + int fd, cfds[2]; struct server_client { @@ -158,23 +149,6 @@ void server_client_write_pending(struct server_client *const c, c->write = write; } -static volatile sig_atomic_t do_exit; - -static void handle_signal(const int signum) -{ - switch (signum) - { - case SIGINT: - /* Fall through. */ - case SIGTERM: - do_exit = 1; - break; - - default: - break; - } -} - static size_t get_clients(const struct server *const s) { size_t ret = 0; @@ -185,12 +159,27 @@ static size_t get_clients(const struct server *const s) return ret; } +static int check_exit(const int fd) +{ + char exit; + const ssize_t n = read(fd, &exit, sizeof exit); + + if (n <= 0) + { + fprintf(stderr, "%s: read(2): %s\n", __func__, strerror(errno)); + return -1; + } + + return 0; +} + struct server_client *server_poll(struct server *const s, bool *const io, bool *const exit) { + enum {SERVER, PIPE, CLIENTS}; struct server_client *ret = NULL; const size_t n_clients = get_clients(s); - const nfds_t n = n_clients + 1; + const nfds_t n = n_clients + CLIENTS; struct pollfd *const fds = malloc(n * sizeof *fds); if (!fds) @@ -199,7 +188,7 @@ struct server_client *server_poll(struct server *const s, bool *const io, goto end; } - struct pollfd *const sfd = &fds[0]; + struct pollfd *const sfd = &fds[SERVER], *const pfd = &fds[PIPE]; *io = *exit = false; *sfd = (const struct pollfd) @@ -208,8 +197,14 @@ struct server_client *server_poll(struct server *const s, bool *const io, .events = POLLIN }; + *pfd = (const struct pollfd) + { + .fd = s->cfds[0], + .events = POLLIN + }; + for (struct {const struct server_client *c; size_t j;} - _ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++) + _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++) { struct pollfd *const p = &fds[_.j]; const int fd = _.c->fd; @@ -230,14 +225,8 @@ again: res = poll(fds, n, -1); - if (do_exit) + if (res < 0) { - *exit = true; - goto end; - } - else if (res < 0) - { - switch (errno) { case EAGAIN: @@ -257,6 +246,15 @@ again: fprintf(stderr, "%s: poll(2) returned zero\n", __func__); goto end; } + else if (pfd->revents) + { + if (check_exit(pfd->fd)) + fprintf(stderr, "%s: check_exit failed\n", __func__); + else + *exit = true; + + goto end; + } else if (sfd->revents) { ret = alloc_client(s); @@ -264,7 +262,7 @@ again: } for (struct {struct server_client *c; size_t j;} - _ = {.c = s->c, .j = 1}; _.c; _.c = _.c->next, _.j++) + _ = {.c = s->c, .j = CLIENTS}; _.c; _.c = _.c->next, _.j++) { const struct pollfd *const p = &fds[_.j]; @@ -283,66 +281,25 @@ end: return ret; } -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; -} - struct server *server_init(const unsigned short port, unsigned short *const outport) { struct server *const s = malloc(sizeof *s); + int fds[2] = {-1, -1}, fd = -1; if (!s) { fprintf(stderr, "%s: malloc(3): %s\n", __func__, strerror(errno)); goto failure; } - - *s = (const struct server) - { - .fd = socket(AF_INET, SOCK_STREAM, 0) - }; - - if (s->fd < 0) + else if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { fprintf(stderr, "%s: socket(2): %s\n", __func__, strerror(errno)); goto failure; } - else if (init_signals()) + else if (pipe(fds)) { - fprintf(stderr, "%s: init_signals failed\n", __func__); + fprintf(stderr, "%s: socketpair(2): %s\n", __func__, strerror(errno)); goto failure; } @@ -354,12 +311,12 @@ struct server *server_init(const unsigned short port, enum {QUEUE_LEN = 10}; - if (bind(s->fd, (const struct sockaddr *)&addr, sizeof addr)) + if (bind(fd, (const struct sockaddr *)&addr, sizeof addr)) { fprintf(stderr, "%s: bind(2): %s\n", __func__, strerror(errno)); goto failure; } - else if (listen(s->fd, QUEUE_LEN)) + else if (listen(fd, QUEUE_LEN)) { fprintf(stderr, "%s: listen(2): %s\n", __func__, strerror(errno)); goto failure; @@ -368,17 +325,45 @@ struct server *server_init(const unsigned short port, struct sockaddr_in in; socklen_t sz = sizeof in; - if (getsockname(s->fd, (struct sockaddr *)&in, &sz)) + if (getsockname(fd, (struct sockaddr *)&in, &sz)) { fprintf(stderr, "%s: getsockname(2): %s\n", __func__, strerror(errno)); goto failure; } - else if (outport) + + *s = (const struct server) + { + .fd = fd, + .cfds = { + [0] = fds[0], + [1] = fds[1] + } + }; + + if (outport) *outport = ntohs(in.sin_port); return s; -failure: - server_close(s); +failure:; + + const int a[] = {fd, fds[0], fds[1]}; + + for (size_t i = 0; sizeof a / sizeof *a; i++) + { + const int fd = a[i]; + + if (fd >= 0 && close(fd)) + fprintf(stderr, "%s: close(2)[%zu]: %s\n", + __func__, i, strerror(errno)); + } + return NULL; } + +int server_notify_close(struct server *const s) +{ + char exit = 0; + + return write(s->cfds[1], &exit, sizeof exit) <= 0 ? -1 : 0; +} |
