Csilk 0.2.1
A lightweight, high-performance C HTTP web framework
Loading...
Searching...
No Matches
server.c File Reference

Core event-driven HTTP server implementation. More...

#include <limits.h>
#include <llhttp.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#include "context_internal.h"
#include "csilk/core/internal.h"
#include "csilk/csilk.h"
#include "server_internal.h"
#include "h2.h"
#include "csilk/reflection/reflect.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
Include dependency graph for server.c:

Data Structures

struct  worker_data_t
 Per-worker thread initialization data for SO_REUSEPORT multi-loop mode. More...
 
struct  worker_stop_data_t
 

Macros

#define UV_HANDLE_BOUND   0x00002000
 

Functions

static void alloc_buffer (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
 libuv buffer allocation callback — allocates a receive buffer.
 
static void on_read (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
 libuv read callback — processes incoming data from a client connection.
 
static void on_idle_timeout (uv_timer_t *handle)
 libuv timer callback: fired when the connection has been idle (no active request) beyond keepalive_idle_ms.
 
static void on_write_timeout (uv_timer_t *handle)
 libuv timer callback: fired when the response write has not completed within write_timeout_ms.
 
static void on_server_handle_close (uv_handle_t *handle)
 libuv close callback for server-level handles during shutdown.
 
static void init_tls (csilk_server_t *s)
 Initialize the server's TLS/SSL context using OpenSSL.
 
static void cleanup_tls (csilk_server_t *s)
 Clean up the server's TLS/SSL context and global SSL state.
 
static int setup_client_tls (csilk_client_t *client)
 Set up TLS for an individual client connection.
 
static void process_tls_read (csilk_client_t *client)
 Process incoming TLS data — complete the handshake or decrypt application data.
 
static void flush_tls_write (csilk_client_t *client)
 Flush buffered TLS encrypted data to the client socket.
 
static void trigger_hooks (csilk_server_t *s, csilk_ctx_t *c, csilk_hook_type_t type)
 Internal: invoke all registered handlers for a given hook type.
 
static csilk_client_t * pool_get (csilk_server_t *server)
 Get a client connection object from the server's free pool or allocate a new one.
 
static void pool_put (csilk_server_t *server, csilk_client_t *client)
 Return a client connection to the server's free pool for reuse.
 
static void client_list_add (csilk_server_t *server, csilk_client_t *client)
 Insert a client at the head of the server's active client list.
 
static void client_list_remove_internal (csilk_server_t *server, csilk_client_t *client)
 Remove a client from the active list (no locking).
 
static void client_list_remove (csilk_server_t *server, csilk_client_t *client)
 Remove a client from the active list (thread-safe).
 
static void on_timer_close (uv_handle_t *handle)
 libuv close callback for client timer handles.
 
static void on_close (uv_handle_t *handle)
 libuv close callback for client TCP handles — performs full cleanup.
 
static void on_signal (uv_signal_t *handle, int signum)
 libuv signal handler for SIGINT — initiates graceful server shutdown.
 
static void on_stop_async (uv_async_t *handle)
 libuv async callback to stop the server gracefully.
 
static void on_sendfile_complete (uv_fs_t *req)
 libuv sendfile completion callback — continues with keep-alive or close.
 
static void on_write (uv_write_t *req, int status)
 libuv write completion callback — handles post-write pipeline.
 
static void on_read_timeout (uv_timer_t *handle)
 libuv timer callback: fired when no request data has been received within read_timeout_ms.
 
static int on_message_begin (llhttp_t *p)
 llhttp callback: a new HTTP message begins.
 
static int on_url (llhttp_t *p, const char *at, size_t length)
 llhttp callback: URL data received.
 
static int on_header_field (llhttp_t *p, const char *at, size_t length)
 llhttp callback: header field name received.
 
static char * buf_grow (char *buf, size_t *cap, size_t needed)
 Grow a heap-allocated buffer to at least needed bytes.
 
static int on_header_value (llhttp_t *p, const char *at, size_t length)
 llhttp callback: header value data received.
 
static int on_headers_complete (llhttp_t *p)
 llhttp callback: all HTTP headers have been received.
 
static int on_body (llhttp_t *p, const char *at, size_t length)
 llhttp callback: body data received.
 
static const char * get_status_text (int status)
 Map an HTTP status code to its standard reason phrase.
 
void csilk_client_write (csilk_client_t *client, const uint8_t *data, size_t len)
 Send raw data to the client (TLS-aware).
 
void _csilk_send_data (csilk_ctx_t *c, const uint8_t *data, size_t len)
 Internal: Send data through the appropriate I/O path.
 
void _csilk_send_response (csilk_ctx_t *c)
 Send the assembled HTTP response to the client.
 
static void finalize_request (csilk_client_t *client, llhttp_t *p)
 Finalize the parsed request data before routing.
 
static int on_message_complete (llhttp_t *p)
 llhttp callback: the full HTTP request message has been parsed.
 
static void on_rejected_close (uv_handle_t *handle)
 
static void on_new_connection (uv_stream_t *server_stream, int status)
 libuv connection callback — accept a new incoming TCP connection.
 
const char * csilk_get_client_ip (csilk_ctx_t *c)
 Get the remote client's IP address as a string.
 
csilk_server_t * csilk_server_new (csilk_router_t *router)
 Create a new server instance associated with a router.
 
static void spa_fallback_handler (csilk_ctx_t *c)
 Built-in SPA (Single Page Application) fallback handler.
 
void csilk_server_set_not_found_handler (csilk_server_t *server, csilk_handler_t handler)
 Set a custom handler for unmatched routes (404 Not Found).
 
void csilk_server_set_spa_fallback (csilk_server_t *server, const char *doc_root)
 Enable SPA fallback: all unmatched GET requests serve index.html from the given directory.
 
int csilk_server_use (csilk_server_t *server, csilk_handler_t handler)
 Register a global middleware handler that runs before every request.
 
void csilk_server_free (csilk_server_t *server)
 Free a server instance and all associated resources.
 
void csilk_server_stop (csilk_server_t *server)
 Signal the server to stop gracefully (thread-safe).
 
void csilk_server_get_stats (csilk_server_t *server, int *active_conn, int *pooled_conn)
 
void csilk_server_set_config (csilk_server_t *server, const csilk_server_config_t *config)
 Apply a server configuration struct, overwriting the current settings.
 
int csilk_server_set_max_connections (csilk_server_t *server, int max)
 Set the maximum number of concurrent client connections.
 
void csilk_server_set_storage_driver (csilk_server_t *server, csilk_storage_driver_t *driver)
 Set the pluggable storage driver for context key-value operations.
 
void csilk_server_set_crypto_driver (csilk_server_t *server, csilk_crypto_driver_t *driver)
 Set the pluggable cryptographic driver for the server.
 
void csilk_server_set_cipher_driver (csilk_server_t *server, csilk_cipher_driver_t *driver)
 Set the global cipher algorithm driver for the server.
 
void csilk_server_add_hook (csilk_server_t *s, csilk_hook_type_t type, void *handler)
 Register a lifecycle hook on the server.
 
static int bind_and_listen (uv_loop_t *loop, uv_tcp_t *out_handle, int port, int backlog, bool reuseport)
 Create, bind, and listen on a TCP socket with optional SO_REUSEPORT.
 
static void on_worker_stop_async (uv_async_t *handle)
 Async callback for stopping a worker's event loop gracefully.
 
static void worker_thread (void *arg)
 Worker thread entry point for multi-threaded SO_REUSEPORT mode.
 
int csilk_server_run (csilk_server_t *server, int port)
 Start the server, bind to the given port, and enter the main event loop (blocking).
 
static int alpn_select_cb (SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg)
 
csilk_mq_t * csilk_server_get_mq (csilk_server_t *server)
 Get the internal message queue instance for the server.
 
csilk_router_tcsilk_server_get_router (csilk_server_t *server)
 Get the router instance attached to a server.
 

Detailed Description

Core event-driven HTTP server implementation.

Architecture overview:

This file implements the full lifecycle of a csilk HTTP/TLS server. The design is built on three layers:

  1. Transport layer (libuv): Asynchronous TCP I/O with epoll/kqueue/IO- completion-ports. All I/O is non-blocking and event-driven.
  2. Protocol layer (llhttp): Fast HTTP/1.1 request parsing. Each client connection has a dedicated llhttp parser instance. Parsing happens incrementally as data arrives in on_read().
  3. Connection lifecycle: accept (on_new_connection) -> TLS handshake (if SSL) -> HTTP parse (on_read -> llhttp) -> request route (on_message_complete -> router) -> response (_csilk_send_response) -> keep-alive or close

Key design decisions:

  • TLS uses BIO pairs (memory BIOs) so encryption/decryption is driven by the same on_read callback without changing the I/O model.
  • Client objects are pooled (up to 32) to reduce allocation churn.
  • Connection limits are enforced by accept+immediate-close (drains the kernel backlog without blocking).
  • Graceful shutdown is async: csilk_server_stop() sends an async signal to the event loop, which closes the listener and all active clients.
  • Multi-worker mode uses SO_REUSEPORT so each thread has its own accept loop; the kernel distributes connections across workers.

Data Structure Documentation

◆ worker_data_t

struct worker_data_t

Per-worker thread initialization data for SO_REUSEPORT multi-loop mode.

Passed to worker_thread() when spawning multiple accept loops.

Data Fields
pthread_barrier_t * barrier

Barrier synchronising worker init with main thread.

int port

Port to listen on.

csilk_server_t * server

The server instance.

int worker_index

Index into server->worker_stop_async[].

◆ worker_stop_data_t

struct worker_stop_data_t
Data Fields
uv_tcp_t * listen_handle

The worker's local listen handle.

uv_loop_t * loop

The worker's event loop.

csilk_server_t * server

The server instance.

int worker_index

Index for worker_stop_async.

Macro Definition Documentation

◆ UV_HANDLE_BOUND

#define UV_HANDLE_BOUND   0x00002000

Function Documentation

◆ _csilk_send_data()

void _csilk_send_data ( csilk_ctx_t *  c,
const uint8_t *  data,
size_t  len 
)

Internal: Send data through the appropriate I/O path.

Routes data through the TLS wrapper if TLS is enabled, or writes directly to the TCP socket otherwise.

Parameters
cRequest context.
dataBytes to send.
lenNumber of bytes to send.

◆ _csilk_send_response()

void _csilk_send_response ( csilk_ctx_t *  c)

Send the assembled HTTP response to the client.

Internal: Trigger the response send path.

This is the central response-serialization function. It constructs the HTTP response bytes in memory and sends them via _csilk_send_data(). The response format is:

HTTP/1.1 <status> <reason>\r
[Transfer-Encoding: chunked\r
] Content-Length: <len>\r
Connection: keep-alive|close\r
<custom headers...>\r
\r
<body>

Response mode selection:

  • Normal (Sync): Content-Length is set to body_len; body is appended inline after the header block.
  • Chunked (Async): No Content-Length; Transfer-Encoding: chunked is set. Used when the handler calls csilk_next() with is_async = true, meaning it may write response chunks over time.
  • File (sendfile): Content-Length is set to file_size; the header is sent via _csilk_send_data, then on_write triggers uv_fs_sendfile for zero-copy file delivery. Only available on non-TLS connections.
  • WebSocket (101): Minimal header; the caller manages frames via csilk_ws_send(). See is_websocket branch.

After the response is sent:

  • For sendfile: return early, defer cleanup to on_sendfile_complete.
  • For keep-alive: restart the idle timer, begin reading next request.
  • For close: initiate uv_close.
  • Fire CSILK_HOOK_REQUEST_END, clean up context.
Parameters
cRequest context (must have _internal_client set).

◆ alloc_buffer()

static void alloc_buffer ( uv_handle_t *  handle,
size_t  suggested_size,
uv_buf_t *  buf 
)
static

libuv buffer allocation callback — allocates a receive buffer.

Allocates a buffer of the suggested size using malloc. The buffer is freed by libuv after the read callback is invoked.

Parameters
handleThe libuv handle that will read into the buffer.
suggested_sizeRecommended buffer size from libuv.
buf[out] Pointer to the uv_buf_t to populate.

◆ alpn_select_cb()

static int alpn_select_cb ( SSL *  ssl,
const unsigned char **  out,
unsigned char *  outlen,
const unsigned char *  in,
unsigned int  inlen,
void *  arg 
)
static

◆ bind_and_listen()

static int bind_and_listen ( uv_loop_t *  loop,
uv_tcp_t *  out_handle,
int  port,
int  backlog,
bool  reuseport 
)
static

Create, bind, and listen on a TCP socket with optional SO_REUSEPORT.

Two code paths:

SO_REUSEPORT path (reuseport=true, non-Windows): Creates a raw socket with socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK), sets SO_REUSEADDR and SO_REUSEPORT, binds, and listens. The socket fd is then handed to libuv via uv_tcp_open(). This is used in multi-worker mode so each worker thread has its own accept loop sharing the same port. The kernel distributes incoming connections across the workers in a round-robin fashion.

IMPORTANT: uv_tcp_open() does not set the internal UV_HANDLE_BOUND flag. Since uv_listen() checks for this flag, we must set it manually (out_handle->flags |= UV_HANDLE_BOUND) before calling uv_listen().

Standard path (reuseport=false or Windows): Uses libuv's standard uv_tcp_bind() + uv_listen() sequence. This works on all platforms but does not support SO_REUSEPORT.

Parameters
loopEvent loop to attach the TCP handle to.
out_handle[out] Initialized TCP handle (managed by libuv, do not free by caller).
portTCP port number.
backlogMaximum length of the pending connections queue.
reuseportEnable SO_REUSEPORT for multi-process/thread socket sharing.
Returns
0 on success, -1 on socket/bind/listen error.

◆ buf_grow()

static char * buf_grow ( char *  buf,
size_t *  cap,
size_t  needed 
)
static

Grow a heap-allocated buffer to at least needed bytes.

Uses realloc with capacity doubling for amortized O(1) growth. If buf is NULL and *cap is 0, this acts as a malloc. On realloc failure the original buffer is NOT freed (caller must free it).

Parameters
bufExisting allocation (may be NULL).
cap[in,out] Current capacity — updated on success.
neededMinimum required size in bytes.
Returns
Pointer to the resized buffer, or NULL on allocation failure.

◆ cleanup_tls()

static void cleanup_tls ( csilk_server_t *  s)
static

Clean up the server's TLS/SSL context and global SSL state.

Frees the SSL_CTX and calls EVP_cleanup() for OpenSSL global cleanup.

Parameters
sThe server instance (may have ssl_ctx == NULL).

◆ client_list_add()

static void client_list_add ( csilk_server_t *  server,
csilk_client_t *  client 
)
static

Insert a client at the head of the server's active client list.

Thread-safe: acquires the clients_mutex before modification.

Parameters
serverThe server instance.
clientThe client to add (must not already be in a list).

◆ client_list_remove()

static void client_list_remove ( csilk_server_t *  server,
csilk_client_t *  client 
)
static

Remove a client from the active list (thread-safe).

Acquires clients_mutex, then delegates to client_list_remove_internal().

Parameters
serverThe server instance.
clientThe client to remove.

◆ client_list_remove_internal()

static void client_list_remove_internal ( csilk_server_t *  server,
csilk_client_t *  client 
)
static

Remove a client from the active list (no locking).

Unlinks the client from the doubly-linked list and clears its prev/next pointers. Caller must hold clients_mutex.

Parameters
serverThe server instance.
clientThe client to remove.

◆ csilk_client_write()

void csilk_client_write ( csilk_client_t *  client,
const uint8_t *  data,
size_t  len 
)

Send raw data to the client (TLS-aware).

Write data to the client's TCP socket, handling TLS encryption if necessary.

If TLS is active, writes through the SSL session and flushes the write BIO. Otherwise, allocates a write request, copies the data, and queues the write via libuv. The data buffer is freed by the write completion callback.

Parameters
cThe request context.
dataData buffer to send.
lenLength of data in bytes.
Note
This is an internal function used by the framework to send HTTP responses, chunked frames, and WebSocket frames.

◆ csilk_get_client_ip()

const char * csilk_get_client_ip ( csilk_ctx_t *  c)

Get the remote client's IP address as a string.

Get the client's IP address.

Resolves the client's IP address (IPv4 or IPv6) from the underlying TCP socket using libuv's getpeername. The result is allocated in arena memory so it is valid for the duration of the request.

Parameters
cThe request context.
Returns
A string with the client IP (e.g., "127.0.0.1" or "::1"), or NULL if the context is NULL or the address cannot be resolved.

◆ csilk_server_add_hook()

void csilk_server_add_hook ( csilk_server_t *  s,
csilk_hook_type_t  type,
void *  handler 
)

Register a lifecycle hook on the server.

Register a lifecycle hook callback.

Hooks are invoked at specific points in the request lifecycle (conn_open, conn_close, request_begin, request_end, server_start, server_stop). Multiple handlers can be registered for the same hook type; they are called in reverse order of registration (LIFO).

Parameters
sThe server instance.
typeHook type (CSILK_HOOK_CONN_OPEN, CSILK_HOOK_REQUEST_BEGIN, CSILK_HOOK_CONN_CLOSE, CSILK_HOOK_REQUEST_END, CSILK_HOOK_SERVER_START, CSILK_HOOK_SERVER_STOP).
handlerFunction pointer. For server hooks (start/stop), the signature is void(*)(csilk_server_t*). For context hooks, the signature is void(*)(csilk_ctx_t*).

◆ csilk_server_free()

void csilk_server_free ( csilk_server_t *  server)

Free a server instance and all associated resources.

Destroy the server and release all resources.

Should only be called after the event loop has stopped. Joins any worker threads, frees the SPA doc root, drains the client pool, cleans up TLS, frees the message queue, frees all registered hooks, destroys the clients mutex, and frees the server struct.

Parameters
serverThe server to free (may be NULL).
Note
Safe to call with NULL. After this call the server pointer is invalid.

◆ csilk_server_get_mq()

csilk_mq_t * csilk_server_get_mq ( csilk_server_t *  server)

Get the internal message queue instance for the server.

Get the Message Queue instance attached to a server.

The MQ is created automatically during csilk_server_new(). It can be used to register topics, subscribers, and publish messages.

Parameters
serverThe server instance.
Returns
Pointer to the MQ instance, or NULL if server is NULL.

◆ csilk_server_get_router()

csilk_router_t * csilk_server_get_router ( csilk_server_t *  server)

Get the router instance attached to a server.

Parameters
serverThe server instance.
Returns
Pointer to csilk_router_t.

◆ csilk_server_get_stats()

void csilk_server_get_stats ( csilk_server_t *  server,
int *  active_conn,
int *  pooled_conn 
)

◆ csilk_server_new()

csilk_server_t * csilk_server_new ( csilk_router_t router)

Create a new server instance associated with a router.

Create a new server instance.

Initializes the reflection system, allocates the server struct, sets up the libuv default loop, configures the llhttp parser callbacks, applies default server configuration (timeouts, buffer limits, backlog), creates a clients mutex, and creates the internal message queue (MQ) instance.

Parameters
routerThe router instance to use for request matching.
Returns
A new csilk_server_t instance, or NULL on allocation failure.
Note
The server must be configured (via csilk_server_set_config()) and started via csilk_server_run(). Free with csilk_server_free().

◆ csilk_server_run()

int csilk_server_run ( csilk_server_t *  server,
int  port 
)

Start the server, bind to the given port, and enter the main event loop (blocking).

Start the server and enter the libuv event loop.

This is the final step in server startup. The full bootstrap sequence:

  1. TLS init: load SSL_CTX with cert + key (if enable_tls is set). If TLS init fails, the server returns -1 (fail-fast).
  2. Async handle: uv_async_init for cross-thread stop signals. csilk_server_stop() calls uv_async_send() which wakes the event loop and runs on_stop_async().
  3. Bind + listen: bind_and_listen() with SO_REUSEPORT if worker_threads > 1, otherwise standard single-socket bind.
  4. TCP keepalive: if configured, enable TCP keepalive probes to detect dead peers.
  5. Worker threads: if worker_threads > 1, spawn N-1 worker threads each running their own libuv loop + accept loop (SO_REUSEPORT). The main thread also runs its own event loop, so total accept loops = worker_threads.
  6. SIGINT handler: register a libuv signal watcher that calls csilk_server_stop() on SIGINT (Ctrl+C).
  7. Fire CSILK_HOOK_SERVER_START.
  8. uv_run(): enter the event loop. Blocks until the loop stops (via csilk_server_stop() or SIGINT).
Parameters
serverThe server instance.
portTCP port to bind to.
Returns
The uv_run() return value on exit, or -1 on initialization failure.
Note
When worker_threads > 1, the main thread runs the event loop and additional worker threads each run their own independent loop.

◆ csilk_server_set_cipher_driver()

void csilk_server_set_cipher_driver ( csilk_server_t *  server,
csilk_cipher_driver_t driver 
)

Set the global cipher algorithm driver for the server.

Set the cipher driver for symmetric/asymmetric encryption.

Replaces the built-in AES/RSA routines with a user-provided implementation.

Parameters
serverThe server instance.
driverPointer to a csilk_cipher_driver_t.

◆ csilk_server_set_config()

void csilk_server_set_config ( csilk_server_t *  server,
const csilk_server_config_t config 
)

Apply a server configuration struct, overwriting the current settings.

Apply server configuration options.

Copies the provided configuration into the server instance. This should be called before csilk_server_run().

Parameters
serverThe server instance.
configPointer to the configuration to apply (copied by value).

◆ csilk_server_set_crypto_driver()

void csilk_server_set_crypto_driver ( csilk_server_t *  server,
csilk_crypto_driver_t driver 
)

Set the pluggable cryptographic driver for the server.

Set the global crypto driver for the server.

When set, HMAC and UUID operations on request contexts will delegate to the driver instead of using the built-in software implementations.

Parameters
serverThe server instance.
driverPointer to the crypto driver vtable (may be NULL to reset).

◆ csilk_server_set_max_connections()

int csilk_server_set_max_connections ( csilk_server_t *  server,
int  max 
)

Set the maximum number of concurrent client connections.

Set the maximum number of concurrent connections and return the previous limit.

When this limit is reached, new connections are accepted and immediately closed to drain the listen backlog. A value of 0 means unlimited.

Parameters
serverThe server instance.
maxMaximum concurrent connections (0 for unlimited).
Returns
The previous maximum connections value, or -1 if server is NULL.

◆ csilk_server_set_not_found_handler()

void csilk_server_set_not_found_handler ( csilk_server_t *  server,
csilk_handler_t  handler 
)

Set a custom handler for unmatched routes (404 Not Found).

Set a custom handler for 404 (route-not-found) responses.

Replaces the default "Not Found" plain-text response with a custom handler. Overridden by csilk_server_set_spa_fallback().

Parameters
serverThe server instance.
handlerHandler function invoked for unmatched routes.

◆ csilk_server_set_spa_fallback()

void csilk_server_set_spa_fallback ( csilk_server_t *  server,
const char *  doc_root 
)

Enable SPA fallback: all unmatched GET requests serve index.html from the given directory.

Enable single-page application (SPA) fallback mode.

Sets the SPA document root and replaces the 404 handler with the built-in spa_fallback_handler. Overrides any custom 404 handler set via csilk_server_set_not_found_handler().

Parameters
serverThe server instance.
doc_rootAbsolute or relative filesystem path to the directory containing index.html.
Note
The doc_root string is strdup'd internally. Pass NULL to disable.

◆ csilk_server_set_storage_driver()

void csilk_server_set_storage_driver ( csilk_server_t *  server,
csilk_storage_driver_t driver 
)

Set the pluggable storage driver for context key-value operations.

Replace the context key-value storage driver.

When set, calls to csilk_set()/csilk_get() on request contexts belonging to this server will delegate to the driver instead of using the default arena-backed linked list.

Parameters
serverThe server instance.
driverPointer to the storage driver vtable (may be NULL to reset).

◆ csilk_server_stop()

void csilk_server_stop ( csilk_server_t *  server)

Signal the server to stop gracefully (thread-safe).

Request a graceful server shutdown.

Sends an async signal to the event loop which triggers on_stop_async() on the main loop thread. The function returns immediately; the server shuts down asynchronously.

Parameters
serverThe server instance.
Note
This is safe to call from any thread, including signal handlers.

◆ csilk_server_use()

int csilk_server_use ( csilk_server_t *  server,
csilk_handler_t  handler 
)

Register a global middleware handler that runs before every request.

Register global middleware.

Global middlewares are prepended to the matched route's handler chain. They run before route-specific middleware and the final handler. There is a hard limit of 32 global middlewares.

Parameters
serverThe server instance.
handlerMiddleware handler function.
Returns
0 on success, -1 if the limit is reached or parameters are NULL.

◆ finalize_request()

static void finalize_request ( csilk_client_t *  client,
llhttp_t *  p 
)
static

Finalize the parsed request data before routing.

Stores any remaining header field+value pair, splits the URL into path and query string, URL-decodes the path, parses query parameters into the context's query_params map, and sets the HTTP method on the context.

Parameters
clientThe client connection.
pThe llhttp parser instance.

◆ flush_tls_write()

static void flush_tls_write ( csilk_client_t *  client)
static

Flush buffered TLS encrypted data to the client socket.

Reads encrypted data from the write BIO and sends it via libuv write requests. Must be called after SSL_write() or SSL_do_handshake() to ensure the encrypted output is actually transmitted.

Parameters
clientThe client connection whose write BIO should be drained.

◆ get_status_text()

static const char * get_status_text ( int  status)
static

Map an HTTP status code to its standard reason phrase.

Supports common codes: 101, 200, 201, 204, 400, 401, 403, 404, 500. Unrecognized codes default to "OK".

Parameters
statusHTTP status code.
Returns
A static string literal with the reason phrase.

◆ init_tls()

static void init_tls ( csilk_server_t *  s)
static

Initialize the server's TLS/SSL context using OpenSSL.

Loads error strings, initializes SSL algorithms, creates a TLS server method context, loads the certificate chain and private key from the configured file paths, optionally loads a CA file, and optionally enables peer verification. On any failure, the SSL context is freed and set to NULL (TLS is effectively disabled).

Parameters
sThe server instance (config must have tls_cert_file and tls_key_file set if enable_tls is true).

◆ on_body()

static int on_body ( llhttp_t *  p,
const char *  at,
size_t  length 
)
static

llhttp callback: body data received.

Appends body data to the request body buffer (realloc as needed). Enforces max_body_size limit (returns HPE_USER if exceeded). On realloc failure, the existing body is freed and HPE_USER is returned.

Parameters
pThe llhttp parser instance.
atPointer to body data.
lengthLength of body data in bytes.
Returns
0 (HPE_OK) on success, HPE_USER if the body exceeds max_body_size.

◆ on_close()

static void on_close ( uv_handle_t *  handle)
static

libuv close callback for client TCP handles — performs full cleanup.

Triggers the CSILK_HOOK_CONN_CLOSE hook, removes the client from the active connections list, stops all four timers, and initiates their close via on_timer_close. When all timers are closed, the client's request context, arena, and temporary buffers are freed and the client is returned to the pool.

Parameters
handleThe TCP handle being closed (data points to csilk_client_t).

◆ on_header_field()

static int on_header_field ( llhttp_t *  p,
const char *  at,
size_t  length 
)
static

llhttp callback: header field name received.

Accumulates header field names. When a previous field+value pair is complete, stores it in the request context. Enforces max_header_size and max_headers_count limits (returns HPE_USER on violation).

Parameters
pThe llhttp parser instance.
atPointer to header field data.
lengthLength of the header field data in bytes.
Returns
0 (HPE_OK) on success, HPE_USER if size/count limits are exceeded.

◆ on_header_value()

static int on_header_value ( llhttp_t *  p,
const char *  at,
size_t  length 
)
static

llhttp callback: header value data received.

Appends to the current header value buffer. Enforces max_header_size limit (returns HPE_USER if exceeded). On allocation failure, frees the partial value and returns HPE_USER.

Parameters
pThe llhttp parser instance.
atPointer to header value data.
lengthLength of header value data.
Returns
0 (HPE_OK) on success, HPE_USER if size limit or allocation fails.

◆ on_headers_complete()

static int on_headers_complete ( llhttp_t *  p)
static

llhttp callback: all HTTP headers have been received.

Flushes any remaining header field+value pair into the request context.

Parameters
pThe llhttp parser instance.
Returns
0 (HPE_OK) to continue parsing.

◆ on_idle_timeout()

static void on_idle_timeout ( uv_timer_t *  handle)
static

libuv timer callback: fired when the connection has been idle (no active request) beyond keepalive_idle_ms.

Closes the client connection immediately.

Parameters
handleThe timer handle (castable to client via handle->data).

◆ on_message_begin()

static int on_message_begin ( llhttp_t *  p)
static

llhttp callback: a new HTTP message begins.

Resets per-request state (total_header_size, header_count, etc. are reset elsewhere). Clears the thread-local request ID so each request starts fresh. Stops and restarts the request timeout timer.

Parameters
pThe llhttp parser instance (data points to csilk_client_t).
Returns
0 (HPE_OK) to continue parsing.

◆ on_message_complete()

static int on_message_complete ( llhttp_t *  p)
static

llhttp callback: the full HTTP request message has been parsed.

This is the main request dispatch point. It executes the following pipeline for every incoming HTTP request:

  1. finalize_request(): store remaining headers, split URL into path and query, URL-decode the path, parse query parameters.
  2. trigger_hooks(CSILK_HOOK_REQUEST_BEGIN): user-registered request-start hooks (e.g., request logging, rate limiting).
  3. csilk_router_match_ctx(): walk the radix tree to find a matching route. If matched, the handler chain is set on ctx->handlers.
  4. Global middleware prepend: if the server has global middlewares (registered via csilk_server_use()), they are prepended to the route-specific handler chain. The combined chain is allocated from the arena (no heap fragmentation for per-request allocations).
  5. csilk_next(): execute the handler chain. Handlers call csilk_next() to pass to the next handler, or send a response and mark is_async.
  6. If no route matched: invoke the 404 handler or send a default "Not Found" plain-text response.
  7. If not async: send the response synchronously via _csilk_send_response(). For async handlers, the response is sent later when the handler decides to complete.
Parameters
pThe llhttp parser instance.
Returns
0 (HPE_OK) on success, non-zero to abort parsing.

◆ on_new_connection()

static void on_new_connection ( uv_stream_t *  server_stream,
int  status 
)
static

libuv connection callback — accept a new incoming TCP connection.

This is the entry point for every new TCP connection. The sequence is:

  1. Connection limiter: if active_connections >= max_connections, accept and immediately close (drains the kernel backlog without processing).
  2. Client acquisition: get a client struct from the pool (pool_get). Pool reuse avoids calloc/free churn for every connection.
  3. TCP handle init: uv_tcp_init + uv_accept to attach the fd. TCP_NODELAY is applied if configured (disables Nagle's algorithm).
  4. Counters: atomic_fetch_add active_connections.
  5. Parser init: llhttp_init with the server's callback table. The parser state machine is reset for each new connection.
  6. TLS setup: if ssl_ctx is configured, set up BIO pairs and start the TLS handshake (setup_client_tls).
  7. Timer setup: read_timeout and request_timeout are one-shot timers that fire if no data arrives within the configured window.
  8. Arena init: per-connection bump allocator for request-scoped allocations (path strings, query params, arena handler chains).
  9. Read: uv_read_start registers the on_read callback with libuv.

If any step fails (allocation, accept, init), the client is cleaned up via close callbacks and returned to the pool.

Parameters
server_streamThe listening server stream.
statusConnection status (negative on error).

◆ on_read()

static void on_read ( uv_stream_t *  stream,
ssize_t  nread,
const uv_buf_t *  buf 
)
static

libuv read callback — processes incoming data from a client connection.

This is the heart of the event-driven I/O model. Every byte from every connection arrives here. The dispatch logic has three paths:

TLS path (client->ssl is set): Data is written to the read BIO, then process_tls_read() drives the TLS handshake (if not yet complete) or decrypts and feeds the result to the llhttp parser (or WebSocket frame parser). Encrypted output from the write BIO is flushed via flush_tls_write().

WebSocket path (client->ctx.is_websocket): Data is parsed directly as WebSocket frames by csilk_ws_parse_frame(). No HTTP parsing occurs on this connection after the upgrade.

HTTP path (default): Data is fed directly to llhttp_execute(). The callbacks in server->settings (on_url, on_header_field, on_body, etc.) incrementally build the request struct. When the request is complete, on_message_complete fires to dispatch routing.

On positive nread: feed data to the appropriate handler. On nread == UV_EOF: peer closed the connection; close the client. On nread < 0 (error): log and close.

The idle timer is always stopped when data arrives (keep-alive wait is reset). The read timeout is restarted.

Parameters
streamThe client TCP stream.
nreadNumber of bytes read (negative for error/EOF).
bufThe buffer that was read into (freed by this callback).

◆ on_read_timeout()

static void on_read_timeout ( uv_timer_t *  handle)
static

libuv timer callback: fired when no request data has been received within read_timeout_ms.

Closes the connection immediately.

Parameters
handleThe timer handle (castable to client via handle->data).

◆ on_rejected_close()

static void on_rejected_close ( uv_handle_t *  handle)
static

◆ on_sendfile_complete()

static void on_sendfile_complete ( uv_fs_t *  req)
static

libuv sendfile completion callback — continues with keep-alive or close.

sendfile() is used for efficient zero-copy file serving (kernel copies file data directly to the socket without userspace buffering). After sendfile completes, this callback:

  1. Cleans up the uv_fs_t request (freed).
  2. Checks whether the connection should be kept alive (HTTP/1.1 Connection header).
  3. If keep-alive: restarts the idle timer and begins reading again.
  4. If close: initiates uv_close to tear down the connection.
  5. Fires CSILK_HOOK_REQUEST_END and cleans up the request context.

sendfile is only used for non-TLS connections; TLS connections must use the buffered write path (SSL_write) because file data must be encrypted before transmission.

Parameters
reqThe completed uv_fs_t sendfile request (freed by this callback).

◆ on_server_handle_close()

static void on_server_handle_close ( uv_handle_t *  handle)
static

libuv close callback for server-level handles during shutdown.

Currently a no-op placeholder. Called when the server, signal, or async handles finish closing.

Parameters
handleThe handle being closed (unused).

◆ on_signal()

static void on_signal ( uv_signal_t *  handle,
int  signum 
)
static

libuv signal handler for SIGINT — initiates graceful server shutdown.

Delegates to csilk_server_stop() which sends an async signal to the event loop to trigger cleanup on the main loop thread.

Parameters
handlelibuv signal handle (data points to csilk_server_t).
signumReceived signal number (e.g., SIGINT).

◆ on_stop_async()

static void on_stop_async ( uv_async_t *  handle)
static

libuv async callback to stop the server gracefully.

This function performs the full shutdown sequence:

  1. Fire CSILK_HOOK_SERVER_STOP — so users can flush state.
  2. Close the listener (uv_close) — stops accepting new connections.
  3. Iterate active_clients and close each connection appropriately:
    • WebSocket: send close frame (1001) to notify the peer.
    • SSE: send a "close" event, then close the connection.
    • HTTP: close immediately (existing requests finish via on_close). ONLY main loop handles are closed here.
  4. Close the SIGINT and async handles.
  5. Signal all worker threads to stop.
  6. Free the message queue.

The actual client struct cleanup happens asynchronously in on_close() when each TCP handle finishes closing. This avoids blocking the event loop for connection draining.

Parameters
handlelibuv async handle (data points to csilk_server_t).

◆ on_timer_close()

static void on_timer_close ( uv_handle_t *  handle)
static

libuv close callback for client timer handles.

Decrements the close_pending counter. When all four timers are closed (close_pending reaches 0), the client is fully cleaned up: the arena is freed, temporary fields are freed, and the client is returned to the pool.

Parameters
handleThe timer handle being closed (data points to csilk_client_t).

◆ on_url()

static int on_url ( llhttp_t *  p,
const char *  at,
size_t  length 
)
static

llhttp callback: URL data received.

Stores the raw URL string. Checks against max_url_size and returns HPE_USER if exceeded (aborts parsing).

Parameters
pThe llhttp parser instance.
atPointer to the URL data.
lengthLength of the URL data in bytes.
Returns
0 (HPE_OK) on success, HPE_USER if URL exceeds max_url_size.

◆ on_worker_stop_async()

static void on_worker_stop_async ( uv_async_t *  handle)
static

Async callback for stopping a worker's event loop gracefully.

This is the worker-thread equivalent of on_stop_async. It:

  1. Closes the worker's local listen handle.
  2. Closes all active connections owned by this worker's loop.
  3. Closes the stop async handle itself.

The loop will then naturally exit when all close callbacks finish.

Parameters
handleThe per-worker async handle (data points to worker_stop_data_t).

◆ on_write()

static void on_write ( uv_write_t *  req,
int  status 
)
static

libuv write completion callback — handles post-write pipeline.

After a response body (or TLS-encrypted data) has been written to the socket, this callback orchestrates the next action:

  1. If the response includes a file descriptor (file_fd >= 0), the sendfile pipeline is triggered: uv_fs_sendfile() is called to stream file data directly from the kernel page cache to the socket. This path is only used for non-TLS connections.
  2. If no file descriptor is pending, the write request is freed and the connection's keep-alive/close decision is handled by the caller (_csilk_send_response, which already set up timers).
  3. On write error, logs the failure and does NOT retry (the caller is expected to close the connection via the read callback or timer).

The write request's data buffer (buf_copy) is freed here because it was allocated by _csilk_send_data / flush_tls_write.

Parameters
reqThe completed uv_write_t request.
status0 on success, negative on error.

◆ on_write_timeout()

static void on_write_timeout ( uv_timer_t *  handle)
static

libuv timer callback: fired when the response write has not completed within write_timeout_ms.

Closes the connection immediately.

Parameters
handleThe timer handle (castable to client via handle->data).

◆ pool_get()

static csilk_client_t * pool_get ( csilk_server_t *  server)
static

Get a client connection object from the server's free pool or allocate a new one.

Reuses a previously freed client if available (up to 32 pooled entries), otherwise allocates a new zero-initialized csilk_client_t. The returned client's file_fd is initialized to -1.

Parameters
serverThe server instance.
Returns
A csilk_client_t ready for use, or NULL on allocation failure.

◆ pool_put()

static void pool_put ( csilk_server_t *  server,
csilk_client_t *  client 
)
static

Return a client connection to the server's free pool for reuse.

If the client has an SSL session, it is freed first. The client struct is zeroed. If the pool has fewer than 32 entries, the client is saved for reuse; otherwise it is freed.

Parameters
serverThe server instance.
clientThe client to return (must not be used after this call).

◆ process_tls_read()

static void process_tls_read ( csilk_client_t *  client)
static

Process incoming TLS data — complete the handshake or decrypt application data.

If the TLS handshake is not yet complete, performs SSL_do_handshake() and flushes the write BIO. If the handshake is complete, calls SSL_read() in a loop to decrypt application data and feeds the decrypted data to the llhttp parser (or WebSocket frame parser).

Parameters
clientThe client connection with pending TLS data in the read BIO.

◆ setup_client_tls()

static int setup_client_tls ( csilk_client_t *  client)
static

Set up TLS for an individual client connection.

Creates a new SSL session from the server's SSL_CTX, initializes memory BIOs for reading and writing encrypted data, and starts the TLS handshake by calling process_tls_read().

Parameters
clientThe client connection to set up TLS on.
Returns
0 on success, -1 if SSL session creation or BIO setup fails.

◆ spa_fallback_handler()

static void spa_fallback_handler ( csilk_ctx_t *  c)
static

Built-in SPA (Single Page Application) fallback handler.

For unmatched GET requests, attempts to serve "index.html" from the configured SPA doc root. This enables client-side routing for SPAs like React, Vue, or Angular that handle their own URL routing in the browser.

Parameters
cThe request context.
Note
Only applies to GET requests. Non-GET unmatched requests receive a standard 404 response. The doc root is set via csilk_server_set_spa_fallback().

◆ trigger_hooks()

static void trigger_hooks ( csilk_server_t *  s,
csilk_ctx_t *  c,
csilk_hook_type_t  type 
)
static

Internal: invoke all registered handlers for a given hook type.

Walks the hook's linked list and calls each handler. Server-level hooks (start/stop) receive the server pointer. Context-level hooks receive the request context pointer.

Parameters
sThe server instance.
cThe request context (may be NULL for server-level hooks).
typeHook type to trigger.

◆ worker_thread()

static void worker_thread ( void *  arg)
static

Worker thread entry point for multi-threaded SO_REUSEPORT mode.

Each worker runs its own libuv event loop and accept loop, sharing the same port via SO_REUSEPORT. The kernel distributes incoming connections across worker threads. The worker_data_t argument is freed by this function.

The worker registers a per-worker uv_async_t in server->worker_stop_async[idx] so the main thread can signal it to stop gracefully. After uv_run returns, the async handle and the server_handle are closed synchronously, then the loop is closed.

Parameters
argPointer to worker_data_t (freed when the function exits).