AG_Web
— Agar
HTTP/1.1 application server
#include <agar/core.h>
#include <agar/net/web.h>
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).
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_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.
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>" }
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_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).
void
WEB_SetCode
(WEB_Query
*q, const char
*code);
void
WEB_SetCompression
(WEB_Query
*q, int enable,
int level);
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_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.
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.
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.
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 "<" and ">" to
">".
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 "<" and ">" to
">".
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 "<" and ">" to
">". 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);
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 */
};
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.
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.