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:

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:

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

WebSocket: WS /jobs/ws?id=:uuid&since_event_id=456 (streams job_events with replay support).

Authentication

Implemented details:

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:

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:

For Arch Linux:

For MacOS:

Compile and run the server:

make
make run

Server flags:

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.