Welcome to c-web-modules Documentation
Getting Started
c-web-modules is a proof-of-concept C plugin host for compute-focused web workloads. Inspired by kernel modules, it enables dynamic runtime compilation and deployment of C code directly to the server. No precompilation is required.
Server
The c-web-modules server is a simple web server that listens on port 8080. It provides the foundation for loading and executing modules at runtime. The server relies on the following libraries:
- openssl: For secure connections (planned) and hashing
- sqlite3: For database management
- jansson: For JSON parsing and manipulation
Check the Installation section for setup details. The easiest way to start the server is by using Docker.
Modules
Modules are dynamically loaded pieces of C code that define HTTP routes and WebSocket handlers. The most important part of a module is the module_t config.
For a module to be loaded into the server, it must define a module_t struct named config (preferably with the `export` macro). The module_t config defines module metadata, routes, WebSocket handlers, and load/unload hooks.
/* Module information */
typedef struct module {
char name[128];
char author[128];
route_info_t routes[10];
int size;
websocket_info_t websockets[10];
int ws_size;
job_info_t jobs[10];
int job_size;
void (*onload)(struct cweb_context *);
void (*unload)(struct cweb_context *);
} module_t;
Check the Examples section for sample modules.
Code examples
Minimal route module:
static int index_route(struct cweb_context *ctx, http_request_t *req, http_response_t *res) {
snprintf(res->body, HTTP_RESPONSE_SIZE, "ok");
res->status = HTTP_200_OK;
return 0;
}
export module_t config = {
.name = "counter",
.author = "cweb",
.routes = {
{"/counter", "GET", index_route, NONE},
},
.size = 1,
};
Minimal job module:
static int sum_job(struct cweb_context *ctx, const job_payload_t *payload, job_ctx_t *job) {
return job->set_result(job, "{\"ok\":true}");
}
export module_t config = {
.name = "job_sum",
.author = "cweb",
.job_size = 1,
.jobs = {
{"sum", sum_job},
},
};
Minimal WebSocket module:
#include <string.h>
#include <cweb.h>
static void on_open(struct cweb_context *ctx, websocket_t *ws) {
}
static void on_message(struct cweb_context *ctx, websocket_t *ws, const char *message, size_t length) {
ws->send(ws, message, length);
}
static void on_close(struct cweb_context *ctx, websocket_t *ws) {
}
export module_t config = {
.name = "websocket",
.author = "cweb",
.websockets = {
{"/websocket", on_open, on_message, on_close},
},
.ws_size = 1,
};
A route is defined by a path, method, function and flags. The path needs to be unique for the server, or else the module will not be loaded.
WebSockets are defined by a path, on_open, on_message and on_close. Connections stay alive through module reloads, and handler pointers are swapped when you deploy a new module.
Job model
c-web-modules is focused on compute + streaming:
- HTTP triggers jobs via server-owned /jobs endpoints.
- Jobs are defined by modules, not by HTTP routes.
- WebSockets stream job events with replay support.
- Job status includes module_hash for provenance.
- Hot-swap semantics: running jobs keep the old module; new jobs use the updated module.
Notice: Jobs are executed by a single scheduler worker thread. Multiple jobs can be queued, but they run one at a time.
Jobs system (server-level)
The server provides a durable, SQLite-backed jobs system. Jobs deploy the same way as HTTP/WS modules (upload to /mgnt). A module can define routes, WebSockets, and jobs together.
/jobs/* is a protected namespace owned by the server, and modules cannot register routes under /jobs/.
- POST /jobs - Trigger a job with { "module": "mod", "job": "name", "payload": { ... } }
- GET /jobs/:uuid - Job status, including module_hash
- POST /jobs/:uuid/cancel - Cancel a job
- GET /jobs?state=running - List jobs by state
WebSocket: WS /jobs/ws?id=:uuid&since_event_id=456 (streams job_events with replay support).
Authentication
- Set CWEB_ADMIN_KEY to require an admin key for /mgnt, /jobs, and /jobs/ws.
- Send X-API-Key: <key> or Authorization: Bearer <key>.
- In DEV builds, requests from 127.0.0.1 are allowed without a key.
Implemented details:
- Server-owned /jobs API with UUIDs, status, cancel, and list.
- SQLite tables for jobs + job_events (durable state and replay).
- Jobs declared in module_t.jobs[] and triggered by name.
- Module hash computed from uploaded .so for provenance.
- Hot-swap semantics: running jobs keep old module, new jobs use updated module.
- /jobs/ws supports id + since_event_id query params.
Deployment
The most simple way to load a module is to use curl:
curl -X POST -d @path/to/module.c http://localhost:8080/mgnt
You can also deploy modules dynamically with configuration files:
./cweb deploy path/to/module.c
# Using a config file (routes.ini)
server_url=http://localhost:8080/mgnt
[modules]
example1.c
example2.c
Examples
Highlighted examples in the repository:
- job_sum.c - Jobs API example with JSON payloads.
- websocket.c - WebSocket echo server.
- chat.c - Multi-user WebSocket chat with an HTML client.
- static.c - Simple static file server for /static/*.
Other examples: counter.c, json.c.
typedef struct job_payload {
const char *json;
size_t json_len;
} job_payload_t;
typedef struct job_ctx {
const char *job_uuid;
int (*emit_event)(struct job_ctx *job, const char *type, const char *data_json);
int (*set_result)(struct job_ctx *job, const char *result_json);
int (*set_error)(struct job_ctx *job, const char *error_text);
} job_ctx_t;
typedef int (*job_run_t)(struct cweb_context *ctx, const job_payload_t *payload, job_ctx_t *job);
typedef struct job_info {
const char *name;
job_run_t run;
void (*cancel)(struct cweb_context *ctx, const char *job_uuid);
} job_info_t;
For more examples, check out the following links:
Installation
Install the required dependencies for Linux or MacOS:
For Debian:
- sudo apt-get install libssl-dev
- sudo apt-get install libsqlite3-dev
- sudo apt-get install libjansson-dev
For Arch Linux:
- sudo pacman -S openssl
- sudo pacman -S sqlite
- sudo pacman -S jansson
For MacOS:
- brew install openssl
- brew install sqlite3
- brew install jansson
Compile and run the server:
make
make run
Server flags:
--module-dir <path>(or-m <path>): Store compiled modules and routes.dat in a specific directory. Defaults to modules.--purge-modules(or-P): Delete compiled .so modules on shutdown. Off by default.
WebSockets
WebSockets enable real-time streaming between clients and the server. The server exposes /jobs/ws for job events, and modules can also define custom WebSocket handlers.
#include <cweb.h>
static void on_open(struct cweb_context *ctx, websocket_t *ws) {
}
static void on_message(struct cweb_context *ctx, websocket_t *ws, const char *message, size_t length) {
ws->send(ws, "{\"ok\":true}", strlen("{\"ok\":true}"));
}
static void on_close(struct cweb_context *ctx, websocket_t *ws) {
}
export module_t config = {
.name = "websocket",
.author = "cweb",
.websockets = {
{"/websocket", on_open, on_message, on_close},
},
.ws_size = 1,
};
Environments
The c-web-modules framework supports multiple environments.
By default, the development environment is used, which uses HTTP. For production, the server uses HTTPS, which requires `server.crt` and `server.key` files. They can be generated using:
openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes
Alternatively, you can use the `production.sh` script, which will also build the server with the `PRODUCTION` flag.
Production
To build the server for production, use the production.sh script:
./production.sh
To build a Docker image for production, use the --docker flag:
./production.sh --docker
This will create a cweb:production Docker image that you can run with:
docker run -p 8080:8080 cweb:production
Key Structs
The following structs are central to the framework:
HTTP Request
http_request_t {
http_method_t method; // HTTP method (e.g., GET, POST)
char *path; // Request path
char *body; // Request body
int content_length; // Length of the body
struct map *headers; // HTTP headers
int websocket; // Is this a WebSocket request?
};
HTTP Response
http_response_t {
http_error_t status; // HTTP status code
struct map *headers; // Response headers
char *body; // Response body
};
WebSocket
websocket_t {
int client_fd; // Client file descriptor
int (*send)(websocket_t* ws, const char *message, size_t length);
int (*close)(websocket_t* ws);
};
Docker
Build and run the server using Docker:
docker build -t cweb .
docker run -p 8080:8080 --mount type=bind,source=$(pwd)/modules,target=/app/modules cweb
Or use Docker Compose:
docker-compose up --build
For non-volatile modules during restarts, you need to mount the modules directory:
volumes:
- ./modules:/app/modules
FAQ
Frequently asked questions about c-web-modules.
Q: How do I restart the server?
A: Use `make run` after making changes.
Q: How do I debug a failing module?
A: Check logs in the console or the HTTP response from /mgnt.