.\" Copyright (c) 2018-2022 Julien Nadeau Carriere .\" All rights reserved. .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions .\" are met: .\" 1. Redistributions of source code must retain the above copyright .\" notice, this list of conditions and the following disclaimer. .\" 2. Redistributions in binary form must reproduce the above copyright .\" notice, this list of conditions and the following disclaimer in the .\" documentation and/or other materials provided with the distribution. .\" .\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR .\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED .\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE .\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, .\" INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES .\" (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR .\" SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) .\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, .\" STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING .\" IN ANY WAY OUT OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" .Dd December 21, 2022 .Dt AG_WEB 3 .Os Agar 1.7 .Sh NAME .Nm AG_Web .Nd Agar HTTP/1.1 application server .Sh SYNOPSIS .Bd -literal #include #include .Ed .Sh DESCRIPTION The .Nm interface provides the components needed to create a multiprocess, privilege-separated HTTP/1.1 application server in C. .Nm is included in the Agar-Core library if compiled with .Sq --enable-web . .Pp An HTTP application server using .Nm might be deployed as a set of instances running behind an external facing HTTP server (such as Apache httpd with mod_proxy_balancer). .Nm may also be integrated into an existing application in need of an HTTP interface. .Pp .Nm spawns (and expires) one worker process per authenticated session. Queries can be processed (and responses compressed) in parallel. .Pp .Nm handles: .Bl -bullet -compact .It Authentication and session management .It Content and language negotiation .It Parsing of HTTP requests .It Forwarding of requests to the appropriate worker .It Chunking and .Xr zlib 3 compression of worker responses .It Multiplexing Push events into text/event-stream .El .Pp The .Nm 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. .Pp The GET argument "op" specifies the method that a client wishes to invoke. Methods execute in worker processes, and are organized in modules (see .Fn WEB_RegisterModule below). .Sh INITIALIZATION .nr nS 1 .Ft "void" .Fn WEB_Init "WEB_Application *appData" "int clusterID" "int eventSource" .Pp .Ft "void" .Fn WEB_RegisterModule "WEB_Module *mod" .Pp .Ft "void" .Fn WEB_CheckSignals "void" .Pp .Ft "void" .Fn WEB_Exit "int exitCode" "const char *fmt" "..." .Pp .Ft "void" .Fn WEB_SetLanguageFn "int (*fn)(const char *langCode, void *arg)" "void *arg" .Pp .Ft "void" .Fn WEB_SetMenuFn "void (*WEB_MenuFn)(WEB_Query *q, WEB_Variable *V, void *arg)" "void *arg" .Pp .Ft "void" .Fn WEB_SetProcTitle "const char *title" "..." .Pp .Ft "void" .Fn WEB_QueryLoop "const char *hostname" "const char *port" "const WEB_SessionOps *sessOps" .Pp .nr nS 0 The .Fn WEB_Init routine initializes the .Nm library. .Fa clusterID is a number which should identify this instance uniquely in the cluster of servers. If the .Fa 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. .\" MANLINK(WEB_Application) The .Fa app structure should be partially initialized: .Bd -literal .\" SYNTAX(c) 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; .Ed .Pp .Va name is an arbitrary string identifier for the application. .Va copyright is an optional copyright notice. .Va availLangs is a NULL-terminated array of ISO-639 language codes which are valid for this application. .Va homeOp specifies the default .Nm operation to invoke after a successful login. .Va flags should be set to zero (none are currently defined). .Va destroyFn is an optional cleanup function to be run when the server terminates. .Va logFn is an optional callback to receive log messages produced by .Fn WEB_Log* (see .Sq LOGGING section). .Pp For example: .Bd -literal .\" SYNTAX(c) WEB_Application myExampleApp = { "ExampleApp", "Copyright (c) my name", { "en", "es", "fr", NULL }, /* English, Spanish, French */ "main_welcome", 0, /* flags */ NULL, /* destroy */ NULL /* log */ }; .Ed .Pp The .Fn WEB_RegisterModule function registers a new module (and invokes the module's .Fn init method). .\" MANLINK(WEB_Module) The .Fa mod argument must point to an initialized .Ft WEB_Module structure: .Bd -literal .\" SYNTAX(c) 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; .Ed .Pp The .Va name string is a short identifier and operation prefix for this module. It should not exceed .Dv WEB_OPNAME_MAX bytes in length. .Va icon is an optional icon for the module. .Va lname is the full title of the module to display to the user. .Va desc is a description of the module's operation. .Va icon , .Va lname and .Va desc may contain HTML code. .Pp All function pointers below are optional and may be set to NULL. .Pp The .Fn init function is invoked after the module has been registered (typically when the application server is first started). .Fn destroy is invoked to clean up the module's resources (typically when the application server is shutting down). .Pp .Fn sessOpen is called when a new user session is created, where .Fa sess is a pointer to newly created .Ft WEB_Session . It is a good place for a module to initialize its session variables (see .Fn 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). .Pp The .Fn sessClose routine is called when a user closes a session. .Pp .Fn indexFn points to the default method to invoke when the "op" argument contains the module name but does not map onto a specific method. .Pp If set, .Fn 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 .Fa V . .Pp .\" MANLINK(WEB_Command) The .Va commands table maps method names to a module's functions: .Bd -literal .\" SYNTAX(c) 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; .Ed .Pp .Va name is the full method name (the matching "op" argument). .Va fn is a pointer to the function implementing the method. If .Va type is not NULL, it indicates the Content-Type of the data returned by the method. For example: .Bd -literal .\" SYNTAX(c) 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, "" } }; .Ed .Pp 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: .Bd -literal .\" SYNTAX(json) { "code": -1, "error": "", "backend_version": "" } .Ed .Pp The special type "[json]" may be used if the function emits JSON content of its own. Then the following will be emitted: .Bd -literal .\" SYNTAX(json) { "lang": , , "code": , "error": "", "backend_version": "" } .Ed .Pp If the .Va 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 .Fn WEB_SetHeader or .Fn WEB_SetHeaderS . .Pp The .Va flags string defines per-method options. It may contain characters: .Bl -tag -width "`P' " .It Sq P Public method. Make accessible to both authenticated and non-authenticated clients (in the latter case, .Va q->sess will be NULL). .It Sq i Index method. Invoke by default when no specific "op" given. .El .Pp .Fn 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, .Nm invokes .Fn 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). .Pp The .Fn WEB_Exit routine immediately cleans up resources and terminates the running process returning the specified exit code and optional message string. .Pp .Fn WEB_SetLanguageFn sets a callback routine (and optional user pointer) for switching between different locales based on language preferences. The .Fa langCode argument is an ISO-639 language code. .Pp .Fn 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 .Fa V . .Pp .Fn WEB_SetProcTitle set the process title (as shown by .Xr ps 1 ) of the current worker process. If .Xr setproctitle 3 is not available, the function is a no-op. .Pp .Fn WEB_QueryLoop is the standard event loop for the application server. It listens on one or more sockets under .Fa hostname and .Fa port as well as the control socket. .Fn WEB_QueryLoop loops reading HTTP queries and forwarding requests to worker processes, spawning new workers when needed. .Fa sessOps defines the authentication module to use (see .Sq AUTHENTICATION section for details). .Sh HTTP RESPONSE HEADERS .nr nS 1 .Ft "void" .Fn WEB_SetCode "WEB_Query *q" "const char *code" .Pp .Ft "void" .Fn WEB_SetCompression "WEB_Query *q" "int enable" "int level" .Pp .Ft "void" .Fn WEB_SetHeader "WEB_Query *q" "const char *name" "const char *value" "..." .Pp .Ft "void" .Fn WEB_SetHeaderS "WEB_Query *q" "const char *name" "const char *value" .Pp .Ft "void" .Fn WEB_AppendHeader "WEB_Query *q" "const char *name" "const char *value" "..." .Pp .Ft "void" .Fn WEB_AppendHeaderS "WEB_Query *q" "const char *name" "const char *value" .Pp .Ft "WEB_Cookie *" .Fn WEB_SetCookie "WEB_Query *q" "const char *name" "const char *value" "..." .Pp .Ft "WEB_Cookie *" .Fn WEB_SetCookieS "WEB_Query *q" "const char *name" "const char *value" .Pp .Ft "WEB_Cookie *" .Fn WEB_GetCookie "WEB_Query *q" "const char *name" .Pp .Ft "void" .Fn WEB_DelCookie "WEB_Query *q" "const char *name" .Pp .nr nS 0 .Fn 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". .Pp .Fn WEB_SetCompression sets compression parameters for the response. The .Fa enable flag enables or disables compression, and .Fa level sets the .Xr zlib 3 compression level from 1 to 9 (1 = Best speed, 9 = Best compression). .Pp .Fn WEB_SetHeader sets the value of the HTTP output header .Fa name to a new .Fa value . If the header already exists, its value is updated. Otherwise, a new header is created. .Fn WEB_AppendHeader appends the given header unconditionally (without checking for duplicates). .\" MANLINK(WEB_Cookie) .Pp .Fn WEB_SetCookie sets the HTTP cookie identified by .Fa name to the given .Fa 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: .Bd -literal .\" SYNTAX(c) 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; .Ed .Pp The caller can modify any member except .Va name . .Pp .Fn WEB_GetCookie returns a pointer to the value of cookie .Fa name or NULL if no such cookie exists. .Pp .Fn WEB_DelCookie deletes the cookie identified by .Fa name . .P .Sh HTTP ARGUMENT PARSING .nr nS 1 .Ft "const char *" .Fn WEB_Get "WEB_Query *q" "const char *key" "AG_Size maxLength" .Pp .Ft "const char *" .Fn WEB_GetTrim "WEB_Query *q" "const char *key" "AG_Size maxLength" .Pp .Ft "void" .Fn WEB_Set "WEB_Query *q" "const char *key" "const char *value" "..." .Pp .Ft "void" .Fn WEB_SetS "WEB_Query *q" "const char *key" "const char *value" .Pp .Ft "const char *" .Fn WEB_GetSV "WEB_Session *sess" "const char *key" .Pp .Ft "void" .Fn WEB_SetSV "WEB_Query *q" "const char *key" "const char *value" "..." .Pp .Ft "void" .Fn WEB_SetSV_S "WEB_Query *q" "const char *key" "const char *value" .Pp .Ft "void" .Fn WEB_SetSV_ALL "const WEB_SessionOps *sessOps" "const char *user" "const char *key" "const char *value" .Pp .Ft "void" .Fn WEB_Unset "WEB_Query *q" "const char *key" .Pp .Ft "int" .Fn WEB_GetBool "WEB_Query *q" "const char *key" .Pp .Ft "int" .Fn WEB_GetInt "WEB_Query *q" "const char *key" "int *dest" .Pp .Ft "int" .Fn WEB_GetIntR "WEB_Query *q" "const char *key" "int *dest" "int min" "int max" .Pp .Ft "int" .Fn WEB_GetIntRange "WEB_Query *q" "const char *key" "int *minValue" "const char *separator" "int *maxValue" .Pp .Ft "int" .Fn WEB_GetUint "WEB_Query *q" "const char *key" "Uint *dest" .Pp .Ft "int" .Fn WEB_GetUintR "WEB_Query *q" "const char *key" "Uint *dest" "Uint min" "Uint max" .Pp .Ft "int" .Fn WEB_GetUint64 "WEB_Query *q" "const char *key" "Uint64 *dest" .Pp .Ft "int" .Fn WEB_GetSint64 "WEB_Query *q" "const char *key" "Sint64 *dest" .Pp .Ft "int" .Fn WEB_GetEnum "WEB_Query *q" "const char *key" "Uint *dest" "Uint last" .Pp .Ft "int" .Fn WEB_GetFloat "WEB_Query *q" "const char *key" "float *dest" .Pp .Ft "int" .Fn WEB_GetDouble "WEB_Query *q" "const char *key" "double *dest" .Pp .Ft "char *" .Fn WEB_EscapeURL "WEB_Query *q" "const char *url" .Pp .Ft "char *" .Fn WEB_UnescapeURL "WEB_Query *q" "const char *url" .Pp .nr nS 0 .Fn WEB_Get looks up the HTTP argument named .Fa 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). .Pp The .Fn WEB_GetTrim variant of .Fn WEB_Get implicitely removes leading and trailing spaces (characters matching .Xr isspace 3 ) from the argument value. .Pp .Fn WEB_Set modifies the in-memory value associated with argument .Fa key . If no such argument exists then one is created. .Fn WEB_Unset deletes the specified argument from memory. .Pp 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. .Fn WEB_GetSV returns the value of the given session variable or NULL if no such variable exists. .Fn WEB_SetSV sets the session variable .Fa key to .Fa value . The .Fn WEB_SetSV_ALL variant updates all session variables named .Fa key to .Fa value for every session opened by .Fa user . .Pp .Fn WEB_GetBool returns 1 if argument .Fa key exists and its value is not the empty string (""), otherwise it returns 0. .Pp The following .Fn 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. .Pp .Fn WEB_GetInt converts argument .Fa key to a signed integer, returning the result in .Fa dest . The number must lie within the range .Dv INT_MIN to .Dv INT_MAX . The .Fn WEB_GetIntR variant fails if the number is lower than .Fa min or greater than .Fa max . .Pp The .Fn WEB_GetIntRange function parses a range, specified as a string of the form "", for example "1-10" (where .Fa separator would be "-"). The first number is returned into .Fa minValue and second number into .Fa maxValue . The function returns 0 on success or -1 if the argument is missing or does not describe a valid range. .Pp .Fn WEB_GetUint converts argument .Fa key to an unsigned integer, returning the result in .Fa dest . The number must lie within the range 0 to .Dv UINT_MAX . The .Fn WEB_GetUintR variant fails if the number is lower than .Fa min or greater than .Fa max . .Pp .Fn WEB_Get[SU]int64 converts argument .Fa key to a signed or unsigned 64-bit integer, returning the result in .Fa dest . The number must lie within the range .Dv [SU]INT64_MIN to .Dv [SU]INT64_MAX . .Pp The .Fn WEB_GetEnum function converts argument .Fa key to an unsigned integer greater than 0 and less than or equal to .Fa last . .Pp .Fn WEB_GetFloat and .Fn WEB_GetDouble convert the argument to a single or double-precision floating point number and return the value in .Fa dest . .Pp The .Fn WEB_EscapeURL function turns URL-unsafe characters (per RFC1738) from .Fa url into "%02x" format and returns a newly allocated string with the result. .Fn WEB_UnescapeURL transforms all instances of "%02x" escaped characters in .Fa url back to the original character (except NUL which would be returned as '_') and returns a newly allocated string with the result. .Sh LOGGING .nr nS 1 .Ft "void" .Fn WEB_SetLogFile "const char *path" .Pp .Ft "void" .Fn WEB_Log "enum web_loglvl logLevel" "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogS "enum web_loglvl logLevel" "const char *msg" .Pp .Ft "void" .Fn WEB_LogErr "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogWarn "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogInfo "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogNotice "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogDebug "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogWorker "const char *msg" "..." .Pp .Ft "void" .Fn WEB_LogEvent "const char *msg" "..." .Pp .nr nS 0 The .Fn WEB_SetLogFile function sets an alternate destination log file (by default the application name + ".log" in the working directory). .Pp The .Fn WEB_Log and .Fn WEB_LogS functions generate a log entry with the given .Fa logLevel . That the log file is unbuffered. Log levels include: .Bd -literal .\" SYNTAX(c) 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 */ }; .Ed .Pp Alternatively, the .Fn WEB_Log shorthand routines can be used to generate a log message under the implied log level. .Sh HTTP RESPONSE OUTPUT .nr nS 1 .Ft "void" .Fn WEB_Write "WEB_Query *q" "const char *data" "AG_Size len" .Pp .Ft "void" .Fn WEB_PutC "WEB_Query *q" "char c" .Pp .Ft "void" .Fn WEB_PutS "WEB_Query *q" "const char *s" .Pp .Ft "void" .Fn WEB_Printf "WEB_Query *q" "const char *format" "..." .Pp .Ft "void" .Fn WEB_PutJSON "WEB_Query *q" "const char *key" "const char *data" "..." .Pp .Ft "void" .Fn WEB_PutJSON_S "WEB_Query *q" "const char *key" "const char *data" .Pp .Ft "void" .Fn WEB_PutJSON_NoHTML_S "WEB_Query *q" "const char *key" "const char *data" .Pp .Ft "void" .Fn WEB_OutputHTML "WEB_Query *q" "const char *template" .Pp .Ft "void" .Fn WEB_PutJSON_HTML "WEB_Query *q" "const char *key" "const char *document" .Pp .Ft "void" .Fn WEB_OutputError "WEB_Query *q" "const char *msg" .Pp .Ft "void" .Fn WEB_SetError "const char *msg" "..." .Pp .Ft "void" .Fn WEB_SetErrorS "const char *msg" .Pp .Ft "void" .Fn WEB_SetSuccess "const char *msg" "..." .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_New "const char *key" .Pp .Ft "void" .Fn WEB_VAR_Grow "WEB_Variable *v" "AG_Size newLen" .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_Set "const char *key" "const char *value" "..." .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_SetS "const char *key" "const char *value" .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_SetS_NODUP "const char *key" "char *value" .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_SetGlobal "const char *key" "const char *value" "..." .Pp .Ft "WEB_Variable *" .Fn WEB_VAR_SetGlobalS "const char *key" "const char *value" .Pp .Ft void .Fn WEB_VAR_Cat "WEB_Variable *v" "const char *value" "..." .Pp .Ft void .Fn WEB_VAR_CatS "WEB_Variable *v" "const char *value" .Pp .Ft void .Fn WEB_VAR_CatS_NODUP "WEB_Variable *v" "char *value" .Pp .Ft void .Fn WEB_VAR_CatS_NoHTML "WEB_Variable *v" "const char *value" .Pp .Ft void .Fn WEB_VAR_CatC "WEB_Variable *v" "const char c" .Pp .Ft void .Fn WEB_VAR_CatN "WEB_Variable *v" "const void *src" "AG_Size len" .Pp .Ft void .Fn WEB_VAR_CatN_NoNUL "WEB_Variable *v" "const void *src" "AG_Size len" .Pp .Ft void .Fn WEB_VAR_CatJS "WEB_Variable *v" "const char *value" .Pp .Ft void .Fn WEB_VAR_CatJS_NODUP "WEB_Variable *v" "char *value" .Pp .Ft void .Fn WEB_VAR_CatJS_NoHTML "WEB_Variable *v" "const char *value" .Pp .Ft void .Fn WEB_VAR_CatJS_NoHTML_NODUP "WEB_Variable *v" "char *value" .Pp .Ft "char *" .Fn WEB_VAR_Get "const char *key" .Pp .Ft void .Fn WEB_VAR_Wipe "const char *key" .Pp .Ft void .Fn WEB_VAR_Unset "const char *key" .Pp .Ft int .Fn WEB_VAR_Defined "const char *key" .Pp .Ft void .Fn WEB_VAR_Free "WEB_Variable *v" .Pp .nr nS 0 The following routines produce HTTP response data. Upon query completion, this data will be compressed, chunked and written back to the HTTP client. .Pp .Fn WEB_Write appends .Fa len bytes from .Fa data to the HTTP response buffer. .Fn WEB_PutC writes a single character .Fa c . .Fn WEB_PutS writes a NUL-terminated string .Fa s . .Fn WEB_Printf produces .Xr printf 3 formatted text. .Pp .Fn WEB_PutJSON produces a JSON data pair from .Fa key and .Fa data . .Fn WEB_PutJSON escapes .Fa data for double quotes, backslashes, "\\r", "\\n" and "\\t". The .Fn WEB_PutJSON_NoHTML_S variant additionally escapes "<" to "<" and ">" to ">". .Pp The .Fn WEB_OutputHTML function invokes the template engine to produce text/html output from the contents of a .Ft template file with "$variable" references substituted with the current set of .Ft WEB_Variable . The template file should be located under .Pa "WEB_PATH_HTML/