THE FOUNDATION

fwHttp — HTTP Server & Client

epoll edge-triggered HTTP server with zero-copy parsing. Pre-allocated connection pool. SO_REUSEPORT multi-process scaling. Plus an HTTP client with TLS and connection pooling.

What It Does

fwHttp provides both an HTTP server and an HTTP client as a single library. The server uses epoll in edge-triggered mode and a pre-allocated pool of 1024 connections, eliminating per-request allocation for connection state. SO_REUSEPORT allows multiple worker processes to each hold their own listening socket, distributing accept load across cores without a shared accept queue.

HTTP parsing on the server side is zero-copy: headers and body are null-terminated in the read buffer, matching the fwJson approach. The client side adds TLS support (via OpenSSL), connection pooling for persistent connections to known upstreams, and the ability to issue concurrent requests without blocking a thread per inflight call.

Trace Levels

fwHttp uses trace levels 100–149. See the full trace level table for all assignments across the fw-lib stack.

Core Types

// HTTP verbs — used as array index into service vector
typedef enum KhttpVerb {
    KhttpVerbGet     = 0,
    KhttpVerbPut     = 1,
    KhttpVerbPost    = 2,
    KhttpVerbDelete  = 3,
    KhttpVerbPatch   = 4,
    KhttpVerbHead    = 5,
    KhttpVerbOptions = 6,
    // ...
} KhttpVerb;

// Service definition — URL pattern + handler function
typedef struct KhttpService {
    const char*           urlPath;        // URL pattern with wildcards
    KhttpServiceRoutine   serviceRoutine; // Handler function
} KhttpService;

// Service handler signature
typedef FtNode* (*KhttpServiceRoutine)(int* httpStatusP);

Server Functions

// Start server in a background thread; returns immediately
bool fwHttpRun(KhttpServiceVector* serviceVV, unsigned short port);

// Stop server
void fwHttpStop();

// Utilities
const char* fwHttpVerbToString(KhttpVerb verb);
KhttpVerb   fwHttpVerbFromString(const char* str, int len);
const char* fwHttpStatusText(int statusCode);

Thread-Local Request State

extern __thread KhttpState fwHttpState;

// Incoming request
fwHttpState.in.verb              // HTTP method enum
fwHttpState.in.urlPath           // Request URL path
fwHttpState.in.wildcard[0..2]    // Extracted wildcard values
fwHttpState.in.uriParamV         // URI query parameters
fwHttpState.in.httpHeaderV       // HTTP headers
fwHttpState.in.payload           // Raw request body
fwHttpState.in.payloadTree       // Parsed JSON (if Content-Type: application/json)

// Outgoing response
fwHttpState.out.statusCode       // HTTP status (default 200)
fwHttpState.out.headerV          // Response headers
fwHttpState.out.contentType      // Response Content-Type

All request/response data is accessed through thread-local fwHttpState, avoiding any shared mutable state between request handler threads.

URL Wildcards

URL patterns use * as a wildcard. Matched segments are available as fwHttpState.in.wildcard[n]. Wildcards correctly handle URLs-within-URLs (e.g., entity IDs containing ://).

PatternMatchesWildcard Values
/items/*/items/123wildcard[0] = "123"
/users/*/posts/*/users/john/posts/456wildcard[0] = "john", wildcard[1] = "456"
/entities/*/entities/urn:ngsi-ld:Entity:1wildcard[0] = "urn:ngsi-ld:Entity:1"

Quick Start

#include "fwHttp/fwHttpApi.h"
#include "fwJson/kjBuilder.h"

// GET /api/v1/version
FtNode* getVersion(int* httpStatusP)
{
    FjParser*  fwJsonP   = fwHttpState.fwJsonP;
    FtNode* response = kjObject(fwJsonP, NULL);

    kjChildAdd(response, kjString(fwJsonP, "name", "myapp"));
    kjChildAdd(response, kjString(fwJsonP, "version", "1.0.0"));

    *httpStatusP = 200;
    return response;
}

// GET /api/v1/items/{itemId}
FtNode* getItem(int* httpStatusP)
{
    const char* itemId = fwHttpState.in.wildcard[0];
    FjParser*  fwJsonP = fwHttpState.fwJsonP;
    FtNode* item   = kjObject(fwJsonP, NULL);

    kjChildAdd(item, kjString(fwJsonP, "id", itemId));

    *httpStatusP = 200;
    return item;
}

static KhttpService getServices[] = {
    { "/api/v1/version",  getVersion },
    { "/api/v1/items/*",  getItem    },
    { NULL, NULL }
};

int main(void)
{
    KhttpServiceVector serviceVV[KhttpVerbMax] = { 0 };

    serviceVV[KhttpVerbGet].serviceV = getServices;
    serviceVV[KhttpVerbGet].services = 2;

    // Spawns worker thread and returns immediately
    if (!fwHttpRun(serviceVV, 8080)) {
        fprintf(stderr, "Failed to start server\n");
        return 1;
    }

    while (1)
        sleep(1);

    fwHttpStop();
    return 0;
}