aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/man3/handler_notify_close.351
-rw-r--r--examples/headers/main.c75
-rw-r--r--examples/hello/main.c77
-rw-r--r--examples/put/main.c80
-rw-r--r--handler.c5
-rw-r--r--include/libweb/handler.h1
-rw-r--r--include/libweb/server.h1
-rw-r--r--server.c163
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;
}
diff --git a/handler.c b/handler.c
index 3d806e6..b8ecb52 100644
--- a/handler.c
+++ b/handler.c
@@ -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 */
diff --git a/server.c b/server.c
index 6167db3..11e2af0 100644
--- a/server.c
+++ b/server.c
@@ -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;
+}