serve

serve.git
git clone git://git.lenczewski.org/serve.git
Log | Files | Refs

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:
Mdebug.sh | 2+-
Mwebsite/connection.c | 8++++++--
Mwebsite/main.c | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Dwebsite/server.c | 135-------------------------------------------------------------------------------
Mwebsite/utils.c | 5+++++
Mwebsite/website.h | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
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; }