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 */