Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions CorRestState.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
#ifndef CORREST_STATE_H_
#define CORREST_STATE_H_

#include <microhttpd.h>
#include <stdbool.h>
#include <stddef.h> // NULL - came in via microhttpd.h until this header stopped including it

#include "kalloc/KAlloc.h"
#include "kjson/kjson.h"
Expand All @@ -30,7 +30,15 @@
//
typedef struct CorRestState
{
struct MHD_Connection* mhdConnection;
//
// The HTTP backend's handle for the connection this request came in on -
// an `struct MHD_Connection*` under libmicrohttpd, a `CorHttpConn*` under the
// built-in server. Opaque HERE on purpose: this header is included by ~2000
// call sites in the layers above, and naming one server's type in it would
// make every one of them depend on that server being the one in the build.
// The backend that put it here is the only code that casts it back.
//
void* connection;

// Allocator: pool-based, bulk-free after request completes
KAlloc kalloc;
Expand All @@ -49,7 +57,7 @@ typedef struct CorRestState
// Matched service
CorRestService* serviceP;

// Payload accumulation (during MHD body reads)
// Payload accumulation (during the backend's body reads)
int payloadBufSize;

// Request timing
Expand All @@ -63,6 +71,21 @@ typedef struct CorRestState
// this state; a worker runs corRestProcessRequest off the I/O thread, sets
// asyncProcessed, and resumes the connection. asyncNext links the FIFO queue.
bool asyncProcessed;

//
// ...and the SECOND thing a worker does for a request: the post-response
// phase - the deferred notifications, and releasing the arena they are built
// in - once the response is on the wire.
//
// A phase and not a second queue, because it is the same work item at a later
// moment. It is off the I/O thread for the same reason the dispatch is, and
// one reason more: a notification's @context can be one the broker HOSTS
// ITSELF, so this phase can issue a request the broker has to answer. On a
// single-threaded event loop, running it there is a deadlock that resolves
// itself as a timeout - a notification sent ten seconds late with an
// uncompacted body, and nothing in the log saying why.
//
bool asyncFinishing;
struct CorRestState* asyncNext;
} CorRestState;

Expand All @@ -74,7 +97,7 @@ typedef struct CorRestState
//
// Historically `corRest` was a plain __thread object. It is now a __thread
// POINTER (corRestP) behind the `corRest` macro, so the state can later be
// relocated off the thread (into MHD per-connection con_cls) without touching
// relocated off the thread (into the backend's per-connection slot) without touching
// the ~2000 `corRest.foo` call sites. Until a request handler binds corRestP to
// a connection's state, corRestBind() auto-binds it to a per-thread fallback
// object — making every access crash-proof and, for now, behaviourally
Expand Down
166 changes: 166 additions & 0 deletions corRestBackend.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//
// FILE corRestBackend.h
//
// AUTHOR Ken Zangelin
//
// Copyright 2026 Seamware
// SPDX-License-Identifier: Apache-2.0
//
// The seam between corRest and the HTTP server underneath it.
//
// There are two servers - libmicrohttpd and the built-in epoll one in corHttp -
// and exactly one of them is in a build (COR_HTTP_SERVER). Everything on the
// corRest side of this header is the same either way: the service table, the
// dispatch, the worker pool, the response policy. Everything on the other side
// is a socket and a callback shape.
//
// Not a public header: it is the INTERNAL contract between corRestInit.c and
// corRestBackend<Name>.c, and nothing above corRest includes it.
//
#ifndef CORREST_BACKEND_H_
#define CORREST_BACKEND_H_

#include <stdbool.h> // bool

#include "corRest/CorRestKeyValue.h" // CorRestKeyValue
#include "corRest/CorRestState.h" // CorRestState



// -----------------------------------------------------------------------------
//
// What the backend implements
//
// corRestBackendStart - listen on 'port' and serve, and RETURN.
//
// Both backends run their own threads; neither blocks the caller, because
// corRestInit's caller goes on to do other things and then parks. keyPem /
// certPem are the HTTPS server credentials, NULL for plain HTTP - a backend
// without TLS refuses rather than silently serving the port unencrypted.
//
// Returns 0, or -1 with a reason on stderr.
//
extern int corRestBackendStart(unsigned short port, int poolSize, char* keyPem, char* certPem);


//
// corRestBackendStop - stop serving; called after the worker pool has drained.
//
extern void corRestBackendStop(void);


//
// corRestBackendFinish - the request is over; run its post-response work and
// release it
//
// The backend's, because only it knows what the request state BORROWED and what
// therefore has to be given back. Binds corRestP to stateP, runs the
// post-response hook, releases the arena, frees the state, and unbinds.
//
// Runs on a worker whenever there is a pool (corRestAsyncFinish), on the
// calling thread otherwise.
//
extern void corRestBackendFinish(CorRestState* stateP);


//
// corRestBackendResume - a worker has built the response; send it
//
// Called ON THE WORKER THREAD with corRestP still bound to 'stateP', which is
// what lets a backend read corRest.out here. It must not write to the socket
// itself - that belongs to whichever thread owns the connection - and both
// backends therefore only hand the connection back to their event loop.
//
extern void corRestBackendResume(CorRestState* stateP);



// -----------------------------------------------------------------------------
//
// What corRestInit.c provides to the backend
//
// corRestHttpHeaderAdd - one request header; key/value are BORROWED, so they
// must live as long as the request does.
// corRestUriParamsParse - split + percent-decode corRest.in.urlParams into
// corRest.in.uriParamV. Destroys the buffer it parses.
// corRestUriParamAdd - one already-split, already-decoded parameter.
//
extern void corRestHttpHeaderAdd(const char* key, const char* value);
extern void corRestUriParamsParse(void);
extern void corRestUriParamAdd(char* name, char* value);


//
// corRestBodyPolicyCheck - the two answers that are decided before the body
//
// § 6.3.4: POST/PATCH/PUT to an NGSI-LD route must carry Content-Length (411,
// no payload). § 6.3.2: an announced body over the broker's cap is a 413.
// Both are known from the request line and the headers alone, which is why
// they are here and not in the dispatch: a backend that streams the body wants
// to stop reading it, and one that has it already wants to not parse it.
//
// 'clHeader' is the Content-Length header value, NULL when absent.
//
extern void corRestBodyPolicyCheck(const char* url, const char* clHeader);


//
// corRestResponseHeaderVBuild - the response headers, in the order they go out
//
// ONE implementation of the header policy - content type, Preference-Applied,
// the service routine's own headers, CORS - for both backends, because the set
// and the ORDER of these headers is compared line by line by several hundred
// functional tests. Two copies of it would be two chances to drift.
//
// Fills 'hv' and returns how many. Keys and values are borrowed from the
// request arena and from static configuration, so they last as long as the
// request state does.
//
extern int corRestResponseHeaderVBuild(CorRestKeyValue* hv, int max);

#define COR_REST_RESPONSE_HEADERS_MAX 64


//
// corRestAsyncPoolUp / corRestAsyncEnqueue - hand the request to a worker
//
// Two calls and not one, because the connection has to be SUSPENDED BETWEEN
// THEM: a worker can pick the request up and finish it before the enqueue has
// returned, and resuming a connection that was never suspended is an API
// violation in one backend and a lost response in the other.
//
// if (corRestAsyncPoolUp()) { suspend(); corRestAsyncEnqueue(stateP); return; }
// corRestProcessRequest(); // pool down (shutdown, or a server without one)
//
extern bool corRestAsyncPoolUp(void);
extern void corRestAsyncEnqueue(CorRestState* stateP);


//
// corRestAsyncFinish - hand the POST-RESPONSE phase to a worker
//
// Returns true when a worker has taken it - the caller must then not touch
// stateP again. False means there is no pool and the caller runs
// corRestBackendFinish itself.
//
// The response has already gone out, so this is not about latency: it is about
// the thread. The phase can issue an HTTP request the broker itself has to
// answer (a notification compacted with an @context the broker hosts), and a
// single-threaded event loop running it would be waiting on itself.
//
extern bool corRestAsyncFinish(CorRestState* stateP);


//
// corRestWorkerPoolStart / Stop - one worker per I/O thread
//
extern int corRestWorkerPoolStart(int workers);
extern void corRestWorkerPoolStop(void);


//
// corRestProcessRequest - the dispatch itself (public; corRestInit.h)
//
extern void corRestProcessRequest(void);

#endif // CORREST_BACKEND_H_
Loading