AG_WEB(3)

NAME
AG_Web - agar HTTP/1.1 application server

SYNOPSIS

    #include <agar/core.h>
    #include <agar/net/web.h>

DESCRIPTION
The AG_Web interface provides the components needed to create a multiprocess, privilege-separated HTTP/1.1 application server in C. AG_Web is included in the Agar-Core library if compiled with ‘--enable-web’.

An HTTP application server using AG_Web might be deployed as a set of instances running behind an external facing HTTP server (such as Apache httpd with mod_proxy_balancer). AG_Web may also be integrated into an existing application in need of an HTTP interface.

AG_Web spawns (and expires) one worker process per authenticated session. Queries can be processed (and responses compressed) in parallel.

AG_Web handles:
•   Authentication and session management
•   Content and language negotiation
•   Parsing of HTTP requests
•   Forwarding of requests to the appropriate worker
•   Chunking and zlib(3) compression of worker responses
•   Multiplexing Push events into text/event-stream

The AG_Web API also facilitates web development with a basic template engine and methods for input parsing and validation of URLs / query strings, JSON and FORMDATA. Forms encoded as multipart/form-data may include binary BLOBs.

The GET argument "op" specifies the method that a client wishes to invoke. Methods execute in worker processes, and are organized in modules (see WEB_RegisterModule() below).

INITIALIZATION
void WEB_Init(WEB_Application *appData, int clusterID, int eventSource)

void WEB_RegisterModule(WEB_Module *mod)

void WEB_CheckSignals(void)

void WEB_Exit(int exitCode, const char *fmt, ...)

void WEB_SetLanguageFn(int (*fn)(const char *langCode, void *arg), void *arg)

void WEB_SetMenuFn(void (*WEB_MenuFn)(WEB_Query *q, WEB_Variable *V, void *arg), void *arg)

void WEB_SetProcTitle(const char *title, ...)

void WEB_QueryLoop(const char *hostname, const char *port, const WEB_SessionOps *sessOps)

The WEB_Init() routine initializes the AG_Web library. clusterID is a number which should identify this instance uniquely in the cluster of servers. If the eventSource flag is non-zero, this instance will be able to serve requests for text/event-stream, multiplexing Push events to the client until the connection is closed. The app structure should be partially initialized:

    typedef struct web_application {
    	const char *name;			/* Description */
    	const char *copyright;			/* Copyright notice */
    	const char *availLangs[WEB_LANGS_MAX];	/* Available languages */
    	const char *homeOp;			/* Default operation */
    	Uint flags;				/* Option flags */
    	void (*destroyFn)(void);
    	void (*logFn)(enum web_loglvl, const char *s);
    	/* ... */
    } WEB_Application;

name is an arbitrary string identifier for the application. copyright is an optional copyright notice. availLangs is a NULL-terminated array of ISO-639 language codes which are valid for this application. homeOp specifies the default AG_Web operation to invoke after a successful login. flags should be set to zero (none are currently defined). destroyFn is an optional cleanup function to be run when the server terminates. logFn is an optional callback to receive log messages produced by WEB_Log*() (see ‘LOGGING’ section).

For example:

    WEB_Application myExampleApp = {
    	"ExampleApp",
    	"Copyright (c) my name",
    	{ "en", "es", "fr", NULL },  /* English, Spanish, French */
    	"main_welcome",
    	0,		/* flags */
    	NULL,		/* destroy */
    	NULL		/* log */
    };

The WEB_RegisterModule() function registers a new module (and invokes the module's init() method). The mod argument must point to an initialized WEB_Module structure:

    typedef struct web_module {
    	char *name;                     /* Short name */
    	char *icon;                     /* Icon (HTML) */
    	char *lname;                    /* Long name (HTML) */
    	char *desc;                     /* Description (HTML) */
    	int  (*init)(void *sess);       /* App initialization */
    	void (*destroy)(void);          /* App cleanup */
    	int  (*sessOpen)(void *sess);   /* Session opened */
    	void (*sessClose)(void *sess);  /* Session closed */
    	int (*indexFn)(WEB_Query *q);   /* Default op */
    	void (*menu)(WEB_Query *q,      /* Menu override */
    	             WEB_Variable *V);
    	WEB_MenuSection *menuSections;  /* Menu sections or NULL */
    	WEB_Command *commands;          /* Command map */
    } WEB_Module;

The name string is a short identifier and operation prefix for this module. It should not exceed WEB_OPNAME_MAX bytes in length. icon is an optional icon for the module. lname is the full title of the module to display to the user. desc is a description of the module's operation. icon, lname and desc may contain HTML code.

All function pointers below are optional and may be set to NULL.

The init() function is invoked after the module has been registered (typically when the application server is first started). destroy() is invoked to clean up the module's resources (typically when the application server is shutting down).

sessOpen() is called when a new user session is created, where sess is a pointer to newly created WEB_Session. It is a good place for a module to initialize its session variables (see WEB_SetSV()). On success, this function should return 0. If it returns -1, session creation is aborted (and the user will be unable to log in).

The sessClose() routine is called when a user closes a session.

indexFn() points to the default method to invoke when the "op" argument contains the module name but does not map onto a specific method.

If set, menu() will be called to render the menu entry for this module, allowing dynamically generated contents. This method is expected to write HTML code into V.

The commands table maps method names to a module's functions:

    typedef struct web_command {
    	char *name;                         /* Method name */
    	int (*fn)(void *mod, WEB_Query *q); /* Function */
    	const char *type;                   /* MIME type (or NULL) */
    } WEB_Command;

name is the full method name (the matching "op" argument). fn is a pointer to the function implementing the method. If type is not NULL, it indicates the Content-Type of the data returned by the method. For example:

    static WEB_Command mymodCommands[] = {
    	{ "mymod_hello",	mymod_hello,	"text/html",     "Pi" },
    	{ "mymod_image",	mymod_image,	"image/png",     "" },
    	{ "mymod_json",		mymod_json,	"[json]",        "" },
    	{ "mymod_status",	mymod_status,	"[json-status]", "" },
    	{ "mymod_customtype",	mymod_custtype,	NULL,            "" },
    	{ NULL,			NULL,		NULL,            "" }
    };

For a method that does not output anything other than a return value and error code, the special type "[json-status]" can be used. On success, the JSON code {"code":0} will be emitted. If the function fails and return -1, the following will be emitted:

    { "code": -1,
      "error": "<text from AG_GetError()>",
      "backend_version": "<agar version>" }

The special type "[json]" may be used if the function emits JSON content of its own. Then the following will be emitted:

    { "lang": <language code>,
      <extra JSON emitted by function>,
      "code": <return code from function>,
      "error": "<text from AG_GetError() on failure>",
      "backend_version": "<agar version on failure>" }

If the type field of a method is NULL, the function is invoked without any additional processing, and will be expected to set at least "Content-Type" using WEB_SetHeader() or WEB_SetHeaderS().

The flags string defines per-method options. It may contain characters:

‘P’ Public method. Make accessible to both authenticated and non-authenticated clients (in the latter case, q->sess will be NULL).

‘i’ Index method. Invoke by default when no specific "op" given.

WEB_CheckSignals() handles a previous SIGTERM, SIGPIPE and SIGCHLD. The SIGCHLD handler issues a control command to notify server processes that a particular worker process has terminated. Internally, AG_Web invokes WEB_CheckSignals() whenever system calls in the main server process are interrupted. Ideally, the same should be done at the application level when an interruptible system call fails with EINTR. This important for code executing under the main server process (e.g., authentication module methods). This is not needed for code running inside worker processes (e.g., module methods).

The WEB_Exit() routine immediately cleans up resources and terminates the running process returning the specified exit code and optional message string.

WEB_SetLanguageFn() sets a callback routine (and optional user pointer) for switching between different locales based on language preferences. The langCode argument is an ISO-639 language code.

WEB_SetMenuFn() sets a callback routine (and optional user pointer) for constructing the menu. It is expected to return the dynamically-generated menu HTML into V.

WEB_SetProcTitle() set the process title (as shown by ps(1)) of the current worker process. If setproctitle(3) is not available, the function is a no-op.

WEB_QueryLoop() is the standard event loop for the application server. It listens on one or more sockets under hostname and port as well as the control socket. WEB_QueryLoop() loops reading HTTP queries and forwarding requests to worker processes, spawning new workers when needed. sessOps defines the authentication module to use (see ‘AUTHENTICATION’ section for details).

HTTP RESPONSE HEADERS
void WEB_SetCode(WEB_Query *q, const char *code)

void WEB_SetCompression(WEB_Query *q, int enable, int level)

void WEB_SetHeader(WEB_Query *q, const char *name, const char *value, ...)

void WEB_SetHeaderS(WEB_Query *q, const char *name, const char *value)

void WEB_AppendHeader(WEB_Query *q, const char *name, const char *value, ...)

void WEB_AppendHeaderS(WEB_Query *q, const char *name, const char *value)

WEB_Cookie * WEB_SetCookie(WEB_Query *q, const char *name, const char *value, ...)

WEB_Cookie * WEB_SetCookieS(WEB_Query *q, const char *name, const char *value)

WEB_Cookie * WEB_GetCookie(WEB_Query *q, const char *name)

void WEB_DelCookie(WEB_Query *q, const char *name)

WEB_SetCode() sets the HTTP response code of the output. For example, "404 Not Found" or "500 Internal Server Error". When a method is successful, the default is "200 OK".

WEB_SetCompression() sets compression parameters for the response. The enable flag enables or disables compression, and level sets the zlib(3) compression level from 1 to 9 (1 = Best speed, 9 = Best compression).

WEB_SetHeader() sets the value of the HTTP output header name to a new value. If the header already exists, its value is updated. Otherwise, a new header is created. WEB_AppendHeader() appends the given header unconditionally (without checking for duplicates).

WEB_SetCookie() sets the HTTP cookie identified by name to the given value. If an error (such as overflow) occurs, it returns NULL. If the operation is successful, it returns a pointer to the following structure which can be used to change cookie attributes:

    typedef struct web_cookie {
    	char name[WEB_COOKIE_NAME_MAX];      /* Name (RO) */
    	char value[WEB_COOKIE_VALUE_MAX];    /* Value */
    	char expires[WEB_COOKIE_EXPIRE_MAX]; /* Expiration date */
    	char domain[WEB_COOKIE_DOMAIN_MAX];  /* Domain match */
    	char path[WEB_COOKIE_PATH_MAX];      /* Path attribute */
    	Uint flags;
    #define WEB_COOKIE_SECURE 0x01               /* Set Secure attribute */
    } WEB_Cookie;

The caller can modify any member except name.

WEB_GetCookie() returns a pointer to the value of cookie name or NULL if no such cookie exists.

WEB_DelCookie() deletes the cookie identified by name.

HTTP ARGUMENT PARSING
const char * WEB_Get(WEB_Query *q, const char *key, AG_Size maxLength)

const char * WEB_GetTrim(WEB_Query *q, const char *key, AG_Size maxLength)

void WEB_Set(WEB_Query *q, const char *key, const char *value, ...)

void WEB_SetS(WEB_Query *q, const char *key, const char *value)

const char * WEB_GetSV(WEB_Session *sess, const char *key)

void WEB_SetSV(WEB_Query *q, const char *key, const char *value, ...)

void WEB_SetSV_S(WEB_Query *q, const char *key, const char *value)

void WEB_SetSV_ALL(const WEB_SessionOps *sessOps, const char *user, const char *key, const char *value)

void WEB_Unset(WEB_Query *q, const char *key)

int WEB_GetBool(WEB_Query *q, const char *key)

int WEB_GetInt(WEB_Query *q, const char *key, int *dest)

int WEB_GetIntR(WEB_Query *q, const char *key, int *dest, int min, int max)

int WEB_GetIntRange(WEB_Query *q, const char *key, int *minValue, const char *separator, int *maxValue)

int WEB_GetUint(WEB_Query *q, const char *key, Uint *dest)

int WEB_GetUintR(WEB_Query *q, const char *key, Uint *dest, Uint min, Uint max)

int WEB_GetUint64(WEB_Query *q, const char *key, Uint64 *dest)

int WEB_GetSint64(WEB_Query *q, const char *key, Sint64 *dest)

int WEB_GetEnum(WEB_Query *q, const char *key, Uint *dest, Uint last)

int WEB_GetFloat(WEB_Query *q, const char *key, float *dest)

int WEB_GetDouble(WEB_Query *q, const char *key, double *dest)

char * WEB_EscapeURL(WEB_Query *q, const char *url)

char * WEB_UnescapeURL(WEB_Query *q, const char *url)

WEB_Get() looks up the HTTP argument named key and returns a pointer to the value as a NUL-terminated string. If no such argument exists, it returns NULL (with a "Missing argument" error).

The WEB_GetTrim() variant of WEB_Get() implicitely removes leading and trailing spaces (characters matching isspace(3)) from the argument value.

WEB_Set() modifies the in-memory value associated with argument key. If no such argument exists then one is created. WEB_Unset() deletes the specified argument from memory.

Session variables are key-value pairs associated with an authenticated user session. They are saved to disk and preserved across processes handling a same session. WEB_GetSV() returns the value of the given session variable or NULL if no such variable exists. WEB_SetSV() sets the session variable key to value. The WEB_SetSV_ALL() variant updates all session variables named key to value for every session opened by user.

WEB_GetBool() returns 1 if argument key exists and its value is not the empty string (""), otherwise it returns 0.

The following WEB_Get*() functions convert arguments to numerical values, returning 0 on success. If no such argument exists, if the input is invalid or the number is out of range, these functions return -1 with an error message.

WEB_GetInt() converts argument key to a signed integer, returning the result in dest. The number must lie within the range INT_MIN to INT_MAX. The WEB_GetIntR() variant fails if the number is lower than min or greater than max.

The WEB_GetIntRange() function parses a range, specified as a string of the form "<min><separator><max>", for example "1-10" (where separator would be "-"). The first number is returned into minValue and second number into maxValue. The function returns 0 on success or -1 if the argument is missing or does not describe a valid range.

WEB_GetUint() converts argument key to an unsigned integer, returning the result in dest. The number must lie within the range 0 to UINT_MAX. The WEB_GetUintR() variant fails if the number is lower than min or greater than max.

WEB_Get[SU]int64() converts argument key to a signed or unsigned 64-bit integer, returning the result in dest. The number must lie within the range [SU]INT64_MIN to [SU]INT64_MAX.

The WEB_GetEnum() function converts argument key to an unsigned integer greater than 0 and less than or equal to last.

WEB_GetFloat() and WEB_GetDouble() convert the argument to a single or double-precision floating point number and return the value in dest.

The WEB_EscapeURL() function turns URL-unsafe characters (per RFC1738) from url into "%02x" format and returns a newly allocated string with the result. WEB_UnescapeURL() transforms all instances of "%02x" escaped characters in url back to the original character (except NUL which would be returned as '_') and returns a newly allocated string with the result.

LOGGING
void WEB_SetLogFile(const char *path)

void WEB_Log(enum web_loglvl logLevel, const char *msg, ...)

void WEB_LogS(enum web_loglvl logLevel, const char *msg)

void WEB_LogErr(const char *msg, ...)

void WEB_LogWarn(const char *msg, ...)

void WEB_LogInfo(const char *msg, ...)

void WEB_LogNotice(const char *msg, ...)

void WEB_LogDebug(const char *msg, ...)

void WEB_LogWorker(const char *msg, ...)

void WEB_LogEvent(const char *msg, ...)

The WEB_SetLogFile() function sets an alternate destination log file (by default the application name + ".log" in the working directory).

The WEB_Log() and WEB_LogS() functions generate a log entry with the given logLevel. That the log file is unbuffered. Log levels include:

    enum web_loglvl {
    	WEB_LOG_EMERG,   /* General panic condition */
    	WEB_LOG_ALERT,   /* Immediate attention required */
    	WEB_LOG_CRIT,    /* Critical conditions, I/O errors */
    	WEB_LOG_ERR,     /* General errors */
    	WEB_LOG_WARNING, /* Warning messages */
    	WEB_LOG_NOTICE,  /* Condition should be handled specially */
    	WEB_LOG_INFO,    /* Informational messages */
    	WEB_LOG_DEBUG,   /* Debugging information */
    	WEB_LOG_QUERY,   /* HTTP query (e.g., GET, POST) parsing */
    	WEB_LOG_WORKER,  /* Errors specific to worker processes */
    	WEB_LOG_EVENT    /* Errors related to Push events */
    };

Alternatively, the WEB_Log<Level>() shorthand routines can be used to generate a log message under the implied log level.

HTTP RESPONSE OUTPUT
void WEB_Write(WEB_Query *q, const char *data, AG_Size len)

void WEB_PutC(WEB_Query *q, char c)

void WEB_PutS(WEB_Query *q, const char *s)

void WEB_Printf(WEB_Query *q, const char *format, ...)

void WEB_PutJSON(WEB_Query *q, const char *key, const char *data, ...)

void WEB_PutJSON_S(WEB_Query *q, const char *key, const char *data)

void WEB_PutJSON_NoHTML_S(WEB_Query *q, const char *key, const char *data)

void WEB_OutputHTML(WEB_Query *q, const char *template)

void WEB_PutJSON_HTML(WEB_Query *q, const char *key, const char *document)

void WEB_OutputError(WEB_Query *q, const char *msg)

void WEB_SetError(const char *msg, ...)

void WEB_SetErrorS(const char *msg)

void WEB_SetSuccess(const char *msg, ...)

WEB_Variable * WEB_VAR_New(const char *key)

void WEB_VAR_Grow(WEB_Variable *v, AG_Size newLen)

WEB_Variable * WEB_VAR_Set(const char *key, const char *value, ...)

WEB_Variable * WEB_VAR_SetS(const char *key, const char *value)

WEB_Variable * WEB_VAR_SetS_NODUP(const char *key, char *value)

WEB_Variable * WEB_VAR_SetGlobal(const char *key, const char *value, ...)

WEB_Variable * WEB_VAR_SetGlobalS(const char *key, const char *value)

void WEB_VAR_Cat(WEB_Variable *v, const char *value, ...)

void WEB_VAR_CatS(WEB_Variable *v, const char *value)

void WEB_VAR_CatS_NODUP(WEB_Variable *v, char *value)

void WEB_VAR_CatS_NoHTML(WEB_Variable *v, const char *value)

void WEB_VAR_CatC(WEB_Variable *v, const char c)

void WEB_VAR_CatN(WEB_Variable *v, const void *src, AG_Size len)

void WEB_VAR_CatN_NoNUL(WEB_Variable *v, const void *src, AG_Size len)

void WEB_VAR_CatJS(WEB_Variable *v, const char *value)

void WEB_VAR_CatJS_NODUP(WEB_Variable *v, char *value)

void WEB_VAR_CatJS_NoHTML(WEB_Variable *v, const char *value)

void WEB_VAR_CatJS_NoHTML_NODUP(WEB_Variable *v, char *value)

char * WEB_VAR_Get(const char *key)

void WEB_VAR_Wipe(const char *key)

void WEB_VAR_Unset(const char *key)

int WEB_VAR_Defined(const char *key)

void WEB_VAR_Free(WEB_Variable *v)

The following routines produce HTTP response data. Upon query completion, this data will be compressed, chunked and written back to the HTTP client.

WEB_Write() appends len bytes from data to the HTTP response buffer. WEB_PutC() writes a single character c. WEB_PutS() writes a NUL-terminated string s. WEB_Printf() produces printf(3) formatted text.

WEB_PutJSON() produces a JSON data pair from key and data. WEB_PutJSON() escapes data for double quotes, backslashes, "\r", "\n" and "\t". The WEB_PutJSON_NoHTML_S() variant additionally escapes "<" to "&lt;" and ">" to "&gt;".

The WEB_OutputHTML() function invokes the template engine to produce text/html output from the contents of a template file with "$variable" references substituted with the current set of WEB_Variable. The template file should be located under WEB_PATH_HTML/<template>.html.<lang>, where lang is the ISO-639 language code for the current session. If no such template file exists, it fails and returns -1.

The WEB_PutJSON_HTML() function invokes the template engine to produce JSON-encapsulated text/html output from template and the current set of WEB_Variable. If no such template file exists, it fails and returns -1.

WEB_OutputError() outputs a complete text/html document with a body displaying error message msg. WEB_SetError() sets the $_error variable to contain a dismissible HTML error message. WEB_SetSuccess() sets the $_error variable to contain a dismissible HTML "success" message.

The WEB_Variable structure represents a variable holding a C string. In template files, variables are referenced as "$variable". Variable values are typically set by a method handler prior to invoking WEB_OutputHTML(). Variables are linked to the current WEB_Query, except for globals which remain persistent across queries.

    typedef struct web_variable {
    	char	 key[WEB_VAR_NAME_MAX]; /* Name ("\0" = anonymous) */
    	char	*value;                 /* Value (C string) */
    	AG_Size	 len;                   /* Content length (characters) */
    	AG_Size	 bufSize;               /* Buffer size */
    	int	 global;                /* Persistent across queries */
    	AG_TAILQ_ENTRY(web_variable) vars;
    } WEB_Variable;

WEB_VAR_New() returns a pointer to a newly allocated WEB_Variable of an undefined value. If the key argument is NULL, it returns an anonymous variable which must be freed explicitely by the caller after use.

WEB_VAR_Grow() pre-allocates up to newLen bytes for the value of v.

WEB_VAR_Set() sets the value of variable key to value. If no such variable exists then a new one is created.

The WEB_VAR_SetS_NODUP() variant accepts a pointer to user memory which must remain accessible and valid for as long as the variable is in use.

The scope of WEB_Variable variables is limited to the current WEB_Query. However, global variables which will remain persistent across queries can be declared using WEB_VAR_SetGlobal(). Since globals are allocated once in the parent process, globals can be shared between processes without extra memory usage.

The WEB_VAR_Cat() and WEB_VAR_CatS() routines append a string to an existing variable. The WEB_VAR_CatS_NODUP() variant frees value after appending its contents. The WEB_VAR_CatS_NoHTML() variant escapes "<" to "&lt;" and ">" to "&gt;".

WEB_VAR_CatC() appends a single character c to the value of variable v.

WEB_VAR_CatN() grows the value of v by len bytes, performs memcpy(3) and NUL-terminates the result (the WEB_VAR_CatN_NoNUL() variant doesn't).

WEB_VAR_CatJS() appends value to JSON data in v, escaping any backslash and double quote characters. The WEB_VAR_CatJS_NoHTML() variant also escapes "<" to "&lt;" and ">" to "&gt;". The WEB_VAR_CatJS_NODUP() and WEB_VAR_CatJS_NoHTML_NODUP() variants both free s after concatenation.

WEB_VAR_Get() looks for a variable key and returns a pointer to its value. If no such variable is defined, it returns NULL.

WEB_VAR_Wipe() trivially overwrites the in-memory value of the variable.

WEB_VAR_Unset() deletes and frees the variable named key, if it exists.

WEB_VAR_Defined() returns 1 if the given variable exists, otherwise 0.

WEB_VAR_Free() frees all resources allocated by an anonymous variable v. It is used internally by WEB_VAR_Unset() and called automatically on all variables after the WEB_Query has been processed.

This example sets variables "username" and "password" and generates HTML using the "login_form" template. Instances of "$username" and "$password" in login_form will be substituted for "nobody" and "test1234".

    	WEB_VAR_SetS("username", "nobody");
    	WEB_VAR_SetS("password", "test1234");
    	WEB_OutputHTML("login_form");
    	WEB_VAR_Wipe("password");

Anonymous variables must be freed explicitely by the caller:

    	WEB_VAR *v;
    	v = WEB_VAR_SetS(NULL, "Hello, ");      /* Anonymous variable */
    	WEB_VAR_CatS(v, "world!");
    	WEB_VAR_Free(v);

AUTHENTICATION
The sessOps argument passed to WEB_QueryLoop() sets the effective authentication module. The argument must point to an initialized WEB_SessionOps structure:

    typedef struct web_session_ops {
    	const char *name;             /* Session class name */
    	AG_Size size;                 /* Structure size */
    	Uint flags;
    #define WEB_SESSION_PREFORK_AUTH 0x01 /* Call auth() before fork() */
    	time_t sessTimeout;           /* Session inactivity (s) */
    	time_t workerTimeout;         /* Worker inactivity (s) */
    	void (*init)(void *sess);
    	void (*destroy)(void *sess);
    	int  (*load)(void *sess, AG_DataSource *);
    	void (*save)(void *sess, AG_DataSource *);
    	int  (*auth)(void *sess, const char *user, const char *pass);
    	WEB_CommandPreAuth preAuthCmds[10];
    	int  (*sessOpen)(void *sess, const char *user);
    	void (*sessRestored)(void *sess, const char *user);
    	void (*sessClose)(void *sess);
    	void (*sessExpired)(void *sess);
    	void (*beginFrontQuery)(WEB_Query *q, const char *op);
    	void (*loginPage)(WEB_Query *q);
    	void (*logout)(WEB_Query *q);
    	void (*addSelectFDs)(void *sess, fd_set *rd, fd_set *wr, int *max);
    	void (*procSelectFDs)(void *sess, fd_set *rd, fd_set *wr);
    } WEB_SessionOps;

The name field is a string identifier for the authentication module. size is the size in bytes of the structure describing a session instance (which may be a WEB_Session structure or a user-defined structure derived from it). Currently the only flags option is WEB_SESSION_PREFORK_AUTH. If this is set, the auth() method will run in the parent process. Otherwise, a worker process will be spawned first with fork(2), and the auth() code will execute in the worker process. Pre-fork auth is best for fast, local file-based authentication methods, and auth() running in the worker process is best for network-based auth methods.

sessTimeout sets the session inactivity timeout in seconds. workerTimeout sess the worker process inactivity timeout in seconds.

init() initializes a the session instance structure. destroy() releases resources allocated by a session instance. load() and save() serialize the session instance structure to machine-independent format.

The auth() operation verifies the given username and password. On failure it should return -1 and set an error message with AG_SetError(3). It is expected to return 0 on success. If the WEB_SESSION_PREFORK_AUTH option is set, the operation will run in the parent server process (best for fast, local auth methods). Otherwise, it will run in a worker process which will terminate should authentication fail (best for network-bound auth methods).

The following preAuthCmds[] array maps the URL-provided "op" onto a method which will execute in the parent server process (as opposed to running inside a worker process). This is useful for pre-auth operations such as generating CAPTCHAs, processing POSTDATA from a user-submitted registration form, e-mail or mobile verification routines, and handling of password recovery requests.

The sessOpen() method is invoked after successful authentication (the s argument will point to the newly allocated session instance structure). sessRestored() is invoked whenever a previously expired worker process restarts, after the saved session state has been successfully recovered from disk. sessClose() is called when a session terminates and is about to be deleted from disk. sessExpired() is called whenever a session expires due to its inactivity timeout.

The beginFrontQuery() routine is invoked as a prologue to any of the preAuthCmds[] methods. It is useful for initializing common WEB_Variable elements such as $_user and $_theme (see ‘HTTP RESPONSE OUTPUT’ section).

loginPage() returns text/html content to show unauthenticated users (typically a login form). The logout() operation is invoked when a user logs out. It is expected to clear the session ID from the "sess" cookie.

Authentication modules which use a polling mechanism such as select(2) should implement addSelectFDs(). This method registers extra file descriptors to be watched for read/write conditions. procSelectFDs() is invoked to process a read or write condition on a watched file descriptor.

The code below illustrates a basic authentication module. It declares a session structure derived from WEB_Session. It also provides a backend for a registration form (regChallenge, regFinish and regConfirm).

    /* Per session data */
    typedef struct mySiteSession {
    	struct web_session _inherit;  /* WEB_Session->MySiteSession */
    	char username[32];            /* Authenticated username */
    	time_t lastLogin;             /* Last login */
    	/* ... */
    } MySiteSession;
    static void
    Init(void *pSess)
    {
    	MySiteSession *S = pSess;
    	S->username[0] = '\0';
    	S->lastLogin = 0;
    }
    static void
    Load(void *pSess, AG_DataSource *ds)
    {
    	MySiteSession *S = pSess;
    	S->lastLogin = (time_t)AG_ReadUint64(ds);
    }
    static void
    Save(void *pSess, AG_DataSource *ds)
    {
    	MySiteSession *S = pSess;
    	AG_WriteUint64(ds, S->lastLogin);
    }
    static int
    Auth(void *pSess, const char *user, const char *password)
    {
    	return AuthSuccessful(user, password) ? 0 : -1;
    }
    static void
    AuthRegChallenge(WEB_Query *q)
    {
    	GenerateCaptchaGif();
    	WEB_Write(q, captchaGif, captchaGifSize);
    }
    static void
    AuthRegFinish(WEB_Query *q)
    {
    	/* Process POSTDATA from registration form and create account */
    }
    static void
    AuthRegConfirm(WEB_Query *q)
    {
    	/* Complete e-mail or mobile verification */
    }
    static int
    SessOpen(void *pSess, const char *username)
    {
    	MySiteSession *S = pSess;
    	time_t t;
    	Strlcpy(S->username, username, sizeof(S->username));
    	time(&t);
    	S->lastLogin = t;
    	WEB_LogInfo("User %s logged in", username);
    	return (0);
    }
    static void
    SessRestored(void *pSess, const char *username)
    {
    	MySiteSession *S = pSess;
    	WEB_Session *WS = pSess;
    	Strlcpy(S->username, username, sizeof(S->username));
    	WEB_LogInfo("User %s recovered session %s", username, WS->id);
    }
    static void
    SessClose(void *pSess)
    {
    	MySiteSession *S = pSess;
    	WEB_LogInfo("User %s logged out", S->username);
    }
    static void
    BeginPreAuthCmd(WEB_Query *q, const char *op)
    {
    	/* Set common variables for all preAuthCmds[] methods */
    	WEB_VAR_SetS("_admin", "webmaster@example.com");
    	WEB_VAR_SetS("_user", "");
    	WEB_VAR_SetS("_theme", "Default");
    	WEB_VAR_SetS("_modules",
    	    "<li><a href='/'>Sign in</a></li>"
    	    "<li><a href='/register.html'>Create account</a></li>");
    }
    static void
    LoginPage(WEB_Query *q)
    {
    	const char *user = WEB_Get(q, "username", 32);
    	const char *pass = WEB_Get(q, "password", 32);
    	const char *op = WEB_Get(q, "op", WEB_OPNAME_MAX);
    	WEB_VAR_SetS("login_username", (user) ? user : "");
    	WEB_VAR_SetS("op", (op) ? op : "main_index");
    	WEB_SetCode(q, "200 OK");
    	WEB_SetHeaderS(q, "Vary", "accept-language,Accept-Encoding,"
    	                          "User-Agent");
    	WEB_SetHeaderS(q, "Last-Modified", q->date);
    	WEB_SetHeaderS(q, "Content-Type", "text/html; charset=utf8");
    	WEB_SetHeaderS(q, "Content-Language", q->lang);
    	WEB_SetHeaderS(q, "Cache-Control", "no-cache, no-store, "
    	                                   "must-revalidate");
    	WEB_SetHeaderS(q, "Expires", "0");
    	WEB_OutputHTML(q, "loginPage");
    }
    static void
    Logout(WEB_Query *q)
    {
    	WEB_Cookie *ck;
    	/* Clear the session ID cookie */
    	ck = WEB_SetCookieS(q, "sess", "");
    	ck->path[0] = '/';
    	ck->path[1] = '\0';
    	WEB_OutputHTML(q, "logoutPage");
    }
    WEB_SessionOps mySiteSessionOps = {
    	"My Site's Auth Module",
    	sizeof(MySiteSession),
    	WEB_SESSION_PREFORK_AUTH, /* Invoke auth() before fork */
    	7*24*60*60,               /* Session inactivity timeout (s) */
    	1*60,                     /* Worker inactivity timeout (s) */
    	Init,
    	NULL,                     /* destroy */
    	Load,
    	Save,
    	Auth,
    	{
    		{ "regChallenge", AuthRegChallenge, "image/gif" },
    		{ "regFinish",    AuthRegFinish,    "text/html" },
    		{ "regConfirm",   AuthRegConfirm,   "text/html" },
    		/*
    		 * Declare more methods to handle password recovery
    		 * and other administrative functions.
    		 */
    		{ NULL,           NULL,             NULL        }
    	},
    	SessOpen,
    	SessRestored,
    	SessClose,
    	NULL,             /* sessExpired */
    	BeginPreAuthCmd,
    	LoginPage,
    	Logout,
    	NULL,             /* addSelectFDs */
    	NULL              /* procSelectFDs */
    };

PUSH EVENTS
int WEB_PostEvent(const char *match, WEB_EventFilterFn filterFn, const void *filterFnArg, const char *type, const char *data, ...)

int WEB_PostEventS(const char *match, WEB_EventFilterFn filterFn, const void *filterFnArg, const char *type, const char *data)

WEB_PostEvent() generates a Server-Sent (Push) Event. The match argument indicates which of the running event listeners should receive the event:

*   All active event sources.
username
    All sessions by the given user.
L=lang
    All sessions in specified language.
S=id
    The session with the given session ID.
NULL
    Based on return value of filterFn.

A custom event filter can be used by passing NULL to match and having filterFn point to a compare function of the form:

    typedef int (*WEB_EventFilterFn)(char *sessID, char *user,
                                     char *langCode, const void *arg);

The compare function will be invoked for each running session. If its returns 0, the event will be forwarded to the associated event listener. filterFnArg is an optional user pointer (passed to arg of the compare function). type and data specify the contents of the Push event. AG_Web automatically adds and increments the "id" field. It also generates "ping" events at regular intervals.

SEE ALSO
AG_Intro(3)

HISTORY
The AG_Web interface is based on libpercgi, which was developed over at Csoft.net Hosting (https://csoft.net/) in 2003 and originally used CGI/FastCGI. It was rewritten to become a standalone framework and finally integrated in Agar 1.5.1 as a component of ag_core. It was moved to a separate library ag_net in Agar 1.6.0.