commit 843d775d11db3c8819cac0542999f3db47ac9ad7
parent 22845e5f6316831f4b72a6ec8fcd7cf3a5f4713e
Author: MikoĊaj Lenczewski <mblenczewski@gmail.com>
Date: Fri, 7 Feb 2025 17:35:17 +0000
Fix kept-alive connections for http/1.1, flesh out mime types
Diffstat:
6 files changed, 222 insertions(+), 142 deletions(-)
diff --git a/debug.sh b/debug.sh
@@ -2,4 +2,4 @@
set -ex
-lldbgui -- bin/website -v out/
+lldbgui -- bin/website -v $@
diff --git a/website/connection.c b/website/connection.c
@@ -54,8 +54,6 @@ parse_http_request(char *src, struct http_request *request, struct http_headers
while (isspace(*val)) /* strip leading whitespace */
val++;
- printf("%s: %s\n", key, val);
-
for (char *ptr = key; *ptr; ptr++) /* normalise as lowercase */
*ptr = isalpha(*ptr) ? tolower(*ptr) : *ptr;
@@ -214,6 +212,10 @@ prepare_response_headers(struct connection *conn, enum http_status status,
conn->len += snprintf(conn->buf + conn->len, conn->cap - conn->len, HTTP_SEP);
assert(conn->len < conn->cap);
+
+ if (opts.verbose)
+ printf("response headers: %zu bytes\n---\n%.*s",
+ conn->len, (int) conn->len, conn->buf);
}
void
@@ -249,6 +251,7 @@ handle_request(struct io_uring *ioring, struct connection *conn, int webroot)
printf("\tsanitised uri: '%s'\n", path);
}
+ // TODO: read from file cache instead of reading directly?
conn->response.fd = openat(webroot, path, O_RDONLY | O_CLOEXEC);
if (conn->response.fd < 0) {
if (errno == EACCES)
@@ -379,6 +382,7 @@ handle_response_chunk(struct io_uring *ioring, struct connection *conn, int res)
}
if (conn->response_headers.keep_alive) {
+ conn->len = conn->cur = 0;
enqueue_recv(ioring, &conn->ioreq, conn->socket,
conn->buf, conn->cap, 0);
return;
diff --git a/website/main.c b/website/main.c
@@ -6,8 +6,7 @@ struct opts opts = {
.port = "8080",
};
-#define OPTSTR "hv"
-
+#define OPTSTR "hva:p:"
static void
usage(char *prog)
{
@@ -79,6 +78,148 @@ reload_handler(int sig, siginfo_t *info, void *uctx)
}
int
+serve(struct io_uring *ioring, int webroot, int sock, struct connection_pool *pool)
+{
+ listen(sock, SERVER_LISTEN_BACKLOG);
+
+ struct sockaddr_storage client_addr;
+ socklen_t client_addrlen = sizeof client_addr;
+
+ struct ioreq accept_req = {
+ .type = IOREQ_ACCEPT,
+ .fd = sock,
+ .accept.addr = (struct sockaddr *) &client_addr,
+ .accept.addrlen = &client_addrlen,
+ .accept.flags = 0,
+ };
+
+ enqueue_ioreq(ioring, &accept_req);
+ submit_ioreqs(ioring);
+
+ quit = 0;
+ while (!quit) {
+ struct io_uring_cqe *cqe;
+ if (io_uring_wait_cqe(ioring, &cqe) < 0) {
+ fprintf(stderr, "io_uring_wait_cqe() failed\n");
+ return EXIT_FAILURE;
+ }
+
+ unsigned head, seen = 0;
+ io_uring_for_each_cqe(ioring, head, cqe) {
+ struct ioreq *ioreq = io_uring_cqe_get_data(cqe);
+ if (!ioreq) { /* overloaded client error messages */
+ // TODO: do something?
+ }
+
+ int res = cqe->res;
+ switch (ioreq->type) {
+ case IOREQ_ACCEPT: {
+ if (res < 0) { /* multishot failed, restart? */
+ fprintf(stderr, "multishot accept failed\n");
+ return EXIT_FAILURE;
+ }
+
+ struct connection *conn = connection_pool_alloc(pool);
+ if (!conn) { /* overloaded server */
+ close(res);
+ goto next_cqe;
+ }
+
+ if (opts.verbose) {
+ char host[NI_MAXHOST];
+ char port[NI_MAXSERV];
+ getnameinfo((struct sockaddr *) &client_addr,
+ client_addrlen,
+ host, sizeof host,
+ port, sizeof port,
+ NI_NUMERICHOST | NI_NUMERICSERV);
+
+ fprintf(stderr, "[%d] Client connection from %s:%s\n",
+ res, host, port);
+ }
+
+ handle_accept(ioring, conn, res);
+ } break;
+
+ case IOREQ_RECV: {
+ struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
+ assert(conn);
+
+ if (res <= 0) {
+ if (opts.verbose)
+ fprintf(stderr, "recv(): %d (%s), closing connection\n", res, strerror(-res));
+ close_connection(ioring, conn);
+ break;
+ }
+
+ if (opts.verbose)
+ fprintf(stderr, "[%d] Client recv: %d bytes\n",
+ conn->socket, res);
+
+ handle_request_chunk(ioring, conn, webroot, res);
+ } break;
+
+ case IOREQ_SEND: {
+ struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
+ assert(conn);
+
+ if (res <= 0) {
+ if (opts.verbose)
+ fprintf(stderr, "send(): %d (%s), closing connection\n", res, strerror(-res));
+ close_connection(ioring, conn);
+ break;
+ }
+
+ if (opts.verbose)
+ fprintf(stderr, "[%d] Client send(): %d bytes\n",
+ conn->socket, res);
+
+ handle_response_chunk(ioring, conn, res);
+ } break;
+
+ case IOREQ_READ: {
+ struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
+ assert(conn);
+
+ if (res <= 0) {
+ if (opts.verbose)
+ fprintf(stderr, "read(): %d (%s), closing connection\n", res, strerror(-res));
+ close_connection(ioring, conn);
+ break;
+ }
+
+ if (opts.verbose)
+ fprintf(stderr, "[%d] Client read: %d bytes\n",
+ conn->socket, res);
+
+ handle_fileread_chunk(ioring, conn, res);
+ } break;
+
+ case IOREQ_CLOSE: {
+ struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
+ assert(conn);
+
+ if (opts.verbose)
+ fprintf(stderr, "[%d] Client closed connection\n",
+ conn->socket);
+
+ connection_pool_free(pool, conn);
+ } break;
+ }
+
+next_cqe:
+ seen++;
+ }
+
+ io_uring_cq_advance(ioring, seen);
+
+ submit_ioreqs(ioring);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+int
main(int argc, char **argv)
{
if (parse_opts(argc, argv) < 0) {
@@ -151,4 +292,3 @@ main(int argc, char **argv)
#include "net.c"
#include "uri.c"
#include "connection.c"
-#include "server.c"
diff --git a/website/server.c b/website/server.c
@@ -1,135 +0,0 @@
-#include "website.h"
-
-int
-serve(struct io_uring *ioring, int webroot, int sock, struct connection_pool *pool)
-{
- listen(sock, SERVER_LISTEN_BACKLOG);
-
- struct sockaddr_storage client_addr;
- socklen_t client_addrlen = sizeof client_addr;
-
- struct ioreq accept_req = {
- .type = IOREQ_ACCEPT,
- .fd = sock,
- .accept.addr = (struct sockaddr *) &client_addr,
- .accept.addrlen = &client_addrlen,
- .accept.flags = 0,
- };
-
- enqueue_ioreq(ioring, &accept_req);
- submit_ioreqs(ioring);
-
- quit = 0;
- while (!quit) {
- struct io_uring_cqe *cqe;
- if (io_uring_wait_cqe(ioring, &cqe) < 0) {
- fprintf(stderr, "io_uring_wait_cqe() failed\n");
- return EXIT_FAILURE;
- }
-
- unsigned head, seen = 0;
- io_uring_for_each_cqe(ioring, head, cqe) {
- struct ioreq *ioreq = io_uring_cqe_get_data(cqe);
- if (!ioreq) { /* overloaded client error messages */
- // TODO: do something?
- }
-
- int res = cqe->res;
- switch (ioreq->type) {
- case IOREQ_ACCEPT: {
- if (res < 0) { /* multishot failed, restart? */
- fprintf(stderr, "multishot accept failed\n");
- return EXIT_FAILURE;
- }
-
- struct connection *conn = connection_pool_alloc(pool);
- if (!conn) { /* overloaded server */
- close(res);
- goto next_cqe;
- }
-
- char host[NI_MAXHOST];
- char port[NI_MAXSERV];
- getnameinfo((struct sockaddr *) &client_addr,
- client_addrlen,
- host, sizeof host,
- port, sizeof port,
- NI_NUMERICHOST | NI_NUMERICSERV);
-
- fprintf(stderr, "[%d] Client connection from %s:%s\n",
- res, host, port);
-
- handle_accept(ioring, conn, res);
- } break;
-
- case IOREQ_RECV: {
- struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
- assert(conn);
-
- if (res <= 0) {
- fprintf(stderr, "recv(): %d (%s), closing connection\n", res, strerror(-res));
- close_connection(ioring, conn);
- break;
- }
-
- fprintf(stderr, "[%d] Client recv: %d bytes\n",
- conn->socket, res);
-
- handle_request_chunk(ioring, conn, webroot, res);
- } break;
-
- case IOREQ_SEND: {
- struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
- assert(conn);
-
- if (res <= 0) {
- fprintf(stderr, "send(): %d (%s), closing connection\n", res, strerror(-res));
- close_connection(ioring, conn);
- break;
- }
-
- fprintf(stderr, "[%d] Client send(): %d bytes\n",
- conn->socket, res);
-
- handle_response_chunk(ioring, conn, res);
- } break;
-
- case IOREQ_READ: {
- struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
- assert(conn);
-
- if (res <= 0) {
- fprintf(stderr, "read(): %d (%s), closing connection\n", res, strerror(-res));
- close_connection(ioring, conn);
- break;
- }
-
-
- fprintf(stderr, "[%d] Client read: %d bytes\n",
- conn->socket, res);
-
- handle_fileread_chunk(ioring, conn, res);
- } break;
-
- case IOREQ_CLOSE: {
- struct connection *conn = TO_PARENT(ioreq, struct connection, ioreq);
- assert(conn);
-
- fprintf(stderr, "[%d] Client closed connection\n",
- conn->socket);
-
- connection_pool_free(pool, conn);
- } break;
- }
-
-next_cqe:
- seen++;
- }
-
- io_uring_cq_advance(ioring, seen);
-
- submit_ioreqs(ioring);
- }
-
- return EXIT_SUCCESS;
-}
diff --git a/website/utils.c b/website/utils.c
@@ -26,3 +26,8 @@ get_timeout(struct timespec const *ts, uint64_t timeout_ms);
extern inline uint64_t
get_timestamp(struct timespec const *ts);
+
+extern inline void
+timespec_diff(struct timespec const *restrict lhs,
+ struct timespec const *restrict rhs,
+ struct timespec *restrict res);
diff --git a/website/website.h b/website/website.h
@@ -37,7 +37,7 @@
#define CONNECTION_POOL_CAPACITY 1024
#define CONNECTION_CONCURRENT_OPS 2
-#define CONNECTION_BUFFER_SIZE 4096
+#define CONNECTION_BUFFER_SIZE 8192
#define CONNECTION_TIMEOUT_MS -1
/* utilities
@@ -193,6 +193,20 @@ get_timestamp(struct timespec const *ts)
return (NANOS * ts->tv_sec) + ts->tv_nsec;
}
+inline void
+timespec_diff(struct timespec const *restrict lhs,
+ struct timespec const *restrict rhs,
+ struct timespec *restrict res)
+{
+ res->tv_sec = lhs->tv_sec - rhs->tv_sec;
+ res->tv_nsec = lhs->tv_nsec - rhs->tv_nsec;
+
+ if (res->tv_nsec < 0) {
+ res->tv_sec -= 1;
+ res->tv_nsec += NANOS;
+ }
+}
+
/* network interaction
* ===========================================================================
*/
@@ -555,6 +569,33 @@ content_type_from_mime(char *mime)
if (strcmp(mime, "*/*") == 0)
return ANY_CONTENT_TYPE;
+ if (strcmp(mime, "image/bmp") == 0)
+ return IMAGE_BMP;
+
+ if (strcmp(mime, "image/gif") == 0)
+ return IMAGE_GIF;
+
+ if (strcmp(mime, "image/jpeg") == 0)
+ return IMAGE_JPEG;
+
+ if (strcmp(mime, "image/png") == 0)
+ return IMAGE_PNG;
+
+ if (strcmp(mime, "image/svg") == 0)
+ return IMAGE_SVG;
+
+ if (strcmp(mime, "text/plain") == 0)
+ return TEXT_PLAIN;
+
+ if (strcmp(mime, "text/html") == 0)
+ return TEXT_HTML;
+
+ if (strcmp(mime, "text/css") == 0)
+ return TEXT_CSS;
+
+ if (strcmp(mime, "text/javascript") == 0)
+ return TEXT_JAVASCRIPT;
+
return APPLICATION_OCTET_STREAM;
}
@@ -564,9 +605,34 @@ content_type_from_ext(char *ext, size_t len)
if (!ext)
return APPLICATION_OCTET_STREAM;
+ if (len == strlen(".bmp") && strncmp(ext, ".bmp", len) == 0)
+ return IMAGE_BMP;
+
+ if (len == strlen(".gif") && strncmp(ext, ".gif", len) == 0)
+ return IMAGE_GIF;
+
+ if ((len == strlen(".jpg") && strncmp(ext, ".jpg", len) == 0) ||
+ (len == strlen(".jpeg") && strncmp(ext, ".jpeg", len) == 0))
+ return IMAGE_JPEG;
+
+ if (len == strlen(".png") && strncmp(ext, ".png", len) == 0)
+ return IMAGE_PNG;
+
+ if (len == strlen(".svg") && strncmp(ext, ".svg", len) == 0)
+ return IMAGE_SVG;
+
+ if (len == strlen(".txt") && strncmp(ext, ".txt", len) == 0)
+ return TEXT_PLAIN;
+
if (len == strlen(".html") && strncmp(ext, ".html", len) == 0)
return TEXT_HTML;
+ if (len == strlen(".css") && strncmp(ext, ".css", len) == 0)
+ return TEXT_CSS;
+
+ if (len == strlen(".js") && strncmp(ext, ".js", len) == 0)
+ return TEXT_JAVASCRIPT;
+
return APPLICATION_OCTET_STREAM;
}