include/sig/http/sig_http_server.h
#define SIG_HTTP_SERVER_MODULE_NAME "http_server"
The shared HTTP/1.1 server: one per runtime, owned by the runtime module below. Other runtime modules (registered AFTER it) look it up in their create() and register the path prefixes they serve; the server listens at start only if at least one prefix was registered. One request per connection, JSON bodies, no TLS, no authentication in v1 (see request_authorized() in sig_http_server.c for the single place an auth check will go). With a sig_loop on the runtime (sig_runtime_set_loop) the server owns no thread: the listener and connections are watched by the loop and handlers run on the loop's thread. Otherwise one server thread serves connections one at a time and handlers run on it. Config string: space-separated key=value pairs. bind=<numeric IPv4> default 127.0.0.1. A bind not starting with "127." exposes the unauthenticated API to the network and logs a warning. port=<0..65535> default 8080; 0 picks an ephemeral port cors_origin=<origin> default none; sent as Access-Control-Allow- Origin; enables OPTIONS preflight (204) timeout_ms=<1..600000> default 5000; total time allowed to read a request, and per-write timeout A malformed config (unknown key, bad value) makes the runtime's start fail with SIG_ERR_INVALID_ARG when the module is created. `bind` is only checked when the server listens: a non-numeric address then fails start with SIG_ERR_INVALID_ARG (an address in use, SIG_ERR_PLATFORM), and goes unnoticed if no prefix was registered. The server answers on its own, before any handler runs: 400 (malformed), 431 (head too large), 413 (body too large), 408 (timeout), 411/415 (PUT or PATCH without Content-Length / with a non application/json Content-Type), OPTIONS (204 with cors_origin set, else 405), 404 (no registered prefix matches the path) and 500 (a handler failed). Error bodies are {"error":"<code>"}. Handlers may be called until the http_server module stops. Modules stop in reverse registration order, so that is AFTER the stop hooks of the modules that registered routes: a client must not tear down what its handler uses in stop(); free it in destroy(), which runs once every module has stopped.
typedef struct sig_http_server sig_http_server;
typedef struct {
const char *method;
const char *path;
const char *content_type;
const char *body;
size_t body_len;
} sig_http_server_request;Borrowed by the handler for the duration of the call. `path` is the full request path (the query string removed), not stripped of the prefix; `content_type` is "" if absent; `body` is not NUL-terminated.
typedef struct {
int status;
char *body;
size_t body_len;
} sig_http_server_response;Filled in by the handler: `body` (JSON text, may be NULL when body_len is 0) is sig_alloc'd by the handler and owned by the server, which sig_free()s it.
typedef sig_status_t (*sig_http_handler_fn)(void *ctx, const sig_http_server_request *req,
sig_http_server_response *resp);Returns SIG_OK with *resp filled in, or any other status for a 500. Either way the server sig_free()s any body the handler set in resp, so a handler must never free it itself (on failure, leave it set or set it to NULL).
typedef struct {
const char *path;
const char *origin;
const char *ws_key;
} sig_http_upgrade_request;A WebSocket upgrade request the server has validated and is handing off. Borrowed for the duration of the call. `path` is the request path (query removed); `origin` is "" if the client sent none; `ws_key` is the Sec-WebSocket-Key (non-empty).
typedef sig_status_t (*sig_http_upgrade_fn)(void *ctx, const sig_http_upgrade_request *req,
sig_net_conn *conn);Return SIG_OK to take ownership of conn (the server never touches it again, and writes nothing to it, not even the 101: the adopter does); any other status makes the server answer 503 {"error":"unavailable"} and close it. Bytes the client sent after the request head are discarded. Runs on the server thread; with the event-loop transport it runs on the loop's thread, the connection is non-blocking, and the server no longer watches its fd, so the adopter may register it with the loop.
const sig_runtime_module * sig_http_server_module()
sig_status_t sig_http_server_from_runtime(sig_runtime * rt, sig_http_server ** out)
| Parameter | Type |
|---|---|
rt | sig_runtime * |
out | sig_http_server ** |
The server created by the runtime's http_server module (borrowed; valid until the runtime stops). SIG_ERR_NOT_FOUND if the module is not registered, SIG_ERR_STATE if it is not yet created (register it before the caller's module).
sig_status_t sig_http_server_register(sig_http_server * s, const char * prefix, sig_http_handler_fn fn, void * ctx)
| Parameter | Type |
|---|---|
s | sig_http_server * |
prefix | const char * |
fn | sig_http_handler_fn |
ctx | void * |
Routes requests whose path starts with `prefix` (copied; must start with '/') to fn(ctx, ...). A prefix matches at a '/' boundary: "/api" matches "/api" and "/api/x" but not "/apix"; "/" matches every path. The longest matching prefix wins. Only valid before the server starts (SIG_ERR_STATE after), i.e. from a later module's create(). A prefix already registered is SIG_ERR_EXISTS.
sig_status_t sig_http_server_register_upgrade(sig_http_server * s, const char * prefix, sig_http_upgrade_fn fn, void * ctx)
| Parameter | Type |
|---|---|
s | sig_http_server * |
prefix | const char * |
fn | sig_http_upgrade_fn |
ctx | void * |
Routes WebSocket upgrade requests (GET with Upgrade: websocket and Connection: Upgrade) whose path starts with `prefix` to fn(ctx, ...), which may adopt the connection. Same prefix rules, longest match, and SIG_ERR_STATE / SIG_ERR_EXISTS as sig_http_server_register; upgrade prefixes are a separate table, so the same prefix may also be a normal route. An upgrade registration also makes the server listen. The server validates Sec-WebSocket-Version 13, a non-empty Sec-WebSocket-Key (else 400 bad_request) and the Origin before calling fn: an Origin header, if present, must equal cors_origin or cors_origin must be "*" (else 403 forbidden_origin; with no cors_origin configured any Origin is refused); no Origin header is allowed. An upgrade request on a path with no upgrade prefix is routed as a normal GET. Like every endpoint it is unauthenticated.
sig_status_t sig_http_server_get_port(sig_runtime * rt, uint16_t * out)
| Parameter | Type |
|---|---|
rt | sig_runtime * |
out | uint16_t * |
The port the server is listening on (useful with port=0). SIG_ERR_STATE if it is not listening (not started, or started with no prefix registered); SIG_ERR_NOT_FOUND if the module is not registered.