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

High-level convenience API — csilk_app_t implementation. More...

#include "csilk/app/app.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "csilk/csilk.h"
Include dependency graph for app.c:

Data Structures

struct  cached_group_t
 Internal: cached route group lookup entry for fast prefix-to-group mapping. More...
 
struct  static_route_t
 Internal: descriptor for a static file serving route mapping URL prefix to filesystem directory. More...
 
struct  csilk_app_t
 Main application structure containing config, router, server, and groups. More...
 

Macros

#define CSILK_MAX_GROUPS   32
 
#define CSILK_MAX_STATIC   32
 
#define CSILK_DFL_PORT   8080
 

Functions

static void init_app_mutex (void)
 Internal: initialize the application-level mutex (called once via uv_once).
 
static csilk_router_tget_openapi_router (void)
 Internal: safely retrieve the current OpenAPI router under the app mutex.
 
static void set_openapi_router (csilk_router_t *r)
 Internal: atomically set the global OpenAPI router reference.
 
static void openapi_handler (csilk_ctx_t *c)
 Built-in handler for the /openapi.json endpoint.
 
static void docs_handler (csilk_ctx_t *c)
 Built-in handler for the /docs endpoint — serves the Swagger UI HTML page.
 
static csilk_group_t * find_or_create_group (csilk_app_t *app, const char *prefix)
 Find an existing group by prefix, or create a new one.
 
static void static_serve (csilk_ctx_t *c)
 Internal static file serving handler.
 
csilk_app_t * csilk_app_new (const char *config_path)
 Create a new application instance with optional YAML configuration file.
 
void csilk_app_free (csilk_app_t *app)
 Free all application resources: server, router, groups, config, and logger.
 
void csilk_app_log_level (csilk_app_t *app, csilk_log_level_t level)
 Set the minimum log level for the global logger.
 
void csilk_app_log_file (csilk_app_t *app, const char *path, size_t max_sz)
 Enable logging to a file with an optional rotation threshold.
 
void csilk_app_log_json (csilk_app_t *app, int enable)
 Enable or disable structured JSON log output format.
 
void csilk_app_use (csilk_app_t *app, csilk_handler_t h)
 Register a middleware handler that applies to all routes globally.
 
void csilk_app_use_group (csilk_app_t *app, const char *prefix, csilk_handler_t h)
 Register a middleware handler scoped to a specific URL prefix group.
 
void csilk_app_apply_config (csilk_app_t *app)
 Apply configuration-driven middleware settings.
 
void csilk_app_enable_openapi (csilk_app_t *app, int enable)
 Enable or disable the built-in /openapi.json endpoint.
 
void csilk_app_add_route (csilk_app_t *app, const char *method, const char *path, csilk_handler_t handler)
 Register a route on the root group with a single handler.
 
void csilk_app_add_route_extended (csilk_app_t *app, const char *method, const char *path, csilk_handler_t handler, const char *input_type, const char *output_type, const char *summary, const char *description)
 Register a route on the root group with a single handler and OpenAPI metadata.
 
void csilk_app_add_route_extended_perm (csilk_app_t *app, const char *method, const char *path, csilk_handler_t handler, const char *input_type, const char *output_type, const char *summary, const char *description, const char *perm_required, const char *perm_resource)
 Register a route with OpenAPI metadata (input/output types).
 
void csilk_app_add_route_perm (csilk_app_t *app, const char *method, const char *path, csilk_handler_t handler, const char *perm_required, const char *perm_resource)
 Register a route with permission metadata.
 
void csilk_app_add_handlers (csilk_app_t *app, const char *method, const char *path, csilk_handler_t *handlers, size_t n)
 Register a route with a custom handler chain on the root group.
 
void csilk_app_static (csilk_app_t *app, const char *prefix, const char *root_dir)
 Configure static file serving: map a URL prefix to a local filesystem directory.
 
void csilk_app_set_server_config (csilk_app_t *app, csilk_server_config_t c)
 Apply server-level configuration to the running server.
 
csilk_config_tcsilk_app_config (csilk_app_t *app)
 Get a heap-allocated copy of the current application configuration.
 
int csilk_app_run (csilk_app_t *app, int port)
 Start the server and enter the libuv event loop (blocking).
 
csilk_router_tcsilk_app_router (csilk_app_t *app)
 Get the underlying router handle from the application.
 
csilk_server_t * csilk_app_server (csilk_app_t *app)
 Get the underlying server handle from the application.
 

Variables

static csilk_router_ts_openapi_router = NULL
 Router reference for the built-in OpenAPI handler.
 
static uv_mutex_t s_app_mutex
 
static uv_once_t s_app_mutex_once = UV_ONCE_INIT
 
static static_route_t g_static [CSILK_MAX_STATIC]
 
static int g_static_n = 0
 

Detailed Description

High-level convenience API — csilk_app_t implementation.

Architecture

csilk_app_t is the top-level facade. It owns one router, one server, one root route group, and a config struct. Users interact only through the app handle; internal wiring (router -> server, group -> router) is hidden.

Bootstrap Sequence

csilk_app_new() runs in this order:

  1. Load YAML config (or apply hard-coded defaults).
  2. Initialize the logger from config.
  3. Create router + server, wire them together.
  4. Register built-in middleware (recovery, request logging) on the server.
  5. Create the root route group.
  6. Register built-in endpoints: /openapi.json, /docs, /csilk-docs/.

Routing & Static Files

Routes are added via the root group (csilk_group_t), which assembles a combined middleware + handler chain and hands it to the router. Static file serving uses a separate global table mapping URL prefixes to local directory roots, dispatched by a single static_serve handler.

OpenAPI / Swagger

The OpenAPI router reference is guarded by a process-level mutex so it can be toggled on/off safely at runtime. Two built-in handlers serve the spec JSON and the Swagger UI HTML page.

Version
0.2.1

Data Structure Documentation

◆ cached_group_t

struct cached_group_t

Internal: cached route group lookup entry for fast prefix-to-group mapping.

Avoids redundant group creation when the same prefix is referenced multiple times (e.g., csilk_app_use_group then csilk_app_add_route). The cache is linear-scanned; CSILK_MAX_GROUPS (32) keeps it cheap.

Data Fields
csilk_group_t * group

Cached group handle — created lazily by find_or_create_group(). Freed once in csilk_app_free().

char prefix[128]

URL path prefix — the lookup key.

◆ static_route_t

struct static_route_t

Internal: descriptor for a static file serving route mapping URL prefix to filesystem directory.

Stored in a fixed-size global table (g_static[]). The static_serve handler scans the table on each request to find the matching root_dir for the requested path.

Data Fields
char root_dir[256]

Local filesystem directory path served for this prefix. Passed to csilk_static() at dispatch time.

char url_prefix[128]

URL path prefix for static files (e.g., "/static"). Used as the lookup key in static_serve().

◆ csilk_app_s

struct csilk_app_s

Main application structure containing config, router, server, and groups.

Opaque application handle.

Lifecycle: created in csilk_app_new(), destroyed in csilk_app_free(). Ownership: owns everything except the OpenAPI router pointer (global).

Examples
/home/runner/work/csilk/csilk/include/csilk/app/app.h.
Collaboration diagram for csilk_app_t:
Data Fields
csilk_config_t config

Application config (port, logger settings, server timeouts, CORS, etc.). Populated from YAML or defaults in csilk_app_new().

int group_count

Number of valid entries in groups[] (0..32).

cached_group_t groups[CSILK_MAX_GROUPS]

Prefix-to-group cache. Linear-scanned array; avoids duplicating groups for the same prefix string. Indexed by group_count.

csilk_group_t * root_group

Root route group with prefix "". All routes added via csilk_app_add_route*() go through this group. Freed in csilk_app_free().

csilk_router_t * router

Central router — all registered routes (including static-file routes) converge here. Created in csilk_app_new(), freed in csilk_app_free().

csilk_server_t * server

libuv-based HTTP server. Wired to the router at creation. Built-in middlewares (recovery, logging) are injected via csilk_server_use().

Macro Definition Documentation

◆ CSILK_DFL_PORT

#define CSILK_DFL_PORT   8080

◆ CSILK_MAX_GROUPS

#define CSILK_MAX_GROUPS   32

◆ CSILK_MAX_STATIC

#define CSILK_MAX_STATIC   32

Function Documentation

◆ csilk_app_add_handlers()

void csilk_app_add_handlers ( csilk_app_t *  app,
const char *  method,
const char *  path,
csilk_handler_t handlers,
size_t  n 
)

Register a route with a custom handler chain on the root group.

Register a route with multiple handlers (middleware + handler).

Parameters
appApplication instance.
methodHTTP method.
pathURL path.
handlersArray of handler functions.
nNumber of handlers in the array.

◆ csilk_app_add_route()

void csilk_app_add_route ( csilk_app_t *  app,
const char *  method,
const char *  path,
csilk_handler_t  handler 
)

Register a route on the root group with a single handler.

Register a route with a single handler.

Parameters
appApplication instance.
methodHTTP method (e.g., "GET", "POST").
pathURL path (e.g., "/users").
handlerHandler function.

◆ csilk_app_add_route_extended()

void csilk_app_add_route_extended ( csilk_app_t *  app,
const char *  method,
const char *  path,
csilk_handler_t  handler,
const char *  input_type,
const char *  output_type,
const char *  summary,
const char *  description 
)

Register a route on the root group with a single handler and OpenAPI metadata.

Register a route with OpenAPI metadata (input/output types).

Parameters
appApplication instance.
methodHTTP method.
pathURL path.
handlerHandler function.
input_typeRegistered type name for request body JSON schema.
output_typeRegistered type name for response body JSON schema.
summaryShort description for the OpenAPI operation.
descriptionDetailed description for the OpenAPI operation.

◆ csilk_app_add_route_extended_perm()

void csilk_app_add_route_extended_perm ( csilk_app_t *  app,
const char *  method,
const char *  path,
csilk_handler_t  handler,
const char *  input_type,
const char *  output_type,
const char *  summary,
const char *  description,
const char *  perm_required,
const char *  perm_resource 
)

Register a route with OpenAPI metadata (input/output types).

Register a route with full metadata including permissions.

Parameters
appApplication handle.
methodHTTP method string.
pathURL pattern (supports :param and *wildcard).
handlerRoute handler function.
input_typeRegistered type name for request body (NULL if none).
output_typeRegistered type name for response (NULL if none).
summaryShort operation summary (NULL if none).
descriptionDetailed operation description (NULL if none).

Register a route with OpenAPI metadata (input/output types).

Parameters
appApplication instance.
methodHTTP method.
pathURL path.
handlerHandler function.
input_typeRegistered type name for request body JSON schema.
output_typeRegistered type name for response body JSON schema.
summaryShort description for the OpenAPI operation.
descriptionDetailed description for the OpenAPI operation.
perm_requiredPermission required for this route, or NULL.
perm_resourceResource pattern for permission check, or NULL.

◆ csilk_app_add_route_perm()

void csilk_app_add_route_perm ( csilk_app_t *  app,
const char *  method,
const char *  path,
csilk_handler_t  handler,
const char *  perm_required,
const char *  perm_resource 
)

Register a route with permission metadata.

Parameters
appApplication instance.
methodHTTP method.
pathURL path.
handlerHandler function.
perm_requiredPermission identifier (e.g., "read"), or NULL.
perm_resourceResource pattern (e.g., "users:*"), or NULL.

◆ csilk_app_apply_config()

void csilk_app_apply_config ( csilk_app_t *  app)

Apply configuration-driven middleware settings.

Auto-apply built-in middleware based on current config.

Reads the current app config and sets up static file serving if config.static_files.enable is true and root_dir is configured. The prefix defaults to "/static" if not specified in the config.

Parameters
appApplication instance.

◆ csilk_app_config()

csilk_config_t * csilk_app_config ( csilk_app_t *  app)

Get a heap-allocated copy of the current application configuration.

Get a copy of the current application configuration.

Parameters
appApplication instance.
Returns
A malloc'd copy of the configuration struct. The caller must free it with free(). Returns NULL if app is NULL or allocation fails.
Note
The returned copy includes deep copies of any dynamically allocated string fields? No — it is a shallow memcpy. Use csilk_config_free() only if you modify strings separately.

◆ csilk_app_enable_openapi()

void csilk_app_enable_openapi ( csilk_app_t *  app,
int  enable 
)

Enable or disable the built-in /openapi.json endpoint.

Enable or disable the built-in /openapi.json endpoint. The endpoint is enabled by default when the app is created.

When enabled, the router's routes are exposed as an OpenAPI 3.0 specification at /openapi.json. When disabled, the endpoint returns 404.

Parameters
appApplication instance.
enable1 to enable, 0 to disable.

◆ csilk_app_free()

void csilk_app_free ( csilk_app_t *  app)

Free all application resources: server, router, groups, config, and logger.

Deallocate all application resources.

Teardown order (reverse of init)

  1. Close logger — stops accepting log entries.
  2. Free server — joins worker threads, closes connections, stops libuv.
  3. Free cached child groups (groups[] array).
  4. Free root group.
  5. Free router — releases all registered routes and OpenAPI metadata.
  6. Free config — releases dynamically allocated strings (log path, etc.).
  7. Free the app struct itself.

Server must be freed before the router because the server holds a reference to the router internally.

Parameters
appThe application instance to free (may be NULL).
Note
Safe to call with NULL. After this call the app pointer is invalid.

◆ csilk_app_log_file()

void csilk_app_log_file ( csilk_app_t *  app,
const char *  path,
size_t  max_sz 
)

Enable logging to a file with an optional rotation threshold.

Enable file logging with optional rotation.

Sets the log output to the specified file path. If max_sz > 0, the log file is rotated (renamed to .1) when it exceeds this size.

Parameters
appApplication instance.
pathFile path for log output. Pass NULL to disable file logging and revert to stdout.
max_szMaximum file size in bytes before rotation (0 = no limit).

◆ csilk_app_log_json()

void csilk_app_log_json ( csilk_app_t *  app,
int  enable 
)

Enable or disable structured JSON log output format.

Enable or disable JSON structured log output.

When enabled, log entries are emitted as JSON objects with structured fields (timestamp, level, file, line, message, request_id). When disabled, plain text format is used.

Parameters
appApplication instance.
enable1 for JSON format, 0 for plain text.

◆ csilk_app_log_level()

void csilk_app_log_level ( csilk_app_t *  app,
csilk_log_level_t  level 
)

Set the minimum log level for the global logger.

Set the minimum log level.

Reinitializes the logger with the updated level. Messages below this level are filtered out.

Parameters
appApplication instance.
levelNew minimum log level (e.g., CSILK_LOG_DEBUG).

◆ csilk_app_new()

csilk_app_t * csilk_app_new ( const char *  config_path)

Create a new application instance with optional YAML configuration file.

Create a new application with optional YAML config.

Bootstrap sequence (step-by-step)

Phase 1 — Config & Logging

  1. Allocate app struct (calloc). Set up the process-level app mutex (uv_once, thread-safe for concurrent csilk_app_new calls).
  2. Load YAML config from config_path, or apply hard-coded defaults (port 8080, info-level logging, 5 s idle timeout, 30 s I/O timeouts, 1 MB max body, 64 KB max header, 8 KB max URL, 100 headers, 128 backlog, TCP_NODELAY on).
  3. Initialize the logger from config.

Phase 2 — Core objects

  1. Create router + server, wire them together.
  2. Push built-in middleware onto the server's global middleware chain: recovery handler first, then request logger. Order matters — recovery must wrap everything.
  3. Create the root route group (prefix "").

Phase 3 — Built-in endpoints

  1. Register the global OpenAPI router reference so /openapi.json works.
  2. Register /openapi.json (GET) — reads the router's OpenAPI spec.
  3. Register /docs (GET) — serves embedded Swagger UI HTML.

Register /csdk-docs/ as a static file route pointing to the bundled Swagger UI assets.

Parameters
config_pathPath to a YAML configuration file, or NULL to use defaults (port 8080, info-level logging to stdout).
Returns
A new csilk_app_t instance, or NULL on initialization failure.
Note
The returned app must be freed with csilk_app_free(). On failure, any partially allocated resources are cleaned up internally.

◆ csilk_app_router()

csilk_router_t * csilk_app_router ( csilk_app_t *  app)

Get the underlying router handle from the application.

Get the underlying router for advanced operations.

Parameters
appApplication instance.
Returns
The router pointer, or NULL if app is NULL.

◆ csilk_app_run()

int csilk_app_run ( csilk_app_t *  app,
int  port 
)

Start the server and enter the libuv event loop (blocking).

Start the server and block until stopped (Ctrl+C).

This is the main entry point into the event-driven I/O loop. It delegates to csilk_server_run() which:

  1. Creates a TCP listener on the given port.
  2. Spawns worker threads (if thread pool is configured).
  3. Calls uv_run(UV_RUN_DEFAULT) — blocks until the loop stops.
Parameters
appApplication instance.
portTCP port to listen on. Pass 0 or negative to use the port from the application config (default 8080).
Returns
The uv_run() return value on exit, or -1 on error.

◆ csilk_app_server()

csilk_server_t * csilk_app_server ( csilk_app_t *  app)

Get the underlying server handle from the application.

Get the underlying server for advanced operations.

Parameters
appApplication instance.
Returns
The server pointer, or NULL if app is NULL.

◆ csilk_app_set_server_config()

void csilk_app_set_server_config ( csilk_app_t *  app,
csilk_server_config_t  c 
)

Apply server-level configuration to the running server.

Apply server-level configuration (timeouts, limits, TCP options).

Updates both the app's stored config and applies it to the server instance. This overrides any previous server config.

Parameters
appApplication instance.
cServer configuration struct.

◆ csilk_app_static()

void csilk_app_static ( csilk_app_t *  app,
const char *  prefix,
const char *  root_dir 
)

Configure static file serving: map a URL prefix to a local filesystem directory.

Serve static files from a local directory.

What it does

  1. Acquires the app mutex, checks the g_static[] table capacity (max 32).
  2. Writes url_prefix + root_dir into the next free global slot.
  3. Releases mutex, then registers two GET routes on the group matching prefix:
    • WILDCARD path — wildcard route that captures everything after the prefix.
    • / — the prefix root (redirects to the index file). Both routes use the same internal static_serve handler, which scans g_static[] at request time to find the correct root_dir.

The dual-route pattern means both /static/ and /static/foo/bar.jpg work. The static_prefix context variable lets csilk_static() strip the URL prefix from the filesystem path.

Parameters
appApplication instance.
prefixURL path prefix for static files (e.g., "/static").
root_dirLocal filesystem directory to serve files from.
Note
Routes are added using the app's internal static_serve handler which dispatches to csilk_static() with the correct root directory.

◆ csilk_app_use()

void csilk_app_use ( csilk_app_t *  app,
csilk_handler_t  h 
)

Register a middleware handler that applies to all routes globally.

Register a global middleware that runs on every route.

Global middleware runs before route-specific middleware and handlers for every request. Built-in recovery and logger middleware are registered automatically by csilk_app_new().

Parameters
appApplication instance.
hMiddleware handler function.
Note
There is a hard limit of 32 global middlewares.

◆ csilk_app_use_group()

void csilk_app_use_group ( csilk_app_t *  app,
const char *  prefix,
csilk_handler_t  h 
)

Register a middleware handler scoped to a specific URL prefix group.

Register a middleware that runs only on a specific prefix group.

Creates (or finds) a route group for the given prefix and adds the middleware to it. The middleware runs for any route whose path starts with the given prefix.

Parameters
appApplication instance.
prefixURL prefix (e.g., "/api/admin").
hMiddleware handler function.

◆ docs_handler()

static void docs_handler ( csilk_ctx_t *  c)
static

Built-in handler for the /docs endpoint — serves the Swagger UI HTML page.

Checks that the OpenAPI router is active, then serves the embedded Swagger UI HTML page which loads /openapi.json at runtime.

Parameters
cThe request context.

◆ find_or_create_group()

static csilk_group_t * find_or_create_group ( csilk_app_t *  app,
const char *  prefix 
)
static

Find an existing group by prefix, or create a new one.

Lookup strategy

  1. Root prefix ("", "/") — return root_group (lazy-created on first call).
  2. Linear scan groups[] cache — O(n) with n capped at CSILK_MAX_GROUPS (32).
  3. Cache miss — create a child group under root_group via csilk_group_group(), store in cache, return.

Nesting under root_group means any middleware registered on root_group automatically applies to all subgroup routes (see group.c: gather_handlers).

Parameters
appApplication handle.
prefixURL path prefix.
Returns
Route group instance, or NULL on failure.

◆ get_openapi_router()

static csilk_router_t * get_openapi_router ( void  )
static

Internal: safely retrieve the current OpenAPI router under the app mutex.

Returns
The current s_openapi_router pointer, or NULL.
Note
Thread-safe. The returned pointer should not be stored beyond the calling scope as it may change.

◆ init_app_mutex()

static void init_app_mutex ( void  )
static

Internal: initialize the application-level mutex (called once via uv_once).

Creates the mutex that protects the shared OpenAPI router reference and the static file route table.

◆ openapi_handler()

static void openapi_handler ( csilk_ctx_t *  c)
static

Built-in handler for the /openapi.json endpoint.

Retrieves the current OpenAPI router and serves the generated OpenAPI 3.0 specification as a JSON response. If the router is NULL (endpoint disabled), returns 404.

Parameters
cThe request context.

◆ set_openapi_router()

static void set_openapi_router ( csilk_router_t r)
static

Internal: atomically set the global OpenAPI router reference.

Parameters
rRouter to set (pass NULL to disable the OpenAPI endpoint).
Note
Thread-safe. The previous value is simply overwritten.

◆ static_serve()

static void static_serve ( csilk_ctx_t *  c)
static

Internal static file serving handler.

Dispatch algorithm

  1. Scan the global g_static[] table under the app mutex.
  2. Compare the request path against each url_prefix.
  3. On match: release the mutex, store the matched prefix in the context (for csilk_static to use as a path-stripping hint), and call csilk_static() with the mapped root_dir.
  4. If no prefix matches, return 404.

The mutex is released before csilk_static() to avoid holding it during disk I/O. The prefix match is a simple strncmp — the longest prefix configured first wins.

Parameters
cThe request context.

Variable Documentation

◆ g_static

static_route_t g_static[CSILK_MAX_STATIC]
static

◆ g_static_n

int g_static_n = 0
static

◆ s_app_mutex

uv_mutex_t s_app_mutex
static

◆ s_app_mutex_once

uv_once_t s_app_mutex_once = UV_ONCE_INIT
static

◆ s_openapi_router

csilk_router_t* s_openapi_router = NULL
static

Router reference for the built-in OpenAPI handler.