2#ifndef _DARWIN_C_SOURCE
3#define _DARWIN_C_SOURCE
5#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && \
6 !defined(__DragonFly__)
7#define _POSIX_C_SOURCE 200809L
18#include "../../sys/io/reactor_rx.h"
19#include "../../sys/io/async_debug.h"
23#include <ttak/mols_control.h>
24#include <ttak/net/lattice.h>
25#include <ttak/priority/scheduler.h>
39#ifndef CWIST_BAREMETAL
42#ifndef CWIST_PLATFORM_NO_PROC
52#if defined(CWIST_PLATFORM_HOSTED)
58#ifndef CWIST_BAREMETAL
60#include <sys/socket.h>
61#include <netinet/in.h>
62#include <netinet/tcp.h>
65#ifndef CWIST_PLATFORM_NO_PROC
66#include <sys/resource.h>
73#include <sys/sendfile.h>
75#if defined(__APPLE__) || defined(__FreeBSD__)
78#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
82#if defined(_WIN32) || defined(_WIN64)
84#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
86#include <sys/sysctl.h>
100#if defined(_WIN32) || defined(_WIN64)
103 return (
long)
sysinfo.dwNumberOfProcessors;
104#elif defined(__linux__)
111#if defined(_SC_NPROCESSORS_ONLN)
116#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
119 size_t len =
sizeof(
nproc);
121#if defined(HW_NCPUONLINE)
130#elif defined(_SC_NPROCESSORS_ONLN)
139static unsigned int g_http_pool_core_limit = 0;
146 g_http_pool_core_limit =
limit;
160 if (g_http_pool_core_limit > 0) {
161 return (
long)g_http_pool_core_limit;
163 const char *
env =
getenv(
"CWIST_WORKER_THREADS");
167 if (
end !=
env && *
end ==
'\0' &&
override > 0)
return override;
173 long workers =
cores;
183 const char *
c1m =
getenv(
"CWIST_C1M_MODE");
233#ifndef CWIST_PLATFORM_NO_SOCKETS
234#define HTTP_TASKS_PER_THREAD 32768
240} http_thread_worker_t;
242static size_t g_rr_index = 0;
243static long g_http_thread_count = 0;
244static http_thread_worker_t *g_workers =
NULL;
247static _Atomic
long g_http_inflight = 0;
248static _Atomic
bool g_http_pool_stopping =
false;
249static _Atomic
long g_http_continuation_shed = 0;
259static _Atomic
uint64_t g_reactor_gen = 0;
260static _Atomic
long g_reactor_posters = 0;
304#define CWIST_HTTP_INFLIGHT_PER_THREAD 32
305#define CWIST_HTTP_INFLIGHT_FD_RESERVE 4096
314static long cwist_http_inflight_limit(
void) {
317#ifndef CWIST_PLATFORM_NO_PROC
330static const char CWIST_HTTP_503[] =
331 "HTTP/1.1 503 Service Unavailable\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
340static void *http_pool_worker(
void *arg) {
341 http_thread_worker_t *
w = (http_thread_worker_t *)arg;
342 t_current_worker =
w;
353#define CWIST_POOL_STACK_SIZE (256 * 1024)
355typedef struct http_pool_task {
357 void (*handler_func)(
int,
void *);
359 struct http_pool_task *next;
375 bool primitives_live;
378} http_dynamic_pool_t;
380static http_dynamic_pool_t g_dyn_pool;
385static void http_rx_recv_cb(
void *
conn_ptr,
int res);
394typedef struct http_live_conn {
397 struct http_live_conn *prev, *next;
400#ifndef CWIST_PLATFORM_NO_PROC
403static _Atomic
bool g_draining =
false;
407static _Atomic
long g_live_count;
410#ifndef CWIST_PLATFORM_NO_PROC
411static void http_live_dec(
void) {
439static void http_live_serve(
int fd,
void (*handler)(
int,
void *),
void *ctx) {
440#ifdef CWIST_PLATFORM_NO_PROC
443 int hfd = fd >= 0 ?
dup(fd) : -1;
450 live.next = g_live_head;
451 if (g_live_head) g_live_head->prev = &
live;
464 g_live_head =
live.next;
474#ifdef CWIST_PLATFORM_NO_PROC
487static void http_live_drain(
void) {
488#ifdef CWIST_PLATFORM_NO_PROC
513 CWIST_LOG_WARN(
"[http] %ld classic connection(s) still busy after the %d s drain; "
514 "shutting them down",
527 CWIST_LOG_WARN(
"[http] still waiting for %ld classic handler(s) to return (%ld s)",
543static void *http_dynamic_worker_loop(
void) {
553 static _Atomic
long idle_timeout_ms = -1;
556 const char *
env =
getenv(
"CWIST_POOL_IDLE_TIMEOUT_MS");
564 if (
ms < 0)
ms = 2000;
574 ts.tv_sec +=
ms / 1000;
575 ts.tv_nsec += (
ms % 1000) * 1000000L;
576 if (
ts.tv_nsec >= 1000000000L) {
578 ts.tv_nsec -= 1000000000L;
587 if (
current > g_http_thread_count) {
600 task = g_dyn_pool.head;
602 g_dyn_pool.head =
task->next;
603 if (!g_dyn_pool.head) g_dyn_pool.tail =
NULL;
609 int fd =
task->client_fd;
611 void *ctx =
task->ctx;
616 if (handler) http_live_serve(fd, handler, ctx);
625static void *http_dynamic_worker_thread(
void *arg) {
627 http_dynamic_worker_loop();
641static bool http_spawn_worker(
void) {
651 CWIST_LOG_WARN(
"[http] pthread_attr_setstacksize(%d) failed; using default stack",
679 const char *
c1m =
getenv(
"CWIST_C1M_MODE");
702 g_workers =
cwist_alloc(g_http_thread_count *
sizeof(http_thread_worker_t));
703 if (!g_workers)
return -1;
705 if (!g_worker_loads) {
710 for (
int i = 0;
i < g_http_thread_count;
i++)
atomic_init(&g_worker_loads[
i], 0);
712 memset(g_workers, 0, g_http_thread_count *
sizeof(http_thread_worker_t));
714 for (
int i = 0;
i < g_http_thread_count;
i++) {
716 if (!g_workers[
i].reactor)
return -1;
719 cwist_reactor_set_rx_cb(g_workers[
i].reactor, http_rx_recv_cb);
727 g_dyn_pool.head =
NULL;
728 g_dyn_pool.tail =
NULL;
737 if (!g_dyn_pool.primitives_live) {
740 g_dyn_pool.primitives_live =
true;
747 long prewarm = g_http_thread_count;
748 const char *
pw =
getenv(
"CWIST_POOL_PREWARM");
758 if (!http_spawn_worker()) {
773 long limit = cwist_http_inflight_limit();
775 if (inflight >
limit) {
790 node->client_fd = client_fd;
791 node->handler_func = handler;
796 if (!g_dyn_pool.tail) {
797 g_dyn_pool.head = node;
798 g_dyn_pool.tail = node;
800 g_dyn_pool.tail->next = node;
801 g_dyn_pool.tail = node;
825 if (client_fd < 0 || !handler)
return false;
847 g_dyn_pool.head =
NULL;
848 g_dyn_pool.tail =
NULL;
851 if (node->client_fd >= 0)
close(node->client_fd);
867 if (g_dyn_pool.primitives_live) {
875 if ((
now.tv_sec -
start.tv_sec) * 1000 + (
now.tv_nsec -
start.tv_nsec) / 1000000 >=
887 g_dyn_pool.primitives_live =
false;
896 for (
int i = 0;
i < g_http_thread_count;
i++) {
897 if (g_workers[
i].reactor) {
901 for (
int i = 0;
i < g_http_thread_count;
i++) {
903 if (g_workers[
i].reactor) {
905 g_workers[
i].reactor =
NULL;
910 if (g_worker_loads) {
912 g_worker_loads =
NULL;
933static void http_async_event_cb(
int fd,
void *ctx);
941static uint32_t cwist_http_keep_alive_timeout_sec(
void) {
945 const char *
env =
getenv(
"CWIST_HTTP_KEEP_ALIVE_TIMEOUT");
960#define CWIST_ASYNC_STASH_IDLE_SEC_DEFAULT 10
968static uint32_t http_async_stash_idle_sec(
void) {
969 static _Atomic
int cached = -1;
972 const char *s =
getenv(
"CWIST_ASYNC_STASH_IDLE_SEC");
1006#define CWIST_RX_REARM_MIN_AVAIL 1024
1012#define CWIST_ASYNC_STASH_IDLE 4096
1041 if (conn->
len == 0) {
1042 if (conn->
cap > 65536) {
1077 http_async_ctx_t next = {
1093 }
else if (conn->
cap > 0) {
1104 if (cwist_reactor_recv_arm(reactor, fd, conn->
rbuf + conn->
len, (
unsigned)
avail,
1113 return cwist_reactor_add(reactor, fd, http_async_event_cb, &next,
sizeof(next));
1146 http_async_stash_idle_shrink(conn);
1163 http_async_conn_release(conn);
1176 http_async_stash_idle_shrink(conn);
1178 if (!http_async_arm_wait(fd, conn, handler, ctx, reactor)) {
1179 if (cwist_async_debug_enabled()) {
1182 if (
n <= 5 ||
n % 10000 == 0)
1183 fprintf(
stderr,
"[async] rearm failed fd=%d total=%ld\n", fd,
n);
1186 http_async_conn_release(conn);
1195static void http_rx_recv_cb(
void *
conn_ptr,
int res) {
1203 http_async_conn_release(conn);
1210 }
else if (res < 0 && res != -
EAGAIN) {
1212 http_async_conn_release(conn);
1214 }
else if (res > 0) {
1248 http_async_ctx_t next = {
1249 .client_fd = conn->
fd,
1257 http_async_conn_release(conn);
1276 cwist_reactor_probe_record(conn->
reactor,
false, (
t1 -
t0) / 1000);
1293static void http_async_event_cb(
int fd,
void *ctx) {
1294 http_async_ctx_t *
c = (http_async_ctx_t *)ctx;
1299 uint32_t timeout_sec = cwist_http_keep_alive_timeout_sec();
1307 http_async_close(fd, conn);
1321 if (http_async_arm_wait(fd, conn, handler,
c->ctx,
c->reactor))
return;
1324 http_async_dispatch(fd, conn, handler,
c->ctx,
c->reactor);
1329 http_async_ctx_t next;
1330} http_async_continuation_t;
1338static void http_async_continue(
void *ctx) {
1343 http_async_close(next.client_fd, next.conn);
1346 http_async_event_cb(next.client_fd, &next);
1359 if (client_fd < 0 || !reactor || !conn)
return false;
1363 http_async_close(client_fd, conn);
1367 http_async_close(client_fd, conn);
1371 http_async_ctx_t next = {
1372 .client_fd = client_fd,
1378 if (conn->
len > 0) {
1384 http_async_close(client_fd, conn);
1389 .cb = http_async_continue,
1396 if (cwist_async_debug_enabled() && (
n <= 5 ||
n % 10000 == 0))
1397 fprintf(
stderr,
"[async] continuation shed fd=%d total=%ld\n", client_fd,
n);
1399 http_async_close(client_fd, conn);
1407 http_async_stash_idle_shrink(conn);
1412 if (!http_async_arm_wait(client_fd, conn, conn->
handler, conn->
user_ctx, reactor)) {
1414 http_async_conn_release(conn);
1426 if (client_fd >= 0) {
1444 http_async_conn_release(conn);
1457 http_async_close(client_fd, conn);
1469 long limit = cwist_http_inflight_limit();
1471 if (inflight >
limit) {
1490 memset(conn, 0,
sizeof(*conn));
1491 conn->
fd = client_fd;
1500 if (g_worker_loads && g_http_thread_count > 1) {
1504 g_rr_index = (g_rr_index + 1) % (
size_t)g_http_thread_count;
1509 if (g_worker_loads) {
1519 if (http_async_arm_wait(client_fd, conn, handler, ctx,
w->
reactor))
return true;
1521 if (cwist_async_debug_enabled()) {
1524 if (
n <= 5 ||
n % 10000 == 0)
1525 fprintf(
stderr,
"[async] submit-add failed fd=%d total=%ld worker=%zu\n", client_fd,
n,
1528 http_async_conn_release(conn);
1564 default:
return "UNKNOWN";
1568#define MAKE_MAGIC4(a, b, c, d) \
1569 ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
1571#define MAGIC_GET MAKE_MAGIC4('G', 'E', 'T', ' ')
1572#define MAGIC_POST MAKE_MAGIC4('P', 'O', 'S', 'T')
1573#define MAGIC_PUT MAKE_MAGIC4('P', 'U', 'T', ' ')
1574#define MAGIC_DELE MAKE_MAGIC4('D', 'E', 'L', 'E')
1575#define MAGIC_HEAD MAKE_MAGIC4('H', 'E', 'A', 'D')
1610 }
else if (len == 4) {
1615 }
else if (len == 5) {
1617 }
else if (len == 6) {
1619 }
else if (len == 7) {
1642static void *cwist_http_struct_alloc(
cwist_arena_t *arena,
size_t size) {
1669 if (!str)
return NULL;
1693 const char *data,
size_t len) {
1700 if (data && len > 0)
memcpy(buf, data, len);
1720 size_t key_len,
const char *value,
1744 node->
key = cwist_http_sstring_create(arena);
1745 node->
value = cwist_http_sstring_create(arena);
1748 bool ok = node->
key && node->
value;
1751 ok = cwist_error_is_ok(&
kerr);
1756 ok = cwist_error_is_ok(&
verr);
1766 "Failed to allocate header strings");
1773 err.error.err_i16 = 0;
1786 const char *key,
const char *value) {
1787 return cwist_http_header_add_ex_len(
head, arena, key, key ?
strlen(key) : 0, value,
1788 value ?
strlen(value) : 0);
1801 const char *value) {
1824 node->
key = cwist_http_sstring_create(arena);
1825 node->
value = cwist_http_sstring_create(arena);
1833 "Failed to allocate header strings");
1843 err.error.err_i16 = 0;
1848static bool cwist_http_header_text_is_safe(
const char *text) {
1849 return !text || !
strpbrk(text,
"\r\n");
1862 const char *value) {
1866 if (!
head || !cwist_http_header_text_is_safe(key) || !cwist_http_header_text_is_safe(value)) {
1868 err.error.err_i16 = -1;
1871 return cwist_http_header_add_ex(
head,
NULL, key, value);
1908 if (!
head || !key)
return 0;
1943 cwist_http_header_add_static(&res->
headers, arena,
"X-Frame-Options",
"DENY");
1946 cwist_http_header_add_static(&res->
headers, arena,
"X-Content-Type-Options",
"nosniff");
1949 cwist_http_header_add_static(&res->
headers, arena,
"Referrer-Policy",
1950 "strict-origin-when-cross-origin");
1953 cwist_http_header_add_static(
1954 &res->
headers, arena,
"Content-Security-Policy",
1955 "default-src 'self'; "
1956 "script-src 'self' https://cdnjs.cloudflare.com; "
1957 "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; "
1958 "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net; "
1960 "connect-src 'self'; "
1961 "frame-ancestors 'none'; "
1963 "form-action 'self'; "
1964 "object-src 'none';");
1967 cwist_http_header_add_static(&res->
headers, arena,
"Cross-Origin-Resource-Policy",
1975 cwist_http_header_add_static(&res->
headers, arena,
"Permissions-Policy",
1976 "camera=(), microphone=(), geolocation=(), payment=(), "
1977 "usb=(), interest-cohort=()");
1980 cwist_http_header_add_static(&res->
headers, arena,
"Cross-Origin-Opener-Policy",
"same-origin");
1997 cwist_http_header_add_static(&res->
headers, arena,
"Strict-Transport-Security",
1998 "max-age=31536000; includeSubDomains");
2012 if (!
curr->arena_owned) {
2029static bool cwist_get_cached_date_header(
char out_buf[36]) {
2066 req->
path = cwist_http_sstring_create(arena);
2067 req->
query = cwist_http_sstring_create(arena);
2070 req->
version = cwist_http_sstring_create(arena);
2072 req->
body = cwist_http_sstring_create(arena);
2111 strftime(buf, len,
"%a, %d %b %Y %H:%M:%S GMT", &
tm);
2120 if (!str)
return (
time_t)-1;
2122 const char *
fmt =
"%a, %d %b %Y %H:%M:%S %Z";
2125 fmt =
"%a, %d-%b-%y %H:%M:%S %Z";
2127 fmt =
"%a %b %d %H:%M:%S %Y";
2146 if (fd <= 0)
return s;
2148#ifndef CWIST_PLATFORM_NO_SOCKETS
2156 fprintf(
stdout,
"[ERROR] Failed to get client info from file descriptor");
2247 bool arena_borrowed) {
2250 if (!res)
return NULL;
2252 res->
version = cwist_http_sstring_create(arena);
2254 res->
status_text = cwist_http_sstring_create(arena);
2256 res->
body = cwist_http_sstring_create(arena);
2272 res->
stream_buf = cwist_http_sstring_create(arena);
2308 return cwist_http_response_create_impl((
cwist_arena_t *)arena,
true);
2319 cwist_http_response_release_ptr_body(res);
2320 cwist_http_response_release_file_stream(res);
2361 cwist_http_response_release_file_stream(res);
2362 cwist_http_response_release_ptr_body(res);
2413 size_t body_len = 0;
2418 }
else if (res->
body) {
2428 static const char status_prefix[] =
"HTTP/1.1 200 OK\r\nContent-Length: ";
2445 static const char conn_ka[] =
"\r\nConnection: keep-alive\r\n\r\n";
2447 offset +=
sizeof(
conn_ka) - 1;
2449 static const char conn_cl[] =
"\r\nConnection: close\r\n\r\n";
2451 offset +=
sizeof(
conn_cl) - 1;
2471 const char *version =
2486 buf[offset++] =
' ';
2489 buf[offset++] =
' ';
2492 buf[offset++] =
'\r';
2493 buf[offset++] =
'\n';
2503 if (
curr->key->data &&
curr->value->data) {
2508 buf[offset +
klen] =
':';
2509 buf[offset +
klen + 1] =
' ';
2511 buf[offset +
klen + 2 +
vlen] =
'\r';
2512 buf[offset +
klen + 2 +
vlen + 1] =
'\n';
2515 char k0 =
curr->key->data[0];
2516 if (
klen == 4 && (
k0 ==
'D' ||
k0 ==
'd')) {
2518 }
else if (
klen == 14 && (
k0 ==
'C' ||
k0 ==
'c')) {
2520 }
else if (
klen == 10 && (
k0 ==
'C' ||
k0 ==
'c')) {
2522 }
else if (
klen == 17 && (
k0 ==
'T' ||
k0 ==
't')) {
2531 memcpy(buf + offset,
"Date: ", 6);
2533 buf[offset + 35] =
'\r';
2534 buf[offset + 36] =
'\n';
2539 static const char te_chunked[] =
"Transfer-Encoding: chunked\r\n";
2557 memcpy(buf + offset,
"Content-Length: ", 16);
2559 buf[offset + 16 +
num_len] =
'\r';
2560 buf[offset + 16 +
num_len + 1] =
'\n';
2567 static const char conn_ka[] =
"Connection: keep-alive\r\n";
2570 offset +=
sizeof(
conn_ka) - 1;
2573 static const char conn_cl[] =
"Connection: close\r\n";
2576 offset +=
sizeof(
conn_cl) - 1;
2592 buf[offset++] =
'\r';
2593 buf[offset++] =
'\n';
2610 return serialize_headers(res, buf,
buf_size);
2613#ifndef CWIST_PLATFORM_NO_SOCKETS
2623#if defined(__linux__) && defined(TCP_CORK)
2624#define CWIST_TCP_CORK_DEFAULT_BURST (256 * 1024)
2632 static int enabled = -1;
2634 const char *
env =
getenv(
"CWIST_USE_TCP_CORK");
2638 enabled = (
end !=
env && *
end ==
'\0' &&
v > 0) ? 1 : 0;
2643 return enabled == 1;
2651 static size_t burst = 0;
2653 const char *
env =
getenv(
"CWIST_TCP_CORK_BURST");
2694static int cwist_http_sendmsg_all(
int fd,
struct iovec *
iov,
int iovcnt,
int flags) {
2695#ifdef CWIST_WASI_SOCKETS
2702 if (!buf)
return -1;
2706 off +=
iov[
i].iov_len;
2710 while (
sent < total) {
2730#elif !defined(CWIST_PLATFORM_NO_SOCKETS)
2766 if (
ret <= 0)
return -1;
2772 if (
n == 0)
return -1;
2774 while (
curcnt > 0 && (
size_t)
n >=
cur->iov_len) {
2780 cur->iov_base = (
char *)
cur->iov_base +
n;
2806#if defined(__linux__)
2812#if defined(__linux__)
2828 if (
sent == 0)
break;
2881 if (client_fd < 0 || !res) {
2882 err.error.err_i16 = -1;
2892 size_t body_len = 0;
2907 iov[0].iov_len = header_len;
2911 iov[1].iov_len = body_len;
2916#if defined(MSG_NOSIGNAL)
2919#if defined(MSG_DONTWAIT)
2923#if defined(__linux__) && defined(TCP_CORK)
2927#if defined(MSG_MORE)
2934 if (cwist_http_sendmsg_all(client_fd,
iov,
iov_cnt, flags) != 0) {
2935 err.error.err_i16 = -1;
2937 err.error.err_i16 = 0;
2939 if (!cwist_http_stream_file_fast(client_fd, res)) {
2940 err.error.err_i16 = -1;
2945#if defined(__linux__) && defined(TCP_CORK)
2951 cwist_http_response_release_ptr_body(res);
2952 cwist_http_response_release_file_stream(res);
2963 if (client_fd < 0 || !res) {
2964 err.error.err_i16 = -1;
2973#if defined(MSG_NOSIGNAL)
2976#if defined(MSG_DONTWAIT)
2980 err.error.err_i16 = (cwist_http_sendmsg_all(client_fd, &
iov, 1, flags) == 0) ? 0 : -1;
2982 cwist_http_response_release_ptr_body(res);
2983 cwist_http_response_release_file_stream(res);
3006 void *body_cleanup_ctx;
3007} http_parked_write_state_t;
3021 http_parked_write_state_t *state;
3025} http_parked_write_t;
3028 "parked write state must fit a reactor slot payload");
3033static uint32_t http_parked_write_deadline(
void) {
3037static void http_parked_write_cb(
int fd,
void *ctx);
3046static bool http_parked_rearm(
int fd, http_parked_write_t *
w) {
3057static void http_parked_write_finish(
int fd, http_parked_write_t *
w,
bool drained) {
3063#if defined(__linux__)
3064 if (
w->state->close_file_fd &&
w->state->file_fd >= 0)
close(
w->state->file_fd);
3066 if (
w->state->body_cleanup &&
w->state->body) {
3067 size_t total =
w->state->body_off +
w->state->body_left;
3068 w->state->body_cleanup((
void *)
w->state->body, total,
w->state->body_cleanup_ctx);
3075 http_async_close(fd, conn);
3087static void http_parked_write_cb(
int fd,
void *ctx) {
3088 http_parked_write_t *
w = (http_parked_write_t *)ctx;
3090#if defined(MSG_NOSIGNAL)
3093#if defined(MSG_DONTWAIT)
3098 while (
w->off <
w->len) {
3099 ssize_t n = send(fd,
w->buf +
w->off,
w->len -
w->off, flags);
3104 w->deadline_sec = http_parked_write_deadline();
3112 http_parked_write_finish(fd,
w,
false);
3119#if defined(__linux__)
3120 http_parked_write_state_t *
f =
w->state;
3121 while (
f &&
f->file_left > 0) {
3125 w->deadline_sec = http_parked_write_deadline();
3134 http_parked_write_finish(fd,
w,
f &&
f->file_left == 0);
3137 http_parked_write_finish(fd,
w,
false);
3143 http_parked_write_state_t *s =
w->state;
3144 while (s && s->body_left > 0) {
3145 ssize_t n = send(fd, s->body + s->body_off, s->body_left, flags);
3149 w->deadline_sec = http_parked_write_deadline();
3158 http_parked_write_finish(fd,
w, s && s->body_left == 0);
3162 http_parked_write_finish(fd,
w,
true);
3180static bool http_park_streamed_body(
int client_fd,
cwist_reactor_t *reactor,
3183 const void *
body_ptr,
size_t body_len,
bool keep_alive,
3186 if (!
st)
return false;
3189#if defined(__linux__)
3200 st->close_file_fd =
true;
3209 st->body_off = body_sent;
3210 st->body_left = body_len - body_sent;
3222 http_parked_write_t
w = {
3229 .deadline_sec = http_parked_write_deadline(),
3231 .keep_alive = keep_alive,
3236 if (!
w.buf)
goto fail;
3244#if defined(__linux__)
3245 if (
st->close_file_fd &&
st->file_fd >= 0)
close(
st->file_fd);
3250 st->body_cleanup((
void *)
st->body,
st->body_off +
st->body_left,
st->body_cleanup_ctx);
3282 bool keep_alive,
bool defer,
3284#if !defined(__linux__)
3302#if defined(MSG_NOSIGNAL)
3305#if defined(MSG_DONTWAIT)
3314 if (http_park_streamed_body(client_fd, reactor, conn, res,
header_buf, header_len,
sent,
3315 NULL, 0, keep_alive,
true)) {
3316 cwist_http_response_release_file_stream(res);
3327 http_async_close(client_fd, conn);
3339 http_async_close(client_fd, conn);
3344 st->close_file_fd =
true;
3346 cwist_http_response_release_file_stream(res);
3349 if (
st->close_file_fd)
close(
st->file_fd);
3351 http_async_close(client_fd, conn);
3356 while (
st->file_left > 0) {
3364 http_parked_write_t
w = {
3371 .deadline_sec = http_parked_write_deadline(),
3373 .keep_alive = keep_alive,
3385 if (
st->close_file_fd)
close(
st->file_fd);
3397 http_async_close(client_fd, conn);
3416 if (client_fd < 0 || !res || !reactor || !conn) {
3417 http_async_close(client_fd, conn);
3426#if defined(__linux__)
3427 if (cwist_http_file_begin(client_fd, res, reactor, conn, keep_alive,
false,
NULL) ==
3433 if (keep_alive &&
err.error.err_i16 == 0) {
3436 http_async_close(client_fd, conn);
3445 size_t body_len = 0;
3459 iov[0].iov_len = header_len;
3462 iov[1].iov_len = body_len;
3467#if defined(MSG_NOSIGNAL)
3470#if defined(MSG_DONTWAIT)
3481 http_park_streamed_body(client_fd, reactor, conn, res,
header_buf, header_len,
sent,
3482 body_ptr, body_len, keep_alive,
false)) {
3483 cwist_http_response_release_ptr_body(res);
3484 cwist_http_response_release_file_stream(res);
3489 size_t total = header_len + body_len;
3491 http_parked_write_t
w = {
3497 .deadline_sec = http_parked_write_deadline(),
3498 .keep_alive = keep_alive,
3505 size_t body_off =
sent > header_len ?
sent - header_len : 0;
3509 cwist_http_response_release_ptr_body(res);
3510 cwist_http_response_release_file_stream(res);
3516 http_async_close(client_fd, conn);
3520 cwist_http_response_release_ptr_body(res);
3521 cwist_http_response_release_file_stream(res);
3525 http_async_close(client_fd, conn);
3540#if defined(__linux__)
3542 if (cwist_http_file_begin(client_fd, res, conn->
reactor, conn, keep_alive,
true,
3556 size_t body_len = 0;
3570 iov[0].iov_len = header_len;
3573 iov[1].iov_len = body_len;
3578#if defined(MSG_NOSIGNAL)
3581#if defined(MSG_DONTWAIT)
3592 http_park_streamed_body(client_fd, conn->
reactor, conn, res,
header_buf, header_len,
3594 cwist_http_response_release_ptr_body(res);
3595 cwist_http_response_release_file_stream(res);
3598 size_t total = header_len + body_len;
3600 http_parked_write_t
w = {
3606 .deadline_sec = http_parked_write_deadline(),
3607 .keep_alive = keep_alive,
3614 size_t body_off =
sent > header_len ?
sent - header_len : 0;
3619 cwist_http_response_release_ptr_body(res);
3620 cwist_http_response_release_file_stream(res);
3625 cwist_http_response_release_ptr_body(res);
3626 cwist_http_response_release_file_stream(res);
3630 cwist_http_response_release_ptr_body(res);
3631 cwist_http_response_release_file_stream(res);
3654 if (len == 0)
return 0;
3656 if (conn->
olen + len > conn->
ocap) {
3687#if defined(MSG_NOSIGNAL)
3690#if defined(MSG_DONTWAIT)
3697 int rc = cwist_http_sendmsg_all(client_fd, &
iov, 1, flags);
3710 http_parked_write_t
w = {
3716 .deadline_sec = http_parked_write_deadline(),
3717 .keep_alive = keep_alive,
3738 if (!conn || conn->
olen == 0)
return 0;
3741#if defined(MSG_NOSIGNAL)
3744#if defined(MSG_DONTWAIT)
3747 int rc = cwist_http_sendmsg_all(client_fd, &
iov, 1, flags);
3763 "HTTP/1.1 %d %s\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n%s",
3765 if (
n <= 0)
return -1;
3766 size_t len = (
size_t)
n <
sizeof(buf) ? (
size_t)
n :
sizeof(buf) - 1;
3790#if defined(__linux__)
3792#if defined(MSG_NOSIGNAL)
3795#if defined(MSG_DONTWAIT)
3800 if (conn->
olen > 0) {
3807 if (http_park_streamed_body(client_fd, conn->
reactor, conn, res, conn->
obuf, conn->
olen,
3808 sent,
NULL, 0, keep_alive,
true)) {
3810 cwist_http_response_release_file_stream(res);
3819 if (cwist_http_file_begin(client_fd, res, conn->
reactor, conn, keep_alive,
true,
3831 size_t body_len = 0;
3848 cwist_http_response_release_ptr_body(res);
3849 cwist_http_response_release_file_stream(res);
3860 size_t total = header_len + body_len;
3864 iov[0].iov_len = header_len;
3867 iov[1].iov_len = body_len;
3872#if defined(MSG_NOSIGNAL)
3875#if defined(MSG_DONTWAIT)
3886 http_park_streamed_body(client_fd, conn->
reactor, conn, res,
header_buf, header_len,
3888 cwist_http_response_release_ptr_body(res);
3889 cwist_http_response_release_file_stream(res);
3893 http_parked_write_t
w = {
3899 .deadline_sec = http_parked_write_deadline(),
3900 .keep_alive = keep_alive,
3907 size_t body_off =
sent > header_len ?
sent - header_len : 0;
3912 cwist_http_response_release_ptr_body(res);
3913 cwist_http_response_release_file_stream(res);
3918 cwist_http_response_release_ptr_body(res);
3919 cwist_http_response_release_file_stream(res);
3923 cwist_http_response_release_ptr_body(res);
3924 cwist_http_response_release_file_stream(res);
3941 cwist_http_response_release_ptr_body(res);
3942 cwist_http_response_release_file_stream(res);
3953 conn->
olen += header_len;
3956 conn->
olen += body_len;
3959 cwist_http_response_release_ptr_body(res);
3960 cwist_http_response_release_file_stream(res);
3972 case 100:
return "Continue";
3973 case 101:
return "Switching Protocols";
3974 case 102:
return "Processing";
3975 case 103:
return "Early Hints";
3976 case 200:
return "OK";
3977 case 201:
return "Created";
3978 case 202:
return "Accepted";
3979 case 203:
return "Non-Authoritative Information";
3980 case 204:
return "No Content";
3981 case 205:
return "Reset Content";
3982 case 206:
return "Partial Content";
3983 case 207:
return "Multi-Status";
3984 case 208:
return "Already Reported";
3985 case 226:
return "IM Used";
3986 case 300:
return "Multiple Choices";
3987 case 301:
return "Moved Permanently";
3988 case 302:
return "Found";
3989 case 303:
return "See Other";
3990 case 304:
return "Not Modified";
3991 case 305:
return "Use Proxy";
3992 case 307:
return "Temporary Redirect";
3993 case 308:
return "Permanent Redirect";
3994 case 400:
return "Bad Request";
3995 case 401:
return "Unauthorized";
3996 case 402:
return "Payment Required";
3997 case 403:
return "Forbidden";
3998 case 404:
return "Not Found";
3999 case 405:
return "Method Not Allowed";
4000 case 406:
return "Not Acceptable";
4001 case 407:
return "Proxy Authentication Required";
4002 case 408:
return "Request Timeout";
4003 case 409:
return "Conflict";
4004 case 410:
return "Gone";
4005 case 411:
return "Length Required";
4006 case 412:
return "Precondition Failed";
4007 case 413:
return "Content Too Large";
4008 case 414:
return "URI Too Long";
4009 case 415:
return "Unsupported Media Type";
4010 case 416:
return "Range Not Satisfiable";
4011 case 417:
return "Expectation Failed";
4012 case 418:
return "I'm a Teapot";
4013 case 421:
return "Misdirected Request";
4014 case 422:
return "Unprocessable Content";
4015 case 423:
return "Locked";
4016 case 424:
return "Failed Dependency";
4017 case 425:
return "Too Early";
4018 case 426:
return "Upgrade Required";
4019 case 428:
return "Precondition Required";
4020 case 429:
return "Too Many Requests";
4021 case 431:
return "Request Header Fields Too Large";
4022 case 451:
return "Unavailable For Legal Reasons";
4023 case 500:
return "Internal Server Error";
4024 case 501:
return "Not Implemented";
4025 case 502:
return "Bad Gateway";
4026 case 503:
return "Service Unavailable";
4027 case 504:
return "Gateway Timeout";
4028 case 505:
return "HTTP Version Not Supported";
4029 case 506:
return "Variant Also Negotiates";
4030 case 507:
return "Insufficient Storage";
4031 case 508:
return "Loop Detected";
4032 case 510:
return "Not Extended";
4033 case 511:
return "Network Authentication Required";
4034 default:
return NULL;
4038#ifndef CWIST_PLATFORM_NO_SOCKETS
4060 "HTTP/1.1 %d %s\r\nContent-Type: text/plain\r\nContent-Length: %zu\r\nConnection: close\r\n\r\n",
4062 if (
n <= 0 || (
size_t)
n >=
sizeof(buf))
return;
4065 {.iov_base = buf, .iov_len = (
size_t)
n},
4069#if defined(MSG_NOSIGNAL)
4072#if defined(MSG_DONTWAIT)
4082static void http_send_100_continue(
int fd) {
4083 static const char k_continue[] =
"HTTP/1.1 100 Continue\r\n\r\n";
4086#if defined(MSG_NOSIGNAL)
4089 (
void)cwist_http_sendmsg_all(fd, &
iov, 1, flags);
4101 if (!res)
return NULL;
4103 if (!s)
return NULL;
4110 }
else if (res->
body) {
4133 if (!
n->key || !
n->key->data ||
strcasecmp(
n->key->data,
"Transfer-Encoding") != 0)
4135 const char *
p = (
n->value &&
n->value->data) ?
n->value->data :
"";
4139 while (*
p ==
' ' || *
p ==
'\t' || *
p ==
',')
p++;
4140 const char *
tok =
p;
4141 while (*
p && *
p !=
',')
p++;
4142 const char *
end =
p;
4145 if (
end ==
tok)
continue;
4156 if (!
seen)
return 0;
4205 const char *
sp2 = cwist_simd_find_char(
sp1 + 1, (
size_t)(
line_end - (
sp1 + 1)),
' ');
4282 if ((
w0 | 0x2020202020202020ULL) == 0x697463656e6e6f63ULL) {
4285 if ((
w1 | 0x2020) == 0x6e6f) {
4294 }
else if (
key_len == 14 && (
k0 ==
'C' ||
k0 ==
'c')) {
4298 size_t vlen = val_len;
4306 for (
size_t i = 0;
i <
vlen;
i++) {
4328 }
else if (
key_len == 4 && (
k0 ==
'H' ||
k0 ==
'h')) {
4332 size_t vlen = val_len;
4337 }
else if (
key_len == 17 && (
k0 ==
'T' ||
k0 ==
't')) {
4339 }
else if (
key_len == 6 && (
k0 ==
'E' ||
k0 ==
'e')) {
4344 size_t vlen = val_len;
4376 int tev = http_validate_transfer_encoding(req->
headers);
4396 if (available > 0) {
4425 if (!buf || len == 0)
return NULL;
4426 const char *
header_end = cwist_simd_find_crlfcrlf(buf, len);
4428 return cwist_http_parse_request_with_header_end(buf, len,
header_end,
NULL);
4440 if (!res || !out || !out_len)
return -1;
4446 const void *body =
NULL;
4447 size_t body_len = 0;
4462 char *buf = (
char *)
cwist_alloc(header_len + body_len + 1);
4463 if (!buf)
return -1;
4465 if (body_len)
memcpy(buf + header_len, body, body_len);
4466 buf[header_len + body_len] =
'\0';
4468 *out_len = header_len + body_len;
4482 if (head_len == 0 || head_len >=
sizeof(
head_buf))
return -1;
4521 if (!data)
return -1;
4522 if (len == 0)
return 0;
4536 if (!cwist_error_is_ok(&
append_err))
return -1;
4537 if (cwist_response_stream_flush(res) != 0) {
4556 if (!cwist_error_is_ok(&
end_err))
return -1;
4557 if (cwist_response_stream_flush(res) != 0) {
4565#ifndef CWIST_PLATFORM_NO_SOCKETS
4570static int http_parse_chunk_size(
const char *
line,
size_t len,
size_t *
out_size) {
4571 size_t value = 0,
digits = 0;
4572 for (
size_t i = 0;
i < len &&
line[
i] !=
';'; ++
i) {
4575 if (
c >=
'0' &&
c <=
'9')
4577 else if (
c >=
'a' &&
c <=
'f')
4579 else if (
c >=
'A' &&
c <=
'F')
4584 value = value * 16 +
digit;
4599static bool http_chunk_append(
cwist_sstring *out,
const char *data,
size_t len) {
4601 bool ok = cwist_error_is_ok(&
err);
4613static int http_read_chunked_body(
int client_fd,
char *buf,
size_t *
avail,
size_t buf_cap,
4626 if (
ret <= 0)
return -1;
4632 if (bytes == 0)
return -1;
4639 size_t chunk_size = 0;
4642 if (http_parse_chunk_size(buf + offset,
line_len - 2, &chunk_size) != 0)
return -1;
4645 if (chunk_size == 0) {
4647 while (offset + 1 < *
avail && !(buf[offset] ==
'\r' && buf[offset + 1] ==
'\n')) {
4656 if (
ret <= 0)
return -1;
4662 if (bytes == 0)
return -1;
4671 if (offset + 1 < *
avail && buf[offset] ==
'\r' && buf[offset + 1] ==
'\n') {
4681 while (*
avail - offset < chunk_size + 2) {
4688 if (
ret <= 0)
return -1;
4694 if (bytes == 0)
return -1;
4699 if (buf[offset + chunk_size] !=
'\r' || buf[offset + chunk_size + 1] !=
'\n')
return -1;
4700 if (!http_chunk_append(out, buf + offset, chunk_size))
return -1;
4701 offset += chunk_size + 2;
4740 CWIST_LOG_WARN(
"[http] dropping connection: headers exceed %zu-byte read buffer",
4752 CWIST_LOG_WARN(
"[http] dropping connection: header read exceeded %d ms",
4765 if (
pr < 0)
continue;
4785 cwist_http_parse_request_with_header_end(read_buf, header_len,
header_end,
err_out);
4786 if (!req)
return NULL;
4797 http_send_100_continue(client_fd);
4869 read_buf[*buf_len] =
'\0';
4876 if (http_read_chunked_body(client_fd, read_buf, buf_len,
buf_size,
chunked) != 0) {
4900 read_buf[*buf_len] =
'\0';
4908#define CWIST_ASYNC_STASH_MAX (CWIST_HTTP_READ_BUFFER_SIZE + CWIST_HTTP_MAX_BODY_SIZE)
4913#define CWIST_HTTP_BODY_ZC_MIN (16 * 1024)
4923 if (conn->
cap >=
need)
return true;
4938 if (
cap == conn->
cap)
return false;
4940 if (!
nb)
return false;
4966 if (conn->
len + 1 >= conn->
cap && !http_async_stash_grow(conn, conn->
len + 4096)) {
4975 if ((
size_t)
n <
avail)
return 0;
4994static int http_chunked_scan(
const char *buf,
size_t avail,
size_t *
consumed,
5001 size_t chunk_size = 0;
5002 if (http_parse_chunk_size(buf + pos,
line_len, &chunk_size) != 0)
return -1;
5005 if (chunk_size == 0) {
5008 if (
avail - pos < 2)
return 0;
5009 if (buf[pos] ==
'\r' && buf[pos + 1] ==
'\n') {
5014 if (!
tcrlf)
return 0;
5020 if (
avail - pos < chunk_size + 2)
return 0;
5021 if (buf[pos + chunk_size] !=
'\r' || buf[pos + chunk_size + 1] !=
'\n')
return -1;
5024 if (!http_chunk_append(
assemble, (
const char *)buf + pos, chunk_size))
return -1;
5026 pos += chunk_size + 2;
5046 CWIST_LOG_WARN(
"[http] dropping async connection: headers exceed %d-byte read buffer",
5073 http_send_100_continue(conn->
fd);
5085 if (!http_async_stash_grow(conn, header_len + (
size_t)req->
content_length + 1)) {
5143 if (!http_async_stash_grow(conn, conn->
len + 4096)) {
5182static const cwist_mime_entry CWIST_MIME_TABLE[] = {{
".html",
"text/html; charset=utf-8"},
5183 {
".htm",
"text/html; charset=utf-8"},
5184 {
".css",
"text/css; charset=utf-8"},
5185 {
".js",
"application/javascript"},
5186 {
".mjs",
"application/javascript"},
5187 {
".json",
"application/json"},
5188 {
".wasm",
"application/wasm"},
5189 {
".png",
"image/png"},
5190 {
".jpg",
"image/jpeg"},
5191 {
".jpeg",
"image/jpeg"},
5192 {
".gif",
"image/gif"},
5193 {
".svg",
"image/svg+xml"},
5194 {
".txt",
"text/plain; charset=utf-8"},
5195 {
".ico",
"image/x-icon"}};
5201static const char *cwist_guess_mime(
const char *
file_path) {
5202 if (!
file_path)
return "application/octet-stream";
5205 return "application/octet-stream";
5207 for (
size_t i = 0;
i <
sizeof(CWIST_MIME_TABLE) /
sizeof(CWIST_MIME_TABLE[0]);
i++) {
5209 return CWIST_MIME_TABLE[
i].mime;
5212 return "application/octet-stream";
5231 cwist_http_response_release_file_stream(res);
5232 cwist_http_response_release_ptr_body(res);
5265#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
5275 char *buffer =
NULL;
5298 err.error.err_i16 = -
EIO;
5328 err.error.err_i16 = 0;
5335 "HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: keep-alive\r\n\r\n";
5337 "HTTP/1.1 404 Not Found\r\nContent-Length: 13\r\nConnection: keep-alive\r\n\r\n404 Not Found";
5339 "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 21\r\nConnection: close\r\n\r\nInternal Server Error";
5345#ifndef CWIST_PLATFORM_NO_SOCKETS
5348 const char *
env =
getenv(
"CWIST_REUSEPORT");
5349 if (!
env)
return true;
5392#if defined(SO_REUSEPORT)
5398#if defined(__APPLE__) || defined(__FreeBSD__)
5422 char err_format[128] =
"Failed to listen at %s:%d";
5442static bool cwist_accept_error_should_retry(
int err) {
5457 case ENOMEM:
return true;
5458 default:
return false;
5466static void cwist_accept_error_backoff(
int err) {
5475 ts.tv_nsec = 50 * 1000 * 1000;
5489#if !defined(CWIST_PLATFORM_NO_PROC)
5490static void handle_client_forking(
int client_fd,
void (*handler_func)(
int,
void *),
void *ctx) {
5493 handler_func(client_fd, ctx);
5496 }
else if (
pid > 0) {
5502#if defined(__SANITIZE_THREAD__)
5503#define HTTP_ACCEPT_TSAN 1
5504#elif defined(__has_feature)
5505#if __has_feature(thread_sanitizer)
5506#define HTTP_ACCEPT_TSAN 1
5510#ifdef HTTP_ACCEPT_TSAN
5511#define HTTP_ACCEPT_WAIT_MS 100
5513#define HTTP_ACCEPT_WAIT_MS (-1)
5527static int http_accept_blocking(
int server_fd) {
5528#ifdef HTTP_ACCEPT_TSAN
5532 if (r > 0 || (r < 0 &&
errno !=
EINTR))
break;
5551 void (*handler_func)(
int client_fd,
void *),
void *ctx) {
5574 handler_func(client_fd, ctx);
5578 err.error.err_i16 = -1;
5591 void (*handler)(
int,
void *),
void *ctx) {
5593 if (!config ||
server_fd < 0 || !handler) {
5594 err.error.err_i16 = -1;
5598#if !defined(CWIST_PLATFORM_NO_PROC)
5601 int client_fd = http_accept_blocking(
server_fd);
5602 if (client_fd < 0) {
5605 if (cwist_accept_error_should_retry(
accept_err)) {
5609 err.error.err_i16 = -1;
5614 handle_client_forking(client_fd, handler, ctx);
5624 err.error.err_i16 = -1;
5628 int client_fd = http_accept_blocking(
server_fd);
5629 if (client_fd < 0) {
5633 if (cwist_accept_error_should_retry(
accept_err)) {
5644 err.error.err_i16 = 0;
5655 err.error.err_i16 = -1;
5663 err.error.err_i16 = -1;
5681 if (client_fd >= 0) {
5685 handler(client_fd, ctx);
5691 if (cwist_accept_error_should_retry(
accept_err)) {
5695 err.error.err_i16 = -1;
5708#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
5715 err.error.err_i16 = -1;
5722 err.error.err_i16 = -1;
5738 if (client_fd >= 0) {
5742 handler(client_fd, ctx);
5748 if (cwist_accept_error_should_retry(
accept_err)) {
5752 err.error.err_i16 = -1;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
Request-scoped arena allocator backed by libttak arena generations.
struct cwist_arena cwist_arena_t
void * cwist_arena_alloc(cwist_arena_t *arena, size_t size)
Bump-allocate size bytes from the active generation.
bool cwist_arena_owns(const cwist_arena_t *arena, const void *ptr)
Check whether ptr lies inside the active generation buffer.
void cwist_arena_destroy(cwist_arena_t *arena)
Retire the generation and destroy the arena, releasing every allocation made from it at once.
cwist_arena_t * cwist_arena_create(size_t generation_bytes)
Create an arena backed by a recycled thread-local generation buffer.
Private wiring between the deferred-response path (async.c) and the C1M connection engine (http....
void cwist_async_forget_response(const cwist_http_response *res)
Safe string type with helper methods.
cwist_error_t cwist_sstring_append_len(cwist_sstring *str, const char *data, size_t len)
Append raw bytes with length.
cwist_error_t cwist_sstring_adopt_region(cwist_sstring *str, char *base, size_t offset, size_t len)
Adopt a heap buffer as a region view without copying.
size_t cwist_sstring_get_size(cwist_sstring *str)
Get the current size.
cwist_sstring * cwist_sstring_create(void)
Create a new sstring instance.
cwist_error_t cwist_sstring_assign_len(cwist_sstring *str, const char *data, size_t len)
Assign raw bytes with length.
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
cwist_error_t cwist_sstring_append_sstring(cwist_sstring *str, const cwist_sstring *from)
Append another sstring.
void cwist_sstring_destroy(cwist_sstring *str)
Destroy an sstring instance.
cwist_error_t cwist_sstring_borrow(cwist_sstring *str, const char *data, size_t len)
Borrow an external buffer without copying.
cwist_error_t cwist_sstring_copy_sstring(cwist_sstring *origin, const cwist_sstring *from)
Copy from another sstring.
int cwist_sstring_compare_sstring(cwist_sstring *left, const cwist_sstring *right)
Compare two sstrings.
cwist_error_t cwist_sstring_adopt_len(cwist_sstring *str, char *buf, size_t len)
Adopt a heap buffer, taking ownership without copying.
#define CWIST_ENDPOINT_FILE
#define CWIST_ENDPOINT_DEFAULT
Default option for new endpoints.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
@ CWIST_ERR_INT8
mostly used to check a char
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
struct http_pool_task http_pool_task_t
const char CWIST_BLOB_200_OK[]
bool cwist_http_pool_rearm_current(int client_fd, void(*handler)(int, void *), void *ctx)
Requeue the current connection's next handler turn on the pool.
const char CWIST_BLOB_404[]
#define CWIST_RX_REARM_MIN_AVAIL
cwist_file_begin_result_t
@ CWIST_FILE_BEGIN_HANDLED
@ CWIST_FILE_BEGIN_FALLBACK
cwist_http_request * cwist_http_receive_request(int client_fd, char *read_buf, size_t buf_size, size_t *buf_len, cwist_http_parse_error_t *err_out)
Blocking read of one full request from a classic-pool connection.
int headers_have_content_length(cwist_http_header_node *headers)
Detect whether a header list already defines Content-Length.
cwist_async_send_status_t cwist_http_send_response_coalesced(int client_fd, cwist_http_response *res, cwist_http_async_conn_t *conn, bool keep_alive, bool head_only)
Coalescing variant of the async send: serialize small responses directly into the connection's output...
#define CWIST_ASYNC_STASH_IDLE
int cwist_http_async_conn_fill(cwist_http_async_conn_t *conn)
Drain the socket into the connection stash. Stops at EAGAIN, and also after a short read: poll is lev...
bool cwist_http_async_rearm(int client_fd, cwist_reactor_t *reactor, cwist_http_async_conn_t *conn)
Re-arm an async connection for its next event.
cwist_async_send_status_t cwist_http_send_response_async(int client_fd, cwist_http_response *res, cwist_http_async_conn_t *conn, bool keep_alive, bool head_only)
Async send variant that reports the outcome to the caller instead of re-arming or closing the connect...
long get_cpu_cores(void)
Return the number of CPUs available to this process.
long cwist_http_inflight_count(void)
void cwist_http_live_idle_leave(void)
void cwist_http_send_error_response(int fd, int status, const char *msg)
Send a minimal error response with Connection: close, used to answer malformed requests (400/413/417/...
time_t cwist_http_parse_date(const char *str)
Parse an HTTP-date string into a time_t.
void cwist_http_async_send_response(int client_fd, cwist_http_response *res, cwist_reactor_t *reactor, cwist_http_async_conn_t *conn, bool keep_alive, bool head_only)
Send a response on the async path, parking the unsent remainder on EAGAIN instead of blocking the rea...
uint64_t cwist_http_reactor_generation(void)
Return the current reactor generation counter.
cwist_http_method_t cwist_http_string_to_method_len(const char *str, size_t len)
Parse a method token (with explicit length) into a method enum.
const int CWIST_HTTP_LISTEN_FAILED
size_t cwist_http_serialize_headers(cwist_http_response *res, char *buf, size_t buf_size)
Serialize the status line and headers into a caller-provided buffer.
cwist_error_t cwist_http_server_loop(int server_fd, cwist_server_config *config, void(*handler)(int, void *), void *ctx)
Run the main HTTP accept loop using the configured concurrency strategy.
bool cwist_reuseport_enabled(void)
cwist_error_t cwist_accept_socket(int server_fd, struct sockaddr *sockv4, void(*handler_func)(int client_fd, void *), void *ctx)
Accept one client connection and dispatch it according to the current server strategy.
int cwist_http_response_stream_end(cwist_http_response *res)
Terminate a streaming response with the final zero-length chunk and flush.
void cwist_http_response_set_body_ptr(cwist_http_response *res, const void *ptr, size_t len)
Attach an unmanaged zero-copy body pointer to a response.
#define CWIST_HTTP_INFLIGHT_PER_THREAD
const int CWIST_CREATE_SOCKET_FAILED
int cwist_http_pool_init(void)
Initialize the HTTP connection pool using CWIST_C1M_MODE.
size_t cwist_http_header_remove(cwist_http_header_node **head, const char *key)
Unlink and release every header node matching a name.
cwist_sstring * cwist_get_client_ip_from_fd(int fd)
Resolve the peer IP address for a connected client socket.
bool cwist_tcp_cork_enabled(void)
TCP_CORK coalescing stub for platforms without TCP_CORK.
void cwist_http_pool_limit_core(unsigned int limit)
Fix the HTTP pool worker count to a specific value.
const char CWIST_BLOB_500[]
cwist_error_t cwist_http_send_response(int client_fd, cwist_http_response *res)
Serialize and send an HTTP response to a connected client socket.
void cwist_http_format_date(time_t t, char *buf, size_t len)
Resolve the peer IP address for a connected client socket.
cwist_error_t cwist_http_response_send_file(cwist_http_response *res, const char *file_path, const char *content_type_hint, size_t *out_size)
Prepare a response to serve a file either by buffering or direct streaming.
int cwist_make_socket_ipv4(struct sockaddr_in *sockv4, const char *address, uint16_t port, uint16_t backlog)
Create, configure, bind, and listen on an IPv4 TCP socket.
#define CWIST_HTTP_BODY_ZC_MIN
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value using case-insensitive header-name comparison.
long get_optimal_thread_count(void)
Compute the HTTP worker/event-loop thread count.
#define CWIST_HTTP_INFLIGHT_FD_RESERVE
int cwist_http_coalesce_flush_blocking(int client_fd, cwist_http_async_conn_t *conn)
Flush the coalesced-output stash, blocking in poll() until drained.
cwist_error_t cwist_http_send_response_head(int client_fd, cwist_http_response *res)
Send only the status line and headers of a response (RFC 9110 section 9.3.2 HEAD semantics): Content-...
cwist_recv_status_t cwist_http_receive_request_nb(cwist_http_async_conn_t *conn, cwist_http_request **out, cwist_http_parse_error_t *err_out)
Try to assemble one complete request from the stash without any blocking IO. On CWIST_RECV_OK the con...
void cwist_http_response_add_hsts(cwist_http_response *res)
Add Strict-Transport-Security to a TLS response (HTTPS only).
int cwist_http_coalesce_error_response(cwist_http_async_conn_t *conn, int status)
Build a minimal text error response into the coalesce stash.
void cwist_http_response_set_body_ptr_managed(cwist_http_response *res, const void *ptr, size_t len, cwist_http_body_cleanup_fn cleanup, void *ctx)
Attach a managed zero-copy body pointer and optional cleanup hook to a response.
cwist_coalesce_flush_status_t cwist_http_coalesce_flush(int client_fd, cwist_http_async_conn_t *conn, bool keep_alive)
Flush the coalesced-output stash with one speculative write.
void cwist_http_request_destroy(cwist_http_request *req)
Destroy a parsed HTTP request and all nested allocations it owns.
cwist_http_request * cwist_http_parse_request(const char *raw_request)
Parse a NUL-terminated raw HTTP request buffer.
void cwist_http_response_add_security_headers(cwist_http_response *res)
Add default security headers to an HTTP response if not already present.
cwist_http_request * cwist_http_parse_request_len(const char *buf, size_t len)
Parse a raw HTTP request buffer with an explicit length.
void cwist_http_pool_submit(int client_fd, void(*handler)(int, void *), void *ctx)
Queue a connection for handling on the classic dynamic pool.
bool cwist_http_live_idle_enter(void)
cwist_sstring * cwist_http_stringify_response(cwist_http_response *res)
Materialize an HTTP response into a contiguous string for debugging or TLS writes.
#define CWIST_ASYNC_STASH_MAX
void cwist_http_async_close_orphan(int client_fd, cwist_http_async_conn_t *conn)
Close an async connection whose owning worker pool is gone.
long cwist_http_continuation_shed_count(void)
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Prepend one header node to the linked-list header collection.
void cwist_http_response_destroy(cwist_http_response *res)
Destroy an HTTP response and release any attached body resources.
struct http_live_conn http_live_conn_t
const int CWIST_HTTP_BIND_FAILED
void cwist_http_header_free_all(cwist_http_header_node *head)
Destroy every node in a request or response header list.
bool cwist_http_pool_submit_async(int client_fd, cwist_async_handler_t handler, void *ctx)
Submit a connection to the C1M async reactor pool.
cwist_http_response * cwist_http_response_create(void)
Allocate a response backed by its own arena.
int cwist_http_response_stream_write(cwist_http_response *res, const char *data, size_t len)
Append one data chunk to a streaming response.
#define HTTP_ACCEPT_WAIT_MS
void cwist_http_pool_destroy(void)
Tear down both pool variants.
cwist_http_response * cwist_http_response_create_in_arena(void *arena)
Allocate a response carved from a caller-owned arena.
const int CWIST_HTTP_UNAVAILABLE_ADDRESS
const char * cwist_http_method_to_string(cwist_http_method_t method)
Convert a HTTP method enum into its wire-format token.
int cwist_http_response_serialize(cwist_http_response *res, char **out, size_t *out_len)
Serialize a full response (headers + body) into a heap buffer.
#define CWIST_POOL_STACK_SIZE
cwist_http_method_t cwist_http_string_to_method(const char *method_str)
Parse a NUL-terminated method string into a method enum.
const int CWIST_HTTP_SETSOCKOPT_FAILED
#define CWIST_ASYNC_STASH_IDLE_SEC_DEFAULT
int cwist_http_response_stream_begin(cwist_http_response *res)
Switch a response to chunked streaming mode.
int cwist_http_pool_init_mode(bool use_c1m)
Initialize the HTTP connection pool in the requested mode.
bool cwist_http_reactor_post_live(cwist_reactor_t *reactor, uint64_t gen, cwist_reactor_post_t *node)
Post a reactor node only while the captured generation is current.
void cwist_http_response_set_alt_svc(cwist_http_response *res, const char *alt_svc)
Set the Alt-Svc header value for HTTP/3 upgrade advertisement.
const char * cwist_http_status_reason(int status)
Map an HTTP status code to its RFC reason phrase.
int cwist_http_coalesce_append(cwist_http_async_conn_t *conn, const void *data, size_t len)
Append bytes to a connection's coalesced-output stash.
cwist_http_request * cwist_http_request_create(void)
Allocate and initialize a default HTTP request object.
#define CWIST_HTTP_READ_BUFFER_SIZE
#define CWIST_HTTP_MAX_BODY_SIZE
#define CWIST_HTTP_MAX_HEADER_SIZE
cwist_http_parse_error_t
Failure reason reported by the request receive APIs.
@ CWIST_HTTP_PARSE_BODY_TOO_LARGE
@ CWIST_HTTP_PARSE_HEADER_OVERFLOW
@ CWIST_HTTP_PARSE_MALFORMED
@ CWIST_HTTP_PARSE_TE_UNSUPPORTED
@ CWIST_HTTP_PARSE_EXPECT_FAILED
#define CWIST_HTTP_TIMEOUT_MS
Classic-pool connection registry hooks for shutdown (private).
Lightweight macro-based internal logging for CWIST.
#define CWIST_LOG_WARN(fmt,...)
Production-grade metrics engine for CWIST.
cwist_metrics_registry_t * cwist_metrics_registry(void)
Obtain the global registry, creating it on first call.
@ CWIST_METRIC_HTTP_HEADER_OVERFLOW
@ CWIST_METRIC_HTTP_CONTINUATION_SHED
void cwist_metric_inc(cwist_metrics_registry_t *reg, cwist_metric_id_t id)
Increment a counter metric by 1.
HTTP Protocol Definitions and Helpers.
#define CWIST_HTTP_HEADERS_TIMEOUT_MS
cwist_async_action_t(* cwist_async_handler_t)(int fd, struct cwist_http_async_conn *conn)
#define CWIST_HTTP_KEEP_ALIVE_TIMEOUT_SEC
cwist_async_send_status_t
@ CWIST_ASYNC_SEND_DEFERRED
@ CWIST_ASYNC_SEND_KEEPALIVE
#define CWIST_HTTP_COALESCE_MAX
cwist_coalesce_flush_status_t
@ CWIST_COALESCE_FLUSH_DONE
@ CWIST_COALESCE_FLUSH_ERROR
@ CWIST_COALESCE_FLUSH_PARKED
void(* cwist_http_body_cleanup_fn)(const void *ptr, size_t len, void *ctx)
cwist_query_map * cwist_query_map_create_in_arena(void *arena)
Allocate a query map within a memory arena to avoid GC/heap allocations.
void cwist_query_map_destroy(cwist_query_map *map)
Destroy a query map.
void cwist_query_map_parse(cwist_query_map *map, const char *raw_query)
Parse raw query strings (e.g., "a=1&b=2") into the map.
#define CWIST_REACTOR_PAYLOAD_SIZE
struct cwist_reactor cwist_reactor_t
bool cwist_reactor_add(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
struct cwist_reactor_post cwist_reactor_post_t
void cwist_reactor_destroy(cwist_reactor_t *reactor)
void cwist_reactor_stop(cwist_reactor_t *reactor)
cwist_reactor_t * cwist_reactor_create(void)
void cwist_reactor_run(cwist_reactor_t *reactor)
bool cwist_reactor_add_out(cwist_reactor_t *reactor, int fd, cwist_reactor_cb_t cb, const void *payload, size_t payload_size)
Signed client-side session cookie API.
void cwist_session_destroy(cwist_session_t *session)
Destroy a session handle and its data.
Unified Graceful Shutdown support for CWIST.
int g_cwist_drain_timeout_sec
Drain timeout in seconds before process exits.
atomic_int g_cwist_running
Global running flag. Checked by all server event loops.
Listen-socket option knobs shared by the bind sites (private).
Integrated error structure used throughout the system.
bool expect_continue_sent
cwist_reactor_t * reactor
cwist_async_handler_t handler
cwist_query_map * query_params
Parsed query parameters.
bool te_chunked_seen
Parser saw and validated Transfer-Encoding: chunked (HTTP/1.1).
char * csrf_token
CSRF token populated by csrf middleware.
cwist_query_map * flash
Flash messages for this request (one-time read).
cwist_sstring * path
e.g., "/users/1"
void * async_conn
C1M connection shell when on the reactor path (internal).
cwist_sstring * query
e.g., "active=true" (raw)
bool expect_100_seen
Parser saw and validated Expect: 100-continue (HTTP/1.1).
void * h2_queue
HTTP/2 async queue when on H2 path (internal).
struct cwist_app * app
Owning app context (if any).
uint32_t stream_id
HTTP/2 or HTTP/3 stream ID (0 for HTTP/1.1).
void * arena
Per-request arena for bump-allocated structs (internal).
void * session
cwist_session_t pointer set by session middleware.
cwist_sstring * version
e.g., "HTTP/1.1"
void * private_data
Internal framework use (protocol-specific context).
cwist_query_map * path_params
Parsed path parameters (e.g. :id)
void * https_conn
HTTPS connection wrapper when on TLS path (internal).
cwist_http_header_node * headers
cwist_endpoint_opt_t endpoint_opts
Behavior hints for the active endpoint.
cwist_db * db
Shared database handle from cwist_app.
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_endpoint_opt_t endpoint_opts
Mirrors req->endpoint_opts.
cwist_sstring * version
e.g., "HTTP/1.1"
cwist_sstring * status_text
e.g., "OK"
void * ptr_body_cleanup_ctx
User data for release hook.
int file_stream_fd
Open descriptor for sendfile.
bool use_file_stream
Fast File Streaming.
const void * ptr_body
Pointer to external data (e.g., mmap region)
cwist_http_status_t status_code
bool file_stream_auto_close
Close fd after streaming.
off_t file_stream_offset
Current offset for sendfile loop.
char * alt_svc
Alt-Svc header for HTTP/3 upgrade advertisement.
cwist_http_header_node * headers
bool is_ptr_body
Zero-Copy Pointer Body.
cwist_http_body_cleanup_fn ptr_body_cleanup
Optional release hook.
size_t stream_flushed
stream_buf prefix already pushed to stream_sink.
bool stream_head_sent
Head serialized and pushed to the sink.
int(* stream_sink)(void *ctx, const char *data, size_t len)
Live sink (streaming dispatch only).
void * arena
Per-response arena for bump-allocated structs (internal).
bool stream_mode
cwist_http_response_stream_begin() ran.
cwist_sstring * stream_buf
Framed chunk payload (hex\r\ndata\r ...).
size_t ptr_body_len
Length of external data.
bool stream_failed
A live sink rejected a chunk/head.
size_t file_stream_len
Total bytes to stream.
bool stream_ended
cwist_http_response_stream_end() ran.
bool use_forking
Process per request.
bool use_threading
Thread per request.
bool use_epoll
Use epoll for accepting.
int(* compare)(struct cwist_sstring *left, const struct cwist_sstring *right)
Should mimic strcmp, internally use strncmp.
char * data
Access directly only when raw handling is necessary.
cwist_error_t(* append)(struct cwist_sstring *str, const struct cwist_sstring *from)
Append another sstring.
cwist_error_t(* copy)(struct cwist_sstring *str, const struct cwist_sstring *from)
size_t(* get_size)(struct cwist_sstring *str)
bool borrows_buffer
data is borrowed (static/arena); never freed, detached on mutation
Unified error code and type definitions for the CWIST system layer.
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...
WASI target feature detection shared by all CWIST sources.
Zero-latency speculative fast-path writer and P2C worker scheduler.
cwist_write_status_t cwist_http_sendmsg_speculative(int fd, struct iovec *iov, int iovcnt, int flags, size_t *total_sent)
Speculative scatter/gather sendmsg without blocking poll().
uint32_t cwist_sched_p2c_select_worker(const _Atomic uint32_t *worker_loads, uint32_t num_workers)
Select worker using Power of Two Random Choices (P2C).
uint32_t cwist_fast_monotonic_sec(void)
Fast monotonic clock in seconds (vDSO coarse where available).