serve

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

website.h (15456B)


      1 #ifndef WEBSITE_H
      2 #define WEBSITE_H
      3 
      4 #include <assert.h>
      5 #include <ctype.h>
      6 #include <inttypes.h>
      7 #include <limits.h>
      8 #include <stdalign.h>
      9 #include <stdbool.h>
     10 #include <stddef.h>
     11 #include <stdio.h>
     12 #include <stdint.h>
     13 #include <stdlib.h>
     14 #include <string.h>
     15 #include <unistd.h>
     16 
     17 #include <signal.h>
     18 
     19 #include <getopt.h>
     20 
     21 #include <fcntl.h>
     22 #include <sys/stat.h>
     23 
     24 #include <sys/socket.h>
     25 #include <netdb.h>
     26 
     27 #include <sys/mman.h>
     28 
     29 #include <liburing.h>
     30 
     31 /* configuration
     32  * ===========================================================================
     33  */
     34 
     35 #define SERVER_LISTEN_BACKLOG 16
     36 
     37 #define CONNECTION_POOL_CAPACITY 1024
     38 
     39 #define CONNECTION_CONCURRENT_OPS 2
     40 #define CONNECTION_BUFFER_SIZE 8192
     41 #define CONNECTION_TIMEOUT_MS -1
     42 
     43 /* utilities
     44  * ===========================================================================
     45  */
     46 
     47 #define NANOS 1000000000ULL
     48 
     49 #define KiB 1024ULL
     50 #define MiB (1024ULL * KiB)
     51 #define GiB (1024ULL * MiB)
     52 #define TiB (1024ULL * GiB)
     53 
     54 #define ARRLEN(arr) (sizeof (arr) / sizeof (arr)[0])
     55 
     56 #define MIN(a, b) ((a) < (b) ? (a) : (b))
     57 #define MAX(a, b) ((a) > (b) ? (a) : (b))
     58 
     59 #define TO_PARENT(child, T, member) \
     60 	((child) ? ((T *) ((uintptr_t) child - offsetof(T, member))) : NULL)
     61 
     62 struct str {
     63 	char *ptr;
     64 	size_t len;
     65 };
     66 
     67 inline char *
     68 strnstr(char *restrict src, size_t len, char const *restrict marker)
     69 {
     70 	char const *end = src + len;
     71 
     72 	while (src < end) {
     73 		char *ptr = src;
     74 		char const *cur = marker;
     75 		while (ptr < end && *cur && *ptr++ == *cur)
     76 			cur++;
     77 
     78 		if (*cur == '\0')
     79 			return src;
     80 
     81 		if (ptr == end)
     82 			goto end;
     83 
     84 		src = ptr;
     85 	}
     86 
     87 end:
     88 	return NULL;
     89 }
     90 
     91 #define ALIGN_PREV(v, align) ((v) & ~((align) - 1))
     92 #define ALIGN_NEXT(v, align) ALIGN_PREV((v) + ((align) - 1), (align))
     93 
     94 struct arena {
     95 	void *ptr;
     96 	uint64_t cap, len;
     97 };
     98 
     99 inline void
    100 arena_reset(struct arena *arena)
    101 {
    102 	arena->len = 0;
    103 }
    104 
    105 inline void *
    106 arena_alloc(struct arena *arena, uint64_t size, uint64_t align)
    107 {
    108 	uint64_t aligned_len = ALIGN_NEXT(arena->len, align);
    109 	if (aligned_len + size > arena->cap)
    110 		return NULL;
    111 
    112 	void *ptr = (void *) ((uintptr_t) arena->ptr + aligned_len);
    113 	arena->len = aligned_len + size;
    114 
    115 	return ptr;
    116 }
    117 
    118 #define ALLOC_ARRAY(arena, T, n) \
    119 	arena_alloc((arena), sizeof(T) * (n), alignof(T))
    120 
    121 #define ALLOC_SIZED(arena, T) ALLOC_ARRAY((arena), T, 1)
    122 
    123 struct list_node {
    124 	struct list_node *prev, *next;
    125 };
    126 
    127 #define FROM_NODE(node, T, member) TO_PARENT((node), T, member)
    128 
    129 struct list {
    130 	struct list_node *head, *tail;
    131 };
    132 
    133 inline void
    134 list_push_head(struct list *restrict list, struct list_node *restrict node)
    135 {
    136 	if (!list->tail)
    137 		list->tail = node;
    138 
    139 	if (list->head)
    140 		list->head->prev = node;
    141 
    142 	node->next = list->head;
    143 	list->head = node;
    144 }
    145 
    146 inline void
    147 list_push_tail(struct list *restrict list, struct list_node *restrict node)
    148 {
    149 	if (!list->head)
    150 		list->head = node;
    151 
    152 	if (list->tail)
    153 		list->tail->next = node;
    154 
    155 	node->prev = list->tail;
    156 	list->tail = node;
    157 }
    158 
    159 inline struct list_node *
    160 list_pop_head(struct list *list)
    161 {
    162 	struct list_node *node = list->head;
    163 
    164 	if (node)
    165 		list->head = list->head->next;
    166 
    167 	return node;
    168 }
    169 
    170 inline struct list_node *
    171 list_pop_tail(struct list *list)
    172 {
    173 	struct list_node *node = list->tail;
    174 
    175 	if (node)
    176 		list->tail = list->tail->prev;
    177 
    178 	return node;
    179 }
    180 
    181 inline struct timespec
    182 get_timeout(struct timespec const *ts, uint64_t timeout_ms)
    183 {
    184 	return (struct timespec) {
    185 		.tv_sec = ts->tv_sec + (timeout_ms / 1000),
    186 		.tv_nsec = ts->tv_nsec + ((timeout_ms % 1000) * (NANOS / 1000)),
    187 	};
    188 }
    189 
    190 inline uint64_t
    191 get_timestamp(struct timespec const *ts)
    192 {
    193 	return (NANOS * ts->tv_sec) + ts->tv_nsec;
    194 }
    195 
    196 inline void
    197 timespec_diff(struct timespec const *restrict lhs,
    198 	      struct timespec const *restrict rhs,
    199 	      struct timespec *restrict res)
    200 {
    201 	res->tv_sec = lhs->tv_sec - rhs->tv_sec;
    202 	res->tv_nsec = lhs->tv_nsec - rhs->tv_nsec;
    203 
    204 	if (res->tv_nsec < 0) {
    205 		res->tv_sec -= 1;
    206 		res->tv_nsec += NANOS;
    207 	}
    208 }
    209 
    210 /* network interaction
    211  * ===========================================================================
    212  */
    213 
    214 #define IOREQ_MAX_IOVS 8
    215 
    216 struct ioreq {
    217 	enum {
    218 		IOREQ_ACCEPT,
    219 		IOREQ_RECV,
    220 		IOREQ_SEND,
    221 		IOREQ_READ,
    222 		IOREQ_CLOSE,
    223 	} type;
    224 
    225 	int fd;
    226 
    227 	union {
    228 		struct {
    229 			struct sockaddr *addr;
    230 			socklen_t *addrlen;
    231 			int flags;
    232 		} accept;
    233 
    234 		struct {
    235 			void *buf;
    236 			size_t len;
    237 			int flags;
    238 		} recv, send;
    239 
    240 		struct {
    241 			void *buf;
    242 			size_t len;
    243 			uint64_t off;
    244 		} read;
    245 	};
    246 };
    247 
    248 int
    249 ioring_init(struct io_uring *ioring);
    250 
    251 void
    252 enqueue_ioreq(struct io_uring *ioring, struct ioreq *ioreq);
    253 
    254 inline void
    255 enqueue_accept(struct io_uring *ioring, struct ioreq *req, int fd,
    256 	       struct sockaddr *addr, socklen_t *addrlen, int flags)
    257 {
    258 	req->type = IOREQ_ACCEPT;
    259 	req->fd = fd;
    260 	req->accept.addr = addr;
    261 	req->accept.addrlen = addrlen;
    262 	req->accept.flags = flags;
    263 
    264 	enqueue_ioreq(ioring, req);
    265 }
    266 
    267 inline void
    268 enqueue_recv(struct io_uring *ioring, struct ioreq *req, int fd,
    269 	     void *buf, size_t len, int flags)
    270 {
    271 	req->type = IOREQ_RECV;
    272 	req->fd = fd;
    273 	req->recv.buf = buf;
    274 	req->recv.len = len;
    275 	req->recv.flags = flags;
    276 
    277 	enqueue_ioreq(ioring, req);
    278 }
    279 
    280 inline void
    281 enqueue_send(struct io_uring *ioring, struct ioreq *req, int fd,
    282 	     void *buf, size_t len, int flags)
    283 {
    284 	req->type = IOREQ_SEND;
    285 	req->fd = fd;
    286 	req->send.buf = buf;
    287 	req->send.len = len;
    288 	req->send.flags = flags;
    289 
    290 	enqueue_ioreq(ioring, req);
    291 }
    292 
    293 inline void
    294 enqueue_read(struct io_uring *ioring, struct ioreq *req, int fd,
    295 	     void *buf, size_t len, uint64_t off)
    296 {
    297 	req->type = IOREQ_READ;
    298 	req->fd = fd;
    299 	req->read.buf = buf;
    300 	req->read.len = len;
    301 	req->read.off = off;
    302 
    303 	enqueue_ioreq(ioring, req);
    304 }
    305 
    306 inline void
    307 enqueue_close(struct io_uring *ioring, struct ioreq *req, int fd)
    308 {
    309 	req->type = IOREQ_CLOSE;
    310 	req->fd = fd;
    311 
    312 	enqueue_ioreq(ioring, req);
    313 }
    314 
    315 void
    316 submit_ioreqs(struct io_uring *ioring);
    317 
    318 int
    319 create_server_socket(char const *restrict host, char const *restrict port);
    320 
    321 /* uri parsing and normalisation
    322  * ===========================================================================
    323  */
    324 
    325 struct urifrag {
    326 	char *ptr;
    327 	int len;
    328 };
    329 
    330 bool
    331 urifrag_try_normalise(struct urifrag *frag);
    332 
    333 void
    334 urifrag_remove_dot_segments(struct urifrag *frag);
    335 
    336 struct uri {
    337 	struct urifrag scheme;
    338 	struct urifrag user;
    339 	struct urifrag host;
    340 	struct urifrag port;
    341 	struct urifrag path;
    342 	struct urifrag query;
    343 	struct urifrag fragment;
    344 };
    345 
    346 struct uri
    347 uri_parse(char *buf, size_t len);
    348 
    349 /* http connection state
    350  * ===========================================================================
    351  */
    352 
    353 #define HTTP_SEP "\r\n"
    354 #define HTTP_TERM HTTP_SEP HTTP_SEP
    355 
    356 enum http_method {
    357 	UNKNOWN_METHOD,
    358 	HTTP_HEAD,
    359 	HTTP_GET,
    360 };
    361 
    362 inline enum http_method
    363 method_from_str(char *method)
    364 {
    365 	if (strcmp(method, "HEAD") == 0)
    366 		return HTTP_HEAD;
    367 
    368 	if (strcmp(method, "GET") == 0)
    369 		return HTTP_GET;
    370 
    371 	return UNKNOWN_METHOD;
    372 }
    373 
    374 inline char const *
    375 method_str(enum http_method method)
    376 {
    377 	switch (method) {
    378 	case HTTP_HEAD:	return "HEAD";
    379 	case HTTP_GET:	return "GET";
    380 	default: return NULL;
    381 	}
    382 }
    383 
    384 enum http_version {
    385 	HTTP_09,
    386 	HTTP_10,
    387 	HTTP_11,
    388 	HTTP_20,
    389 	HTTP_30,
    390 };
    391 
    392 inline enum http_version
    393 version_from_str(char *version)
    394 {
    395 	if (strcmp(version, "HTTP/0.9") == 0)
    396 		return HTTP_09;
    397 
    398 	if (strcmp(version, "HTTP/1.0") == 0)
    399 		return HTTP_10;
    400 
    401 	if (strcmp(version, "HTTP/1.1") == 0)
    402 		return HTTP_11;
    403 
    404 	if (strcmp(version, "HTTP/2") == 0)
    405 		return HTTP_20;
    406 
    407 	if (strcmp(version, "HTTP/3") == 0)
    408 		return HTTP_30;
    409 
    410 	return HTTP_09;
    411 }
    412 
    413 inline char const *
    414 version_str(enum http_version version)
    415 {
    416 	switch (version) {
    417 	case HTTP_09: return "HTTP/0.9";
    418 	case HTTP_10: return "HTTP/1.0";
    419 	case HTTP_11: return "HTTP/1.1";
    420 	case HTTP_20: return "HTTP/2";
    421 	case HTTP_30: return "HTTP/3";
    422 	default: return NULL;
    423 	}
    424 }
    425 
    426 enum http_status {
    427 	HTTP_200 = 200, /* OK */
    428 	HTTP_204 = 204, /* No Content */
    429 	HTTP_206 = 206, /* Partial Content */
    430 
    431 	HTTP_400 = 400, /* Bad Request */
    432 	HTTP_401 = 401, /* Unauthorized */
    433 	HTTP_403 = 403, /* Forbidden */
    434 	HTTP_404 = 404, /* Not Found */
    435 	HTTP_405 = 405, /* Method Not Allowed */
    436 	HTTP_406 = 406, /* Not Acceptable */
    437 	HTTP_408 = 408, /* Request Timeout */
    438 	HTTP_411 = 411, /* Length Required */
    439 	HTTP_413 = 413, /* Payload Too Large */
    440 	HTTP_415 = 415, /* Unsupported Media Type */
    441 	HTTP_416 = 416, /* Range Not Satisfiable */
    442 	HTTP_422 = 422, /* Unprocessable Content */
    443 	HTTP_431 = 431, /* Request Header Fields Too Large */
    444 
    445 	HTTP_500 = 500, /* Internal Server Error */
    446 	HTTP_501 = 501, /* Not Implemented */
    447 	HTTP_503 = 503, /* Service Unavailable */
    448 	HTTP_505 = 505, /* HTTP Version Not Supported */
    449 };
    450 
    451 inline char const *
    452 status_str(enum http_status status)
    453 {
    454 	switch (status) {
    455 	case HTTP_200: return "OK";
    456 	case HTTP_204: return "No Content";
    457 	case HTTP_206: return "Partial Content";
    458 
    459 	case HTTP_400: return "Bad Request";
    460 	case HTTP_401: return "Unauthorized";
    461 	case HTTP_403: return "Forbidden";
    462 	case HTTP_404: return "Not Found";
    463 	case HTTP_405: return "Method Not Allowed";
    464 	case HTTP_406: return "Not Acceptable";
    465 	case HTTP_408: return "Request Timeout";
    466 	case HTTP_411: return "Length Required";
    467 	case HTTP_413: return "Payload Too Large";
    468 	case HTTP_415: return "Unsupported Media Type";
    469 	case HTTP_416: return "Range Not Satisfiable";
    470 	case HTTP_422: return "Unprocessable Content";
    471 	case HTTP_431: return "Request Header Fields Too Large";
    472 
    473 	case HTTP_500: return "Internal Server Error";
    474 	case HTTP_501: return "Not Implemented";
    475 	case HTTP_503: return "Service Unavailable";
    476 	case HTTP_505: return "HTTP Version Not Supported";
    477 
    478 	default: return NULL;
    479 	}
    480 }
    481 
    482 enum content_encoding {
    483 	ENCODING_NONE,
    484 	ENCODING_GZIP,
    485 };
    486 
    487 inline char const *
    488 content_encoding_str(enum content_encoding encoding)
    489 {
    490 	switch (encoding) {
    491 	case ENCODING_GZIP: return "gzip";
    492 	default: return NULL;
    493 	}
    494 }
    495 
    496 inline enum content_encoding
    497 content_encoding_from_list(char *list)
    498 {
    499 	enum content_encoding best_encoding = ENCODING_NONE;
    500 
    501 	char *enc, *saveptr;
    502 	for (enc = strtok_r(list, ", ", &saveptr);
    503 	     enc;
    504 	     enc = strtok_r(NULL, ", ", &saveptr)) {
    505 		if (strcmp(enc, "gzip") == 0)
    506 			best_encoding = MAX(best_encoding, ENCODING_GZIP);
    507 	}
    508 
    509 	return best_encoding;
    510 }
    511 
    512 enum content_type {
    513 	APPLICATION_OCTET_STREAM,
    514 
    515 	IMAGE_BMP,
    516 	IMAGE_GIF,
    517 	IMAGE_JPEG,
    518 	IMAGE_PNG,
    519 	IMAGE_SVG,
    520 
    521 	TEXT_PLAIN,
    522 	TEXT_HTML,
    523 	TEXT_CSS,
    524 	TEXT_JAVASCRIPT,
    525 
    526 	ANY_CONTENT_TYPE,
    527 };
    528 
    529 inline char const *
    530 content_type_str(enum content_type type)
    531 {
    532 	switch (type) {
    533 	case APPLICATION_OCTET_STREAM:	return "application/octet-stream";
    534 
    535 	case IMAGE_BMP:			return "image/bmp";
    536 	case IMAGE_GIF:			return "image/gif";
    537 	case IMAGE_JPEG:		return "image/jpeg";
    538 	case IMAGE_PNG:			return "image/png";
    539 	case IMAGE_SVG:			return "image/svg+xml";
    540 
    541 	case TEXT_PLAIN:		return "text/plain";
    542 	case TEXT_HTML:			return "text/html";
    543 	case TEXT_CSS:			return "text/css";
    544 	case TEXT_JAVASCRIPT:		return "text/javascript";
    545 
    546 	default: return NULL;
    547 	}
    548 }
    549 
    550 inline bool
    551 content_type_is_text(enum content_type type)
    552 {
    553 	switch (type) {
    554 	case IMAGE_SVG:
    555 	case TEXT_PLAIN:
    556 	case TEXT_HTML:
    557 	case TEXT_CSS:
    558 	case TEXT_JAVASCRIPT:
    559 		return true;
    560 
    561 	default:
    562 		return false;
    563 	}
    564 }
    565 
    566 inline enum content_type
    567 content_type_from_mime(char *mime)
    568 {
    569 	if (strcmp(mime, "*/*") == 0)
    570 		return ANY_CONTENT_TYPE;
    571 
    572 	if (strcmp(mime, "image/bmp") == 0)
    573 		return IMAGE_BMP;
    574 
    575 	if (strcmp(mime, "image/gif") == 0)
    576 		return IMAGE_GIF;
    577 
    578 	if (strcmp(mime, "image/jpeg") == 0)
    579 		return IMAGE_JPEG;
    580 
    581 	if (strcmp(mime, "image/png") == 0)
    582 		return IMAGE_PNG;
    583 
    584 	if (strcmp(mime, "image/svg+xml") == 0)
    585 		return IMAGE_SVG;
    586 
    587 	if (strcmp(mime, "text/plain") == 0)
    588 		return TEXT_PLAIN;
    589 
    590 	if (strcmp(mime, "text/html") == 0)
    591 		return TEXT_HTML;
    592 
    593 	if (strcmp(mime, "text/css") == 0)
    594 		return TEXT_CSS;
    595 
    596 	if (strcmp(mime, "text/javascript") == 0)
    597 		return TEXT_JAVASCRIPT;
    598 
    599 	return APPLICATION_OCTET_STREAM;
    600 }
    601 
    602 inline enum content_type
    603 content_type_from_ext(char *ext, size_t len)
    604 {
    605 	if (!ext)
    606 		return APPLICATION_OCTET_STREAM;
    607 
    608 	if (len == strlen(".bmp") && strncmp(ext, ".bmp", len) == 0)
    609 		return IMAGE_BMP;
    610 
    611 	if (len == strlen(".gif") && strncmp(ext, ".gif", len) == 0)
    612 		return IMAGE_GIF;
    613 
    614 	if ((len == strlen(".jpg") && strncmp(ext, ".jpg", len) == 0) ||
    615 	    (len == strlen(".jpeg") && strncmp(ext, ".jpeg", len) == 0))
    616 		return IMAGE_JPEG;
    617 
    618 	if (len == strlen(".png") && strncmp(ext, ".png", len) == 0)
    619 		return IMAGE_PNG;
    620 
    621 	if (len == strlen(".svg") && strncmp(ext, ".svg", len) == 0)
    622 		return IMAGE_SVG;
    623 
    624 	if (len == strlen(".txt") && strncmp(ext, ".txt", len) == 0)
    625 		return TEXT_PLAIN;
    626 
    627 	if (len == strlen(".html") && strncmp(ext, ".html", len) == 0)
    628 		return TEXT_HTML;
    629 
    630 	if (len == strlen(".css") && strncmp(ext, ".css", len) == 0)
    631 		return TEXT_CSS;
    632 
    633 	if (len == strlen(".js") && strncmp(ext, ".js", len) == 0)
    634 		return TEXT_JAVASCRIPT;
    635 
    636 	return APPLICATION_OCTET_STREAM;
    637 }
    638 
    639 struct http_request {
    640 	enum http_method method;
    641 	enum http_version version;
    642 	struct uri uri;
    643 };
    644 
    645 struct http_headers {
    646 	int keep_alive;
    647 	char *accept;
    648 	enum content_encoding encoding;
    649 	bool have_range;
    650 	struct { uint64_t begin, end; } range;
    651 	enum content_type content_type;
    652 	uint64_t content_length;
    653 };
    654 
    655 void
    656 parse_http_request(char *src, struct http_request *request, struct http_headers *headers);
    657 
    658 struct http_response {
    659 	int fd;
    660 	size_t len;
    661 	uint64_t off;
    662 };
    663 
    664 struct connection {
    665 	int socket;
    666 
    667 	struct ioreq ioreq;
    668 
    669 	char buf[CONNECTION_BUFFER_SIZE];
    670 	size_t cap, len, cur;
    671 
    672 	struct http_request request;
    673 	struct http_headers request_headers;
    674 	struct http_response response;
    675 	struct http_headers response_headers;
    676 
    677 	struct timespec timeout;
    678 
    679 	struct list_node list_node;
    680 };
    681 
    682 void
    683 handle_accept(struct io_uring *ioring, struct connection *conn, int sock);
    684 
    685 void
    686 handle_request_chunk(struct io_uring *ioring, struct connection *conn, int webroot, int res);
    687 
    688 void
    689 handle_request(struct io_uring *ioring, struct connection *conn, int webroot);
    690 
    691 void
    692 handle_response_chunk(struct io_uring *ioring, struct connection *conn, int res);
    693 
    694 void
    695 handle_fileread_chunk(struct io_uring *ioring, struct connection *conn, int res);
    696 
    697 void
    698 error_connection(struct io_uring *ioring, struct connection *conn,
    699 		 enum http_status err, bool linger);
    700 
    701 void
    702 close_connection(struct io_uring *ioring, struct connection *conn);
    703 
    704 struct connection_pool {
    705 	struct connection *ptr;
    706 	struct list freelist;
    707 };
    708 
    709 inline int
    710 connection_pool_init(struct connection_pool *pool, size_t capacity)
    711 {
    712 	size_t cap = capacity * sizeof *pool->ptr;
    713 	int prot = PROT_READ | PROT_WRITE;
    714 	int flags = MAP_PRIVATE | MAP_ANONYMOUS;
    715 
    716 	if ((pool->ptr = mmap(NULL, cap, prot, flags, -1, 0)) == MAP_FAILED) {
    717 		fprintf(stderr, "Failed to map memory for connection pool\n");
    718 		return -1;
    719 	}
    720 
    721 	madvise(pool->ptr, cap, MADV_HUGEPAGE);
    722 
    723 	pool->freelist.head = pool->freelist.tail = NULL;
    724 	for (size_t i = 0; i < capacity; i++)
    725 		list_push_tail(&pool->freelist, &pool->ptr[i].list_node);
    726 
    727 	return 0;
    728 }
    729 
    730 inline struct connection *
    731 connection_pool_alloc(struct connection_pool *pool)
    732 {
    733 	struct list_node *node = list_pop_head(&pool->freelist);
    734 	return FROM_NODE(node, struct connection, list_node);
    735 }
    736 
    737 inline void
    738 connection_pool_free(struct connection_pool *restrict pool,
    739 		     struct connection *restrict conn)
    740 {
    741 	list_push_tail(&pool->freelist, &conn->list_node);
    742 }
    743 
    744 /* server application
    745  * ===========================================================================
    746  */
    747 
    748 struct opts {
    749 	int verbose;
    750 	char *host, *port;
    751 	char *webroot;
    752 };
    753 
    754 extern struct opts opts;
    755 
    756 extern sig_atomic_t quit;
    757 extern sig_atomic_t reload_tree;
    758 
    759 int
    760 serve(struct io_uring *ioring, int webroot, int sock, struct connection_pool *pool);
    761 
    762 #endif /* WEBSITE_H */