forked from Telodendria/Telodendria
Added some more header documentation.
This commit is contained in:
parent
95cb14213f
commit
24a03ba126
10 changed files with 1022 additions and 171 deletions
4
TODO.txt
4
TODO.txt
|
@ -21,7 +21,7 @@ Milestone: v0.3.0
|
||||||
[x] Refactor TelodendriaConfig to just Config
|
[x] Refactor TelodendriaConfig to just Config
|
||||||
|
|
||||||
[ ] Documentation
|
[ ] Documentation
|
||||||
[ ] Array
|
[x] Array
|
||||||
[ ] Io
|
[ ] Io
|
||||||
[ ] Stream
|
[ ] Stream
|
||||||
[ ] Tls
|
[ ] Tls
|
||||||
|
@ -34,7 +34,7 @@ Milestone: v0.3.0
|
||||||
[ ] send-patch
|
[ ] send-patch
|
||||||
[ ] Log
|
[ ] Log
|
||||||
[ ] TelodendriaConfig -> Config
|
[ ] TelodendriaConfig -> Config
|
||||||
[ ] HashMap
|
[x] HashMap
|
||||||
[ ] HttpRouter
|
[ ] HttpRouter
|
||||||
[ ] Str
|
[ ] Str
|
||||||
[ ] HeaderParser
|
[ ] HeaderParser
|
||||||
|
|
|
@ -25,23 +25,74 @@
|
||||||
#ifndef TELODENDRIA_BASE64_H
|
#ifndef TELODENDRIA_BASE64_H
|
||||||
#define TELODENDRIA_BASE64_H
|
#define TELODENDRIA_BASE64_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Base64
|
||||||
|
* @Nd A simple base64 encoder/decoder with unpadded base64 support.
|
||||||
|
* @Dd September 30 2022
|
||||||
|
* @Xr Sha2
|
||||||
|
*
|
||||||
|
* This is an efficient yet simple base64 encoding and decoding API
|
||||||
|
* that supports regular base64, as well as the Matrix specification's
|
||||||
|
* extension to base64, called ``unpadded base64.'' This API provides
|
||||||
|
* the ability to convert between the two, instead of just implementing
|
||||||
|
* unpadded base64.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function computes the amount of bytes needed to store a message
|
||||||
|
* of the specified number of bytes as base64.
|
||||||
|
*/
|
||||||
extern size_t
|
extern size_t
|
||||||
Base64EncodedSize(size_t);
|
Base64EncodedSize(size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function computes the amount of bytes needed to store a decoded
|
||||||
|
* representation of the encoded message. It takes a pointer to the
|
||||||
|
* encoded string because it must read a few bytes off the end in order
|
||||||
|
* to accurately compute the size.
|
||||||
|
*/
|
||||||
extern size_t
|
extern size_t
|
||||||
Base64DecodedSize(const char *, size_t);
|
Base64DecodedSize(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encode the specified number of bytes from the specified buffer as
|
||||||
|
* base64. This function returns a string on the heap that should be
|
||||||
|
* freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* or NULL if a memory allocation error ocurred.
|
||||||
|
*/
|
||||||
extern char *
|
extern char *
|
||||||
Base64Encode(const char *, size_t);
|
Base64Encode(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode the specified number of bytes from the specified buffer of
|
||||||
|
* base64. This function returns a string on the heap that should be
|
||||||
|
* freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* or NULL if a memory allocation error occured.
|
||||||
|
*/
|
||||||
extern char *
|
extern char *
|
||||||
Base64Decode(const char *, size_t);
|
Base64Decode(const char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the padding from a specified base64 string. This function
|
||||||
|
* modifies the specified string in place. It thus has no return value
|
||||||
|
* because it cannot fail. If the passed pointer is invalid, the
|
||||||
|
* behavior is undefined.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
Base64Unpad(char *, size_t);
|
Base64Unpad(char *, size_t);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add padding to an unpadded base64 string. This function takes a
|
||||||
|
* pointer to a pointer because it may be necessary to grow the memory
|
||||||
|
* allocated to the string. This function returns a boolean value
|
||||||
|
* indicating whether the pad operation was successful. In practice,
|
||||||
|
* this means it will only fail if a bigger string is necessary, but it
|
||||||
|
* could not be automatically allocated on the heap.
|
||||||
|
*/
|
||||||
extern int
|
extern int
|
||||||
Base64Pad(char **, size_t);
|
Base64Pad(char **, size_t);
|
||||||
|
|
||||||
|
|
|
@ -25,15 +25,71 @@
|
||||||
#ifndef TELODENDRIA_CANONICALJSON_H
|
#ifndef TELODENDRIA_CANONICALJSON_H
|
||||||
#define TELODENDRIA_CANONICALJSON_H
|
#define TELODENDRIA_CANONICALJSON_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm CanonicalJson
|
||||||
|
* @Nd An extension of the JSON API that produces the Matrix spec's canonical JSON.
|
||||||
|
* @Dd November 30 2022
|
||||||
|
* @Xr Json
|
||||||
|
*
|
||||||
|
* This API is an extension of
|
||||||
|
* .Xr json 3
|
||||||
|
* that is specifically designed to produce the Matrix specification's
|
||||||
|
* "canonical" JSON.
|
||||||
|
* .Pp
|
||||||
|
* Canonical JSON is defined as JSON that:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* Does not have any unecessary whitespace.
|
||||||
|
* .It
|
||||||
|
* Has all object key lexicographically sorted.
|
||||||
|
* .It
|
||||||
|
* Does not contain any floating point numerical values.
|
||||||
|
* .El
|
||||||
|
* .Pp
|
||||||
|
* The regular JSON encoder has no such rules, because normally they
|
||||||
|
* are not needed. However, Canonical JSON is needed in some cases to
|
||||||
|
* consistently sign JSON objects.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
CanonicalJsonEncode(HashMap *, Stream *);
|
* Encode a JSON object following the rules of Canonical JSON. See the
|
||||||
|
* documentation for
|
||||||
|
* .Fn JsonEncode ,
|
||||||
|
* documented in
|
||||||
|
* .Xr Json 3
|
||||||
|
* for more details on how JSON encoding operates. This function exists
|
||||||
|
* as an alternative to
|
||||||
|
* .Fn JsonEncode ,
|
||||||
|
* but should not be preferred to it in most circumstances. It is a lot
|
||||||
|
* more costly, as it must lexicographically sort all keys and strip
|
||||||
|
* out float values. If at all possible, use
|
||||||
|
* .Fn JsonEncode ,
|
||||||
|
* because it is much cheaper both in terms of memory and CPU time.
|
||||||
|
* .Pp
|
||||||
|
* This function returns a boolean value indicating whether or not the
|
||||||
|
* operation was sucessful. This function will fail only if NULL was
|
||||||
|
* given for any parameter. Otherwise, if an invalid pointer is given,
|
||||||
|
* undefined behavior results.
|
||||||
|
*/
|
||||||
|
extern int CanonicalJsonEncode(HashMap *, Stream *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
CanonicalJsonEncodeToString(HashMap *);
|
* This function encodes a JSON object to a string.
|
||||||
|
* .Xr Json 3
|
||||||
|
* doesn't have any way to send JSON to a string, because there's
|
||||||
|
* absolutely no reason to handle JSON strings in most cases. However,
|
||||||
|
* the sole reason Canonical JSON exists is so that JSON objects can
|
||||||
|
* be signed in a consisten way. Thus, it is likely you need a string
|
||||||
|
* to pass to the signing function.
|
||||||
|
* .Pp
|
||||||
|
* This function returns a C string containing the canonical JSON
|
||||||
|
* representation of the given object, or NULL if the encoding failed.
|
||||||
|
*/
|
||||||
|
extern char * CanonicalJsonEncodeToString(HashMap *);
|
||||||
|
|
||||||
#endif /* TELODENDRIA_CANONICALJSON_H */
|
#endif /* TELODENDRIA_CANONICALJSON_H */
|
||||||
|
|
|
@ -24,25 +24,115 @@
|
||||||
#ifndef TELODENDRIA_CRON_H
|
#ifndef TELODENDRIA_CRON_H
|
||||||
#define TELODENDRIA_CRON_H
|
#define TELODENDRIA_CRON_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Cron
|
||||||
|
* @Nd Basic periodic job scheduler.
|
||||||
|
* @Dd December 24 2022
|
||||||
|
*
|
||||||
|
* This is an extremely basic job scheduler. So basic, in fact, that
|
||||||
|
* it currently runs all jobs on a single thread, which means that it
|
||||||
|
* is intended for short-lived jobs. In the future, it might be
|
||||||
|
* extended to support a one-thread-per-job model, but for now, jobs
|
||||||
|
* should take into consideration the fact that they are sharing their
|
||||||
|
* thread, so they should not be long-running.
|
||||||
|
* .Pp
|
||||||
|
* .Nm
|
||||||
|
* works by ``ticking'' at an interval defined by the caller of
|
||||||
|
* .Fn CronCreate .
|
||||||
|
* At each tick, all the registered jobs are queried, and if they are
|
||||||
|
* due to run again, their function is executed. As much as possible,
|
||||||
|
* .Nm
|
||||||
|
* tries to tick at constant intervals, however it is possible that one
|
||||||
|
* or more jobs may overrun the tick duration. If this happens,
|
||||||
|
* .Nm
|
||||||
|
* ticks again immediately after all the jobs for the previous tick
|
||||||
|
* have completed. This is in an effort to compensate for lost time,
|
||||||
|
* however it is important to note that when jobs overrun the tick
|
||||||
|
* interval, the interval is pushed back by the amount that it was
|
||||||
|
* overrun. Because of this,
|
||||||
|
* .Nm
|
||||||
|
* is best suited for scheduling jobs that should happen
|
||||||
|
* ``aproximately'' every so often; it is not a real-time scheduler
|
||||||
|
* by any means.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All functions defined here operate on a structure opaque to the
|
||||||
|
* caller.
|
||||||
|
*/
|
||||||
typedef struct Cron Cron;
|
typedef struct Cron Cron;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A job function is a function that takes a void pointer and returns
|
||||||
|
* nothing. The pointer is passed when the job is scheduled, and
|
||||||
|
* is expected to remain valid until the job is no longer registered.
|
||||||
|
* The pointer is passed each time the job executes.
|
||||||
|
*/
|
||||||
typedef void (JobFunc) (void *);
|
typedef void (JobFunc) (void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new
|
||||||
|
* .Nm
|
||||||
|
* object that all other functions operate on. Like most of the other
|
||||||
|
* APIs in this project, it must be freed with
|
||||||
|
* .Fn CronFree
|
||||||
|
* when it is no longer needed.
|
||||||
|
* .Pp
|
||||||
|
* This function takes the tick interval in milliseconds.
|
||||||
|
*/
|
||||||
extern Cron *
|
extern Cron *
|
||||||
CronCreate(unsigned long);
|
CronCreate(unsigned long);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a one-off job to be executed only at the next tick, and
|
||||||
|
* then immediately discarded. This is useful for scheduling tasks that
|
||||||
|
* only have to happen once, or very infrequently depending on
|
||||||
|
* conditions other than the current time, but don't have to happen
|
||||||
|
* immediately. The caller simply indicates that it wishes for the task
|
||||||
|
* to execute at some time in the future. How far into the future this
|
||||||
|
* practically ends up being is determined by how long it takes for
|
||||||
|
* other registered jobs to finish, and what the tick interval is.
|
||||||
|
* .Pp
|
||||||
|
* This function takes a job function and a pointer to pass to that
|
||||||
|
* function when it is executed.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
CronOnce(Cron *, JobFunc *, void *);
|
CronOnce(Cron *, JobFunc *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Schedule a repetitive task to be executed at aproximately the given
|
||||||
|
* interval. As stated above, this is as fuzzy interval; depending on
|
||||||
|
* the jobs being run and the tick interval, tasks may not execute at
|
||||||
|
* exactly the scheduled time, but they will eventually execute.
|
||||||
|
* .Pp
|
||||||
|
* This function takes an interval in milliseconds, a job function,
|
||||||
|
* and a pointer to pass to that function when it is executed.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
CronEvery(Cron *, unsigned long, JobFunc *, void *);
|
CronEvery(Cron *, unsigned long, JobFunc *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start ticking the clock and executing registered jobs.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
CronStart(Cron *);
|
CronStart(Cron *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop ticking the clock. Jobs that are already executing will
|
||||||
|
* continue to execute, but when they are finished, no new jobs will
|
||||||
|
* be executed until
|
||||||
|
* .Fn CronStart
|
||||||
|
* is called.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
CronStop(Cron *);
|
CronStop(Cron *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Discard all job references and free all memory associated with the
|
||||||
|
* given
|
||||||
|
* .Nm Cron
|
||||||
|
* instance.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
CronFree(Cron *);
|
CronFree(Cron *);
|
||||||
|
|
||||||
|
|
146
src/include/Db.h
146
src/include/Db.h
|
@ -24,48 +24,146 @@
|
||||||
#ifndef TELODENDRIA_DB_H
|
#ifndef TELODENDRIA_DB_H
|
||||||
#define TELODENDRIA_DB_H
|
#define TELODENDRIA_DB_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Db
|
||||||
|
* @Nd A minimal flat-file database with mutex locking and cache.
|
||||||
|
* @Dd April 27 2023
|
||||||
|
* @Xr Json
|
||||||
|
*
|
||||||
|
* Telodendria operates on a flat-file database instead of a
|
||||||
|
* traditional relational database. This greatly simplifies the
|
||||||
|
* persistent storage code, and creates a relatively basic API,
|
||||||
|
* described here.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
#include <Array.h>
|
#include <Array.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All functions in this API operate on a database structure that is
|
||||||
|
* opaque to the caller.
|
||||||
|
*/
|
||||||
typedef struct Db Db;
|
typedef struct Db Db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When an object is locked, a reference is returned. This reference
|
||||||
|
* is owned by the current thread, and the database is inaccessible to
|
||||||
|
* other threads until all references have been returned to the
|
||||||
|
* database.
|
||||||
|
* .Pp
|
||||||
|
* This reference is opaque, but can be manipulated by the functions
|
||||||
|
* defined here.
|
||||||
|
*/
|
||||||
typedef struct DbRef DbRef;
|
typedef struct DbRef DbRef;
|
||||||
|
|
||||||
extern Db *
|
/**
|
||||||
DbOpen(char *, size_t);
|
* Open a data directory. This function takes a path to open, and a
|
||||||
|
* cache size in bytes. If the cache size is 0, then caching is
|
||||||
|
* disabled and objects are loaded off the disk every time they are
|
||||||
|
* locked. Otherwise, objects are stored in the cache, and they are
|
||||||
|
* evicted in a least-recently-used manner.
|
||||||
|
*/
|
||||||
|
extern Db * DbOpen(char *, size_t);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
DbMaxCacheSet(Db *, size_t);
|
* Close the database. This function will flush anything in the cache
|
||||||
|
* to the disk, and then close the data directory. It assumes that
|
||||||
|
* all references have been unlocked. If a reference has not been
|
||||||
|
* unlocked, undefined behavior results.
|
||||||
|
*/
|
||||||
|
extern void DbClose(Db *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
DbClose(Db *);
|
* Set the maximum cache size allowed before
|
||||||
|
* .Nm
|
||||||
|
* starts evicting old objects. If this is set to 0, everything in the
|
||||||
|
* cache is immediately evicted and caching is disabled. If the
|
||||||
|
* database was opened with a cache size of 0, setting this will
|
||||||
|
* initialize the cache, and subsequent calls to
|
||||||
|
* .Fn DbLock
|
||||||
|
* will begin caching objects.
|
||||||
|
*/
|
||||||
|
extern void DbMaxCacheSet(Db *, size_t);
|
||||||
|
|
||||||
extern DbRef *
|
/**
|
||||||
DbCreate(Db *, size_t,...);
|
* Create a new object in the database with the specified name. This
|
||||||
|
* function will fail if the object already exists in the database. It
|
||||||
|
* takes a variable number of C strings, with the exact number being
|
||||||
|
* specified by the second parameter. These C strings are used to
|
||||||
|
* generate a filesystem path at which to store the object. These paths
|
||||||
|
* ensure each object is uniquely identifiable, and provides semantic
|
||||||
|
* meaning to an object.
|
||||||
|
*/
|
||||||
|
extern DbRef * DbCreate(Db *, size_t,...);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
DbDelete(Db *, size_t,...);
|
* Lock an existing object in the database. This function will fail
|
||||||
|
* if the object does not exist. It takes a variable number of C
|
||||||
|
* strings, with the exact number being specified by the second
|
||||||
|
* parameter. These C strings are used to generate the filesystem path
|
||||||
|
* at which to load the object. These paths ensure each object is
|
||||||
|
* uniquely identifiable, and provides semantic meaning to an object.
|
||||||
|
*/
|
||||||
|
extern DbRef * DbLock(Db *, size_t,...);
|
||||||
|
|
||||||
extern DbRef *
|
/**
|
||||||
DbLock(Db *, size_t,...);
|
* Immediately and permanently remove an object from the database.
|
||||||
|
* This function assumes the object is not locked, otherwise undefined
|
||||||
|
* behavior will result.
|
||||||
|
*/
|
||||||
|
extern int DbDelete(Db *, size_t,...);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
DbUnlock(Db *, DbRef *);
|
* Unlock an object and return it back to the database. This function
|
||||||
|
* immediately syncs the object to the filesystem. The cache is a
|
||||||
|
* read cache; writes are always immediate to ensure data integrity in
|
||||||
|
* the event of a system failure.
|
||||||
|
*/
|
||||||
|
extern int DbUnlock(Db *, DbRef *);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
DbExists(Db *, size_t,...);
|
* Check the existence of the given database object in a more efficient
|
||||||
|
* manner than attempting to lock it with
|
||||||
|
* .Fn DbLock .
|
||||||
|
* This function does not lock the object, nor does it load it into
|
||||||
|
* memory if it exists.
|
||||||
|
*/
|
||||||
|
extern int DbExists(Db *, size_t,...);
|
||||||
|
|
||||||
extern Array *
|
/**
|
||||||
DbList(Db *, size_t,...);
|
* List all of the objects at a given path. Unlike the other varargs
|
||||||
|
* functions, this one does not take a path to a specific object; it
|
||||||
|
* takes a directory to be iterated, where each path part is its own
|
||||||
|
* C string. Note that the resulting list only contains the objects
|
||||||
|
* in the specified directory, it does not list any subdirectories.
|
||||||
|
* .Pp
|
||||||
|
* The array returned is an array of C strings containing the object
|
||||||
|
* name.
|
||||||
|
*/
|
||||||
|
extern Array * DbList(Db *, size_t,...);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
DbListFree(Array *);
|
* Free the list returned by
|
||||||
|
* .Fn DbListFree .
|
||||||
|
*/
|
||||||
|
extern void DbListFree(Array *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
DbJson(DbRef *);
|
* Convert a database reference into JSON that can be manipulated.
|
||||||
|
* At this time, the database actually stores objects as JSON on the
|
||||||
|
* disk, so this function just returns an internal pointer, but in the
|
||||||
|
* future it may have to be generated by decompressing a binary blob,
|
||||||
|
* or something of that nature.
|
||||||
|
*/
|
||||||
|
extern HashMap * DbJson(DbRef *);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
DbJsonSet(DbRef *, HashMap *);
|
* Free the existing JSON associated with the given reference, and
|
||||||
|
* replace it with new JSON. This is more efficient than duplicating
|
||||||
|
* a separate object into the database reference.
|
||||||
|
*/
|
||||||
|
extern int DbJsonSet(DbRef *, HashMap *);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -25,35 +25,143 @@
|
||||||
#ifndef TELODENDRIA_HASHMAP_H
|
#ifndef TELODENDRIA_HASHMAP_H
|
||||||
#define TELODENDRIA_HASHMAP_H
|
#define TELODENDRIA_HASHMAP_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HashMap
|
||||||
|
* @Nd A simple hash map implementation.
|
||||||
|
* @Dd October 11 2022
|
||||||
|
* @Xr Array Queue
|
||||||
|
*
|
||||||
|
* This is the public interface for Telodendria's hash map
|
||||||
|
* implementation. This hash map is designed to be simple,
|
||||||
|
* well-documented, and generally readable and understandable, yet also
|
||||||
|
* performant enough to be useful, because it is used extensively
|
||||||
|
* throughout the project.
|
||||||
|
* .Pp
|
||||||
|
* Fundamentally, this is an entirely generic map implementation. It
|
||||||
|
* can be used for many general purposes, but it is designed only to
|
||||||
|
* implement the features Telodendria needs to be functional. One
|
||||||
|
* example of a Telodendria-specific feature is that keys cannot be
|
||||||
|
* arbitrary data; they are NULL-terminated C strings.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These functions operate on an opaque structure, which the caller
|
||||||
|
* has no knowledge about.
|
||||||
|
*/
|
||||||
typedef struct HashMap HashMap;
|
typedef struct HashMap HashMap;
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
HashMapCreate(void);
|
* Create a new hash map that is ready to be used with the rest of the
|
||||||
|
* functions defined here.
|
||||||
|
*/
|
||||||
|
extern HashMap * HashMapCreate(void);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HashMapMaxLoadSet(HashMap *, float);
|
* Free the specified hash map such that it becomes invalid and any
|
||||||
|
* future use results in undefined behavior. Note that this function
|
||||||
|
* does not free the values stored in the hash map, but since it stores
|
||||||
|
* the keys internally, it will free the keys. You should use
|
||||||
|
* .Fn HashMapIterate
|
||||||
|
* to free the values stored in this map appropriately before calling
|
||||||
|
* this function.
|
||||||
|
*/
|
||||||
|
extern void HashMapFree(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Control the maximum load of the hash map before it is expanded.
|
||||||
|
* When the hash map reaches the given capacity, it is grown. You
|
||||||
|
* don't want to only grow hash maps when they are full, because that
|
||||||
|
* makes them perform very poorly. The maximum load value is a
|
||||||
|
* percentage of how full the hash map is, and it should be between
|
||||||
|
* 0 and 1, where 0 means that no elements will cause the map to be
|
||||||
|
* expanded, and 1 means that the hash map must be completely full
|
||||||
|
* before it is expanded. The default maximum load on a new hash map
|
||||||
|
* is 0.75, which should be good enough for most purposes, however,
|
||||||
|
* this function exists specifically so that the maximum load can be
|
||||||
|
* fine-tuned.
|
||||||
|
*/
|
||||||
|
extern void HashMapMaxLoadSet(HashMap *, float);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use a custom hashing function with the given hash map. New hash
|
||||||
|
* maps have a sane hashing function that should work okay for most
|
||||||
|
* use cases, but if you have a better hashing function, it can be
|
||||||
|
* specified this way. Do not change the hash function after keys have
|
||||||
|
* been added; doing so results in undefined behavior. Only set a new
|
||||||
|
* hash function immediately after constructing a new hash map, before
|
||||||
|
* anything has been added to it.
|
||||||
|
* .Pp
|
||||||
|
* The hash function takes a pointer to a C string, and is expected
|
||||||
|
* to return a fairly unique numerical hash value which will be
|
||||||
|
* converted into an array index.
|
||||||
|
*/
|
||||||
extern void
|
extern void
|
||||||
HashMapFunctionSet(HashMap *, unsigned long (*) (const char *));
|
HashMapFunctionSet(HashMap *, unsigned long (*) (const char *));
|
||||||
|
|
||||||
extern void *
|
/**
|
||||||
HashMapSet(HashMap *, char *, void *);
|
* Set the given string key to the given value. Note that the key is
|
||||||
|
* copied into the hash map's own memory space, but the value is not.
|
||||||
|
* It is the caller's job to ensure that the value pointer remains
|
||||||
|
* valid for the life of the hash map, and are freed when no longer
|
||||||
|
* needed.
|
||||||
|
*/
|
||||||
|
extern void * HashMapSet(HashMap *, char *, void *);
|
||||||
|
|
||||||
extern void *
|
/**
|
||||||
HashMapGet(HashMap *, const char *);
|
* Retrieve the value for the given key, or return NULL if no such
|
||||||
|
* key exists in the hash map.
|
||||||
|
*/
|
||||||
|
extern void * HashMapGet(HashMap *, const char *);
|
||||||
|
|
||||||
extern void *
|
/**
|
||||||
HashMapDelete(HashMap *, const char *);
|
* Remove a value specified by the given key from the hash map, and
|
||||||
|
* return it to the caller to deal with. This function returns NULL
|
||||||
|
* if no such key exists.
|
||||||
|
*/
|
||||||
|
extern void * HashMapDelete(HashMap *, const char *);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
HashMapIterate(HashMap *, char **, void **);
|
* Iterate over all the keys and values of a hash map. This function
|
||||||
|
* works very similarly to
|
||||||
|
* .Xr getopt 3 ,
|
||||||
|
* where calls are repeatedly made in a while loop until there are no
|
||||||
|
* more items to go over. The difference is that this function does not
|
||||||
|
* rely on globals; it takes pointer pointers, and stores all
|
||||||
|
* necessary state inside the hash map itself.
|
||||||
|
* .Pp
|
||||||
|
* Note that this function is not thread-safe; two threads cannot be
|
||||||
|
* iterating over any given hash map at the same time, though they
|
||||||
|
* can each be iterating over different hash maps.
|
||||||
|
* .Pp
|
||||||
|
* This function can be tricky to use in some scenarios, as it
|
||||||
|
* continues where it left off on each call, until there are no more
|
||||||
|
* elements to go through in the hash map. If you are not iterating
|
||||||
|
* over the entire map in one go, and happen to break the loop, then
|
||||||
|
* the next time you attempt to iterate the hash map, you'll start
|
||||||
|
* somewhere in the middle, which is most likely not the intended
|
||||||
|
* behavior. Thus, it is always recommended to iterate over the entire
|
||||||
|
* hash map if you're going to use this function.
|
||||||
|
* .Pp
|
||||||
|
* Also note that the behavior of this function is undefined if
|
||||||
|
* insertions or deletions occur during the iteration. This
|
||||||
|
* functionality has not been tested, and will likely not work.
|
||||||
|
*/
|
||||||
|
extern int HashMapIterate(HashMap *, char **, void **);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reentrant version of
|
||||||
|
* .Fn HashMapIterate
|
||||||
|
* that allows the caller to overcome the flaws of that function by
|
||||||
|
* storing the cursor outside of the hash map structure itself. This
|
||||||
|
* allows multiple threads to iterate over the same hash map at the
|
||||||
|
* same time, and it allows the iteration to be halted midway through
|
||||||
|
* without causing any unintended side effects.
|
||||||
|
* .Pp
|
||||||
|
* The cursor should be initialized to 0 at the start of iteration.
|
||||||
|
*/
|
||||||
extern int
|
extern int
|
||||||
HashMapIterateReentrant(HashMap *, char **, void **, size_t *);
|
HashMapIterateReentrant(HashMap *, char **, void **, size_t *);
|
||||||
|
|
||||||
extern void
|
|
||||||
HashMapFree(HashMap *);
|
|
||||||
|
|
||||||
#endif /* TELODENDRIA_HASHMAP_H */
|
#endif /* TELODENDRIA_HASHMAP_H */
|
||||||
|
|
|
@ -24,6 +24,22 @@
|
||||||
#ifndef TELODENDRIA_HTTP_H
|
#ifndef TELODENDRIA_HTTP_H
|
||||||
#define TELODENDRIA_HTTP_H
|
#define TELODENDRIA_HTTP_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Http
|
||||||
|
* @Nd Encode and decode various parts of the HTTP protocol.
|
||||||
|
* @Dd March 12 2023
|
||||||
|
* @Xr HttpClient HttpServer HashMap Queue Memory
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a collection of utility functions and type definitions that are
|
||||||
|
* useful for dealing with HTTP. HTTP is not a complex protocol, but
|
||||||
|
* this API makes it a lot easier to work with.
|
||||||
|
* .Pp
|
||||||
|
* Note that this API doesn't target any particular HTTP version, but
|
||||||
|
* it is currently used with HTTP 1.0 clients and servers, and
|
||||||
|
* therefore may be lacking functionality added in later HTTP versions.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
|
@ -32,6 +48,11 @@
|
||||||
#define HTTP_FLAG_NONE 0
|
#define HTTP_FLAG_NONE 0
|
||||||
#define HTTP_FLAG_TLS (1 << 0)
|
#define HTTP_FLAG_TLS (1 << 0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request methods defined by the HTTP standard. These numeric
|
||||||
|
* constants should be preferred to strings when building HTTP APIs
|
||||||
|
* because they are more efficient.
|
||||||
|
*/
|
||||||
typedef enum HttpRequestMethod
|
typedef enum HttpRequestMethod
|
||||||
{
|
{
|
||||||
HTTP_METHOD_UNKNOWN,
|
HTTP_METHOD_UNKNOWN,
|
||||||
|
@ -46,6 +67,10 @@ typedef enum HttpRequestMethod
|
||||||
HTTP_PATCH
|
HTTP_PATCH
|
||||||
} HttpRequestMethod;
|
} HttpRequestMethod;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration that corresponds to the actual integer values of the
|
||||||
|
* valid HTTP response codes.
|
||||||
|
*/
|
||||||
typedef enum HttpStatus
|
typedef enum HttpStatus
|
||||||
{
|
{
|
||||||
HTTP_STATUS_UNKNOWN = 0,
|
HTTP_STATUS_UNKNOWN = 0,
|
||||||
|
@ -110,28 +135,77 @@ typedef enum HttpStatus
|
||||||
HTTP_NETWORK_AUTH_REQUIRED = 511
|
HTTP_NETWORK_AUTH_REQUIRED = 511
|
||||||
} HttpStatus;
|
} HttpStatus;
|
||||||
|
|
||||||
extern const char *
|
/**
|
||||||
HttpStatusToString(const HttpStatus);
|
* Convert an HTTP status enumeration value into a string description
|
||||||
|
* of the status, which is to be used in server response to a client,
|
||||||
|
* or a client response to a user. For example, calling
|
||||||
|
* .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT"
|
||||||
|
* (or
|
||||||
|
* .Fn HttpStatusToString "504" )
|
||||||
|
* produces the string "Gateway Timeout". Note that the returned
|
||||||
|
* pointers point to static space, so their manipulation is forbidden.
|
||||||
|
*/
|
||||||
|
extern const char * HttpStatusToString(const HttpStatus);
|
||||||
|
|
||||||
extern HttpRequestMethod
|
/**
|
||||||
HttpRequestMethodFromString(const char *);
|
* Convert a string into a numeric code that can be used throughout
|
||||||
|
* the code of a program in an efficient manner. See the definition
|
||||||
|
* of HttpRequestMethod. This function does case-sensitive matching,
|
||||||
|
* and does not trim or otherwise process the input string.
|
||||||
|
*/
|
||||||
|
extern HttpRequestMethod HttpRequestMethodFromString(const char *);
|
||||||
|
|
||||||
extern const char *
|
/**
|
||||||
HttpRequestMethodToString(const HttpRequestMethod);
|
* Convert a numeric code as defined by HttpRequestMethod into a
|
||||||
|
* string that can be sent to a server. Note that the returned pointers
|
||||||
|
* point to static space, so their manipulation is forbidden.
|
||||||
|
*/
|
||||||
|
extern const char * HttpRequestMethodToString(const HttpRequestMethod);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
HttpUrlEncode(char *);
|
* Encode a C string such that it can safely appear in a URL by
|
||||||
|
* performing the necessary percent escaping. A new string on the
|
||||||
|
* heap is returned. It should be freed with
|
||||||
|
* .Fn Free ,
|
||||||
|
* defined in the
|
||||||
|
* .Xr Memory 3
|
||||||
|
* API.
|
||||||
|
*/
|
||||||
|
extern char * HttpUrlEncode(char *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
HttpUrlDecode(char *);
|
* Decode a percent-encoded string into a C string, ignoring encoded
|
||||||
|
* null characters entirely, because those would do nothing but cause
|
||||||
|
* problems.
|
||||||
|
*/
|
||||||
|
extern char * HttpUrlDecode(char *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
HttpParamDecode(char *);
|
* Decode an encoded parameter string in the form of
|
||||||
|
* ``key=val&key2=val2'' into a hash map whose values are C strings.
|
||||||
|
* This function properly decodes keys and values using the functions
|
||||||
|
* defined above.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpParamDecode(char *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
HttpParamEncode(HashMap *);
|
* Encode a hash map whose values are strings as an HTTP parameter
|
||||||
|
* string suitable for GET or POST requests.
|
||||||
|
*/
|
||||||
|
extern char * HttpParamEncode(HashMap *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
HttpParseHeaders(Stream *);
|
* Read HTTP headers from a stream and return a hash map whose values
|
||||||
|
* are strings. All keys are lowercased to make querying them
|
||||||
|
* consistent and not dependent on the case that was read from the
|
||||||
|
* stream. This is useful for both client and server code, since the
|
||||||
|
* headers are in the same format. This function should be used after
|
||||||
|
* parsing the HTTP status line, because it does not parse that line.
|
||||||
|
* It will stop when it encounters the first blank line, which
|
||||||
|
* indicates that the body is beginning. After this function completes,
|
||||||
|
* the body may be immediately read from the stream without any
|
||||||
|
* additional processing.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpParseHeaders(Stream *);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -24,6 +24,28 @@
|
||||||
#ifndef TELODENDRIA_HTTPSERVER_H
|
#ifndef TELODENDRIA_HTTPSERVER_H
|
||||||
#define TELODENDRIA_HTTPSERVER_H
|
#define TELODENDRIA_HTTPSERVER_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm HttpServer
|
||||||
|
* @Nd Extremely simple HTTP server.
|
||||||
|
* @Dd December 13 2022
|
||||||
|
* @Xr Http HttpClient
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* builds on the
|
||||||
|
* .Xr Http 3
|
||||||
|
* API, and provides a very simple, yet very functional API for
|
||||||
|
* creating an HTTP server. It aims at being easy to use and minimal,
|
||||||
|
* yet also efficient. It uses non-blocking I/O, is fully
|
||||||
|
* multi-threaded, and is very configurable. It can be set up in just
|
||||||
|
* two function calls and minimal supporting code.
|
||||||
|
* .Pp
|
||||||
|
* This API should be familar to those that have dealt with the HTTP
|
||||||
|
* server libraries of other programming languages, particularly Java.
|
||||||
|
* In fact, much of the terminology used in this API came from Java,
|
||||||
|
* and you'll notice that the way responses are sent and received very
|
||||||
|
* closely resembles Java.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <Http.h>
|
#include <Http.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -31,68 +53,182 @@
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The functions on this API operate on an opaque structure.
|
||||||
|
*/
|
||||||
typedef struct HttpServer HttpServer;
|
typedef struct HttpServer HttpServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each request receives a context structure. It is opaque, so the
|
||||||
|
* functions defined in this API should be used to fetch data from
|
||||||
|
* it. These functions allow the handler to figure out the context of
|
||||||
|
* the request, which includes the path requested, any parameters,
|
||||||
|
* and the headers and method used to make the request. The context
|
||||||
|
* also provides the means by which the handler responds to the
|
||||||
|
* request, allowing it to set the status code, headers, and body.
|
||||||
|
*/
|
||||||
typedef struct HttpServerContext HttpServerContext;
|
typedef struct HttpServerContext HttpServerContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request handler function is executed when an HTTP request is
|
||||||
|
* received. It takes a request context, and a pointer as specified
|
||||||
|
* in the server configuration.
|
||||||
|
*/
|
||||||
typedef void (HttpHandler) (HttpServerContext *, void *);
|
typedef void (HttpHandler) (HttpServerContext *, void *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of arguments to
|
||||||
|
* .Fn HttpServerCreate
|
||||||
|
* has grown so large that arguments are not stuffed into a
|
||||||
|
* configuration structure, which is in turn passed to
|
||||||
|
* .Fn HttpServerCreate .
|
||||||
|
* This configuration is copied by value into the internal
|
||||||
|
* structures of the server. It is copied with very minimal
|
||||||
|
* validation, so ensure that all values are sensible. It may
|
||||||
|
* make sense to use
|
||||||
|
* .Fn memset
|
||||||
|
* to zero out everything in here before assigning values.
|
||||||
|
*/
|
||||||
typedef struct HttpServerConfig
|
typedef struct HttpServerConfig
|
||||||
{
|
{
|
||||||
unsigned short port;
|
unsigned short port;
|
||||||
unsigned int threads;
|
unsigned int threads;
|
||||||
unsigned int maxConnections;
|
unsigned int maxConnections;
|
||||||
|
|
||||||
int flags;
|
int flags; /* Http(3) flags */
|
||||||
char *tlsCert;
|
char *tlsCert; /* File path */
|
||||||
char *tlsKey;
|
char *tlsKey; /* File path */
|
||||||
|
|
||||||
HttpHandler *handler;
|
HttpHandler *handler;
|
||||||
void *handlerArgs;
|
void *handlerArgs;
|
||||||
} HttpServerConfig;
|
} HttpServerConfig;
|
||||||
|
|
||||||
extern HttpServer *
|
/**
|
||||||
HttpServerCreate(HttpServerConfig *);
|
* Create a new HTTP server using the specified configuration.
|
||||||
|
* This will set up all internal structures used by the server,
|
||||||
|
* and bind the socket and start listening for connections. However,
|
||||||
|
* it will not start accepting connections.
|
||||||
|
*/
|
||||||
|
extern HttpServer * HttpServerCreate(HttpServerConfig *);
|
||||||
|
|
||||||
extern HttpServerConfig *
|
/**
|
||||||
HttpServerConfigGet(HttpServer *);
|
* Retrieve the configuration that was used to instantiate the given
|
||||||
|
* server. Note that this configuration is not necessarily the exact
|
||||||
|
* one that was provided; even though its values are the same, it
|
||||||
|
* should be treated as an entirely separate configuration with no
|
||||||
|
* connection to the original.
|
||||||
|
*/
|
||||||
|
extern HttpServerConfig * HttpServerConfigGet(HttpServer *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HttpServerFree(HttpServer *);
|
* Free the resources associated with the given HTTP server. Note that
|
||||||
|
* the server can only be freed after it has been stopped. Calling this
|
||||||
|
* function while the server is still running results in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
extern void HttpServerFree(HttpServer *);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
HttpServerStart(HttpServer *);
|
* Attempt to start the HTTP server, and return immediately with the
|
||||||
|
* status. This API is fully multi-threaded and asynchronous, so the
|
||||||
|
* caller can continue working while the HTTP server is running in a
|
||||||
|
* separate thread and managing a pool of threads to handle responses.
|
||||||
|
*/
|
||||||
|
extern int HttpServerStart(HttpServer *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HttpServerJoin(HttpServer *);
|
* Typically, at some point after calling
|
||||||
|
* .Fn HttpServerStart ,
|
||||||
|
* the program will have no more work to do, so it will want to wait
|
||||||
|
* for the HTTP server to finish. This is accomplished via this
|
||||||
|
* function, which joins the HTTP worker thread to the calling thread,
|
||||||
|
* pausing the calling thread until the HTTP server has stopped.
|
||||||
|
*/
|
||||||
|
extern void HttpServerJoin(HttpServer *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HttpServerStop(HttpServer *);
|
* Stop the HTTP server. Only the execution of this function will
|
||||||
|
* cause the proper shutdown of the HTTP server. If the main program
|
||||||
|
* is joined to the HTTP thread, then either another thread or a
|
||||||
|
* signal handler will have to stop the server using this function.
|
||||||
|
* The typical use case is to install a signal handler that executes
|
||||||
|
* this function on a global HTTP server.
|
||||||
|
*/
|
||||||
|
extern void HttpServerStop(HttpServer *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
HttpRequestHeaders(HttpServerContext *);
|
* Get the request headers for the request represented by the given
|
||||||
|
* context. The data in the returned hash map should be treated as
|
||||||
|
* read only and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpRequestHeaders(HttpServerContext *);
|
||||||
|
|
||||||
extern HttpRequestMethod
|
/**
|
||||||
HttpRequestMethodGet(HttpServerContext *);
|
* Get the request method used to make the request represented by
|
||||||
|
* the given context.
|
||||||
|
*/
|
||||||
|
extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
HttpRequestPath(HttpServerContext *);
|
* Get the request path for the request represented by the given
|
||||||
|
* context. The return value of this function should be treated as
|
||||||
|
* read-only, and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern char * HttpRequestPath(HttpServerContext *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
HttpRequestParams(HttpServerContext *);
|
* Retrieve the parsed GET parameters for the request represented by
|
||||||
|
* the given context. The returned hash map should be treated as
|
||||||
|
* read-only, and should not be freed; it is managed entirely by the
|
||||||
|
* server.
|
||||||
|
*/
|
||||||
|
extern HashMap * HttpRequestParams(HttpServerContext *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
HttpResponseHeader(HttpServerContext *, char *, char *);
|
* Set a response header to return to the client. The old value for
|
||||||
|
* the given header is returned, if any, otherwise NULL is returned.
|
||||||
|
*/
|
||||||
|
extern char * HttpResponseHeader(HttpServerContext *, char *, char *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HttpResponseStatus(HttpServerContext *, HttpStatus);
|
* Set the response status to return to the client.
|
||||||
|
*/
|
||||||
|
extern void HttpResponseStatus(HttpServerContext *, HttpStatus);
|
||||||
|
|
||||||
extern HttpStatus
|
/**
|
||||||
HttpResponseStatusGet(HttpServerContext *);
|
* Get the current response status that will be sent to the client
|
||||||
|
* making the request represented by the given context.
|
||||||
|
*/
|
||||||
|
extern HttpStatus HttpResponseStatusGet(HttpServerContext *);
|
||||||
|
|
||||||
extern Stream *
|
/**
|
||||||
HttpServerStream(HttpServerContext *);
|
* Send the response headers to the client that made the request
|
||||||
|
* represented by the specified context. This function must be called
|
||||||
|
* before the response body can be written, otherwise a malformed
|
||||||
|
* response will be sent.
|
||||||
|
*/
|
||||||
|
extern void HttpSendHeaders(HttpServerContext *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
HttpSendHeaders(HttpServerContext *);
|
* Get a stream that is both readable and writable. Reading from the
|
||||||
|
* stream reads the request body that the client sent, if there is one.
|
||||||
|
* Note that the rquest headers have already been read, so the stream
|
||||||
|
* is correctly positioned at the beginning of the body of the request.
|
||||||
|
* .Fn HttpSendHeaders
|
||||||
|
* must be called before the stream is written, otherwise a malformed
|
||||||
|
* HTTP response will be sent. An HTTP handler should properly set all
|
||||||
|
* the headers it itends to send, send those headers, and then write
|
||||||
|
* the response body to this stream.
|
||||||
|
* .Pp
|
||||||
|
* Note that the stream does not need to be closed by the HTTP
|
||||||
|
* handler; in fact doing so results in undefined behavior. The stream
|
||||||
|
* is managed entirely by the server itself, so it will close it when
|
||||||
|
* necessary. This allows the underlying protocol to differ: for
|
||||||
|
* instance, an HTTP/1.1 connection may stay for multiple requests and
|
||||||
|
* responses.
|
||||||
|
*/
|
||||||
|
extern Stream * HttpServerStream(HttpServerContext *);
|
||||||
|
|
||||||
#endif
|
#endif /* TELODENDRIA_HTTPSERVER_H */
|
||||||
|
|
|
@ -25,6 +25,49 @@
|
||||||
#ifndef TELODENDRIA_JSON_H
|
#ifndef TELODENDRIA_JSON_H
|
||||||
#define TELODENDRIA_JSON_H
|
#define TELODENDRIA_JSON_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Json
|
||||||
|
* @Nd A fully-featured JSON API.
|
||||||
|
* @Dd March 12 2023
|
||||||
|
* @Xr HashMap Array Stream
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a fully-featured JSON API for C using the array and hash map
|
||||||
|
* APIs. It can parse JSON, ans serialize an in-memory structure to
|
||||||
|
* JSON. It build on the foundation of Array and HashMap because that's
|
||||||
|
* all JSON really is, just arrays and maps.
|
||||||
|
* .Nm
|
||||||
|
* also provides a structure for encapsulating an arbitrary value and
|
||||||
|
* identifying its type, making it easy for a strictly-typed language
|
||||||
|
* like C to work with loosely-typed JSON data.
|
||||||
|
* .Nm
|
||||||
|
* is very strict and tries to adhere as closely as possible to the
|
||||||
|
* proper definition of JSON. It will fail on syntax errors of any
|
||||||
|
* kind, which is fine for a Matrix homeserver that can just return
|
||||||
|
* M_BAD_JSON if anything in here fails, but this behavior may not be
|
||||||
|
* suitable for other purposes.
|
||||||
|
* .Pp
|
||||||
|
* This JSON implementation focuses primarily on serialization and
|
||||||
|
* deserialization to and from streams. It does not provide facilities
|
||||||
|
* for handling JSON strings; it only writes JSON to output streams,
|
||||||
|
* and reads them from input streams. Of course, you can use the
|
||||||
|
* POSIX
|
||||||
|
* .Xr fmemopen 3
|
||||||
|
* and
|
||||||
|
* .Xr open_memstream 3
|
||||||
|
* functions if you want to deal with JSON strings, but JSON is
|
||||||
|
* intended to be an exchange format. Data should be converted to JSON
|
||||||
|
* right when it is leaving the program, and converted from JSON to the
|
||||||
|
* in-memory format as soon as it is coming in.
|
||||||
|
* .Pp
|
||||||
|
* JSON objects are represented as hash maps consisting entirely of
|
||||||
|
* JsonValue structures, and arrays are represented as arrays
|
||||||
|
* consisting entirely of JsonValue structures. When generating a
|
||||||
|
* JSON object, any attempt to stuff a value into a hash map or array
|
||||||
|
* without first encoding it as a JsonValue will result in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <HashMap.h>
|
#include <HashMap.h>
|
||||||
#include <Array.h>
|
#include <Array.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
@ -35,90 +78,233 @@
|
||||||
#define JSON_DEFAULT -1
|
#define JSON_DEFAULT -1
|
||||||
#define JSON_PRETTY 0
|
#define JSON_PRETTY 0
|
||||||
|
|
||||||
typedef enum JsonType
|
/**
|
||||||
{
|
* This opaque structure encapsulates all the possible types that can
|
||||||
JSON_NULL,
|
* be stored in JSON. It is managed entirely by the functions defined
|
||||||
JSON_OBJECT,
|
* in this API. It is important to note that strings, integers, floats,
|
||||||
JSON_ARRAY,
|
* booleans, and the NULL value are all stored by value, but objects
|
||||||
JSON_STRING,
|
* and arrays are stored by reference. That is, it doesn't store these
|
||||||
JSON_INTEGER,
|
* itself, just pointers to them, however, the data
|
||||||
JSON_FLOAT,
|
* .Em is
|
||||||
JSON_BOOLEAN
|
* freed when using
|
||||||
} JsonType;
|
* .Fn JsonFree .
|
||||||
|
*/
|
||||||
typedef struct JsonValue JsonValue;
|
typedef struct JsonValue JsonValue;
|
||||||
|
|
||||||
extern JsonType
|
/**
|
||||||
JsonValueType(JsonValue *);
|
* These are the types that can be used to identify a JsonValue
|
||||||
|
* and act on it accordingly.
|
||||||
|
*/
|
||||||
|
typedef enum JsonType
|
||||||
|
{
|
||||||
|
JSON_NULL, /* Maps to a C NULL */
|
||||||
|
JSON_OBJECT, /* Maps to a HashMap of JsonValues */
|
||||||
|
JSON_ARRAY, /* Maps to an Array of JsonValues */
|
||||||
|
JSON_STRING, /* Maps to a null-terminated C string */
|
||||||
|
JSON_INTEGER, /* Maps to a C long */
|
||||||
|
JSON_FLOAT, /* Maps to a C double */
|
||||||
|
JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */
|
||||||
|
} JsonType;
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueObject(HashMap *);
|
* Determine the type of the specified JSON value.
|
||||||
|
*/
|
||||||
|
extern JsonType JsonValueType(JsonValue *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
JsonValueAsObject(JsonValue *);
|
* Encode a JSON object as a JSON value that can be added to another
|
||||||
|
* object, or an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueObject(HashMap *);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueArray(Array *);
|
* Unwrap a JSON value that represents an object. This function will
|
||||||
|
* return NULL if the value is not actually an object.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonValueAsObject(JsonValue *);
|
||||||
|
|
||||||
extern Array *
|
/**
|
||||||
JsonValueAsArray(JsonValue *);
|
* Encode a JSON array as a JSON value that can be added to an object
|
||||||
|
* or another array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueArray(Array *);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueString(char *);
|
* Unwrap a JSON value that represents an array. This function will
|
||||||
|
* return NULL if the value is not actually an array.
|
||||||
|
*/
|
||||||
|
extern Array * JsonValueAsArray(JsonValue *);
|
||||||
|
|
||||||
extern char *
|
/**
|
||||||
JsonValueAsString(JsonValue *);
|
* Encode a C string as a JSON value that can be added to an object or
|
||||||
|
* an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueString(char *);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueInteger(long);
|
* Unwrap a JSON value that represents a string. This function will
|
||||||
|
* return NULL if the value is not actually a string.
|
||||||
|
*/
|
||||||
|
extern char * JsonValueAsString(JsonValue *);
|
||||||
|
|
||||||
extern long
|
/**
|
||||||
JsonValueAsInteger(JsonValue *);
|
* Encode a number as a JSON value that can be added to an object or
|
||||||
|
* an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueInteger(long);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueFloat(double);
|
* Unwrap a JSON value that represents a number. This function will
|
||||||
|
* return 0 if the value is not actually a number, which may be
|
||||||
|
* misleading. Check the type of the value before making assumptions
|
||||||
|
* about its value.
|
||||||
|
*/
|
||||||
|
extern long JsonValueAsInteger(JsonValue *);
|
||||||
|
|
||||||
extern double
|
/**
|
||||||
JsonValueAsFloat(JsonValue *);
|
* Encode a floating point number as a JSON value that can be added
|
||||||
|
* to an object or an array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueFloat(double);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueBoolean(int);
|
* Unwrap a JSON value that represents a floating point number. This
|
||||||
|
* function will return 0 if the value is not actually a floating
|
||||||
|
* point number, which may be misleading. Check the type of the value
|
||||||
|
* before making assumptions about its type.
|
||||||
|
*/
|
||||||
|
extern double JsonValueAsFloat(JsonValue *);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
JsonValueAsBoolean(JsonValue *);
|
* Encode a C integer according to the way C treats integers in boolean
|
||||||
|
* expressions as a JSON value that can be added to an object or an
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueBoolean(int);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueNull(void);
|
* Unwrap a JSON value that represents a boolean. This function will
|
||||||
|
* return 0 if the value is not actually a boolean, which may be
|
||||||
|
* misleading. Check the type of the value before making assumptions
|
||||||
|
* about its type.
|
||||||
|
*/
|
||||||
|
extern int JsonValueAsBoolean(JsonValue *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
JsonValueFree(JsonValue *);
|
* This is a special case that represents a JSON null. Because the
|
||||||
|
* Array and HashMap APIs do not accept NULL values, this function
|
||||||
|
* should be used to represent NULL in JSON. Even though a small
|
||||||
|
* amount of memory is allocated just to be a placeholder for nothing,
|
||||||
|
* this keeps the APIs clean.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueNull(void);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonValueDuplicate(JsonValue *);
|
* Free the memory being used by a JSON value. Note that this will
|
||||||
|
* recursively free all Arrays, HashMaps, and other JsonValues that are
|
||||||
|
* reachable from the given value, including any strings attached to
|
||||||
|
* this value.
|
||||||
|
*/
|
||||||
|
extern void JsonValueFree(JsonValue *);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
JsonDuplicate(HashMap *);
|
* Recursively duplicate the given JSON value. This returns a new
|
||||||
|
* JSON value that is completely identical to the specified value, but
|
||||||
|
* in no way connected to it.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonValueDuplicate(JsonValue *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
JsonFree(HashMap *);
|
* Recursively duplicate the given JSON object. This returns a new
|
||||||
|
* JSON object that is completely identical to the specified object,
|
||||||
|
* but in no way connect to it.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonDuplicate(HashMap *);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively free a JSON object by iterating over all of its values
|
||||||
|
* and freeing them using
|
||||||
|
* .Fn JsonValueFree .
|
||||||
|
*/
|
||||||
|
extern void JsonFree(HashMap *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
JsonEncodeString(const char *, Stream *);
|
* Encode the given string in such a way that it can be safely
|
||||||
|
* embedded in a JSON stream. This entails:
|
||||||
|
* .Bl -bullet -offset indent
|
||||||
|
* .It
|
||||||
|
* Escaping quotes, backslashes, and other special characters using
|
||||||
|
* their backslash escape.
|
||||||
|
* .It
|
||||||
|
* Encoding bytes that are not UTF-8 using escapes.
|
||||||
|
* .It
|
||||||
|
* Wrapping the entire string in double quotes.
|
||||||
|
* .El
|
||||||
|
* .Pp
|
||||||
|
* This function is only provided via the public
|
||||||
|
* .Nm
|
||||||
|
* API so that it is accessible to custom JSON encoders, such as the
|
||||||
|
* CanonicalJson encoder. This will typically be used for encoding
|
||||||
|
* object keys; to encode values, just use
|
||||||
|
* .Fn JsonEncodeValue .
|
||||||
|
*/
|
||||||
|
extern void JsonEncodeString(const char *, Stream *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
JsonEncodeValue(JsonValue * value, Stream * out, int);
|
* Serialize a JSON value as it would appear in JSON output. This is
|
||||||
|
* a recursive function that also encodes all child values reachable
|
||||||
|
* from the given value. This function is exposed via the public
|
||||||
|
* .Nm
|
||||||
|
* API so that it is accessible to custom JSON encoders. Normal users
|
||||||
|
* that are not writing custom encoders should in most cases just use
|
||||||
|
* .Fn JsonEncode
|
||||||
|
* to encode an entire object.
|
||||||
|
* .Pp
|
||||||
|
* The third parameter is an integer that represents the indent level
|
||||||
|
* of the value to be printed, or a negative number if pretty-printing
|
||||||
|
* should be disabled and JSON should be printed as minimized as
|
||||||
|
* possible. To pretty-print a JSON object, set this to
|
||||||
|
* .Va JSON_PRETTY .
|
||||||
|
* To get minified output, set it to
|
||||||
|
* .Va JSON_DEFAULT .
|
||||||
|
*/
|
||||||
|
extern void JsonEncodeValue(JsonValue *, Stream *, int);
|
||||||
|
|
||||||
extern int
|
/**
|
||||||
JsonEncode(HashMap *, Stream *, int);
|
* Encode a JSON object as it would appear in JSON output, writing it
|
||||||
|
* to the given output stream. This function is recursive; it will
|
||||||
|
* serialize everything accessible from the passed object. The third
|
||||||
|
* parameter has the same behavior as described above.
|
||||||
|
*/
|
||||||
|
extern int JsonEncode(HashMap *, Stream *, int);
|
||||||
|
|
||||||
extern HashMap *
|
/**
|
||||||
JsonDecode(Stream *);
|
* Decode a JSON object from the given input stream and parse it into
|
||||||
|
* a hash map of JSON values.
|
||||||
|
*/
|
||||||
|
extern HashMap * JsonDecode(Stream *);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonGet(HashMap *, size_t,...);
|
* A convenience function that allows the caller to retrieve and
|
||||||
|
* arbitrarily deep keys within a JSON object. It takes a root JSON
|
||||||
|
* object, the number of levels deep to go, and then that number of
|
||||||
|
* keys as a varargs list. All keys must have objects as values, with
|
||||||
|
* the exception of the last one, which is the value that will be
|
||||||
|
* returned. Otherwise, NULL indicates the specified path doas not
|
||||||
|
* exist.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonGet(HashMap *, size_t,...);
|
||||||
|
|
||||||
extern JsonValue *
|
/**
|
||||||
JsonSet(HashMap *, JsonValue *, size_t,...);
|
* A convenience function that allows the caller to set arbitrarily
|
||||||
|
* deep keys within a JSON object. It takes a root JSON object, the
|
||||||
|
* number of levels deep to go, and then that number of keys as a
|
||||||
|
* varargs list. All keys must have object as values, with the
|
||||||
|
* exception of the last one, which is the value that will be set.
|
||||||
|
* The value currently at that key, if any, will be returned.
|
||||||
|
* This function will create any intermediate objects as necessary to
|
||||||
|
* set the proper key.
|
||||||
|
*/
|
||||||
|
extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...);
|
||||||
|
|
||||||
#endif /* TELODENDRIA_JSON_H */
|
#endif /* TELODENDRIA_JSON_H */
|
||||||
|
|
|
@ -25,6 +25,19 @@
|
||||||
#ifndef TELODENDRIA_LOG_H
|
#ifndef TELODENDRIA_LOG_H
|
||||||
#define TELODENDRIA_LOG_H
|
#define TELODENDRIA_LOG_H
|
||||||
|
|
||||||
|
/***
|
||||||
|
* @Nm Log
|
||||||
|
* @Nd A simple logging framework for logging to multiple destinations.
|
||||||
|
* @Dd April 27 2023
|
||||||
|
* @Xr Stream
|
||||||
|
*
|
||||||
|
* .Nm
|
||||||
|
* is a simple C logging library that allows for colorful outputs,
|
||||||
|
* timestamps, and custom log levels. It also features the ability to
|
||||||
|
* have multiple logs open at one time, although Telodendria primarily
|
||||||
|
* utilizes the global log. All logs are thread safe.
|
||||||
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <syslog.h>
|
#include <syslog.h>
|
||||||
|
@ -34,28 +47,67 @@
|
||||||
#define LOG_FLAG_COLOR (1 << 0)
|
#define LOG_FLAG_COLOR (1 << 0)
|
||||||
#define LOG_FLAG_SYSLOG (1 << 1)
|
#define LOG_FLAG_SYSLOG (1 << 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A log is defined as a configuration that describes the properties
|
||||||
|
* of the log. This opaque structure can be manipulated by the
|
||||||
|
* functions defined in this API.
|
||||||
|
*/
|
||||||
typedef struct LogConfig LogConfig;
|
typedef struct LogConfig LogConfig;
|
||||||
|
|
||||||
extern LogConfig *
|
/**
|
||||||
LogConfigCreate(void);
|
* Create a new log configuration with sane defaults that can be used
|
||||||
|
* immediately with the logging functions.
|
||||||
|
*/
|
||||||
|
extern LogConfig * LogConfigCreate(void);
|
||||||
|
|
||||||
extern LogConfig *
|
/**
|
||||||
LogConfigGlobal(void);
|
* Get the global log configuration, creating a new one with
|
||||||
|
* .Fn LogConfigCreate
|
||||||
|
* if necessary.
|
||||||
|
*/
|
||||||
|
extern LogConfig * LogConfigGlobal(void);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
LogConfigFree(LogConfig *);
|
* Free the given log configuration. Note that this does not close the
|
||||||
|
* underlying stream associated with the log, if there is one. Also
|
||||||
|
* note that to avoid memory leaks, the global log configuration must
|
||||||
|
* also be freed, but it cannot be used after it is freed.
|
||||||
|
*/
|
||||||
|
extern void LogConfigFree(LogConfig *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
LogConfigLevelSet(LogConfig *, int);
|
* Set the current log level on the specified log configuration.
|
||||||
|
* This indicates that only messages at or above this level should be
|
||||||
|
* logged; all others are silently discarded. The passed log level
|
||||||
|
* should be one of the log levels defined by
|
||||||
|
* .Xr syslog 3 .
|
||||||
|
* Refer to that page for a complete list of acceptable log levels,
|
||||||
|
* and note that passing an invalid log level will result in undefined
|
||||||
|
* behavior.
|
||||||
|
*/
|
||||||
|
extern void LogConfigLevelSet(LogConfig *, int);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
LogConfigIndent(LogConfig *);
|
* Cause the log output to be indented two more spaces than it was
|
||||||
|
* previously. This can be helpful when generating stack traces or
|
||||||
|
* other hierarchical output. This is a simple convenience wrapper
|
||||||
|
* around
|
||||||
|
* .Fn LogConfigIndentSet .
|
||||||
|
*/
|
||||||
|
extern void LogConfigIndent(LogConfig *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
LogConfigUnindent(LogConfig *);
|
* Cause the log output to be indented two less spaces than it was
|
||||||
|
* previously. This is a simple convenience wrapper around
|
||||||
|
* .Fn LogConfigIndentSet .
|
||||||
|
*/
|
||||||
|
extern void LogConfigUnindent(LogConfig *);
|
||||||
|
|
||||||
extern void
|
/**
|
||||||
LogConfigIndentSet(LogConfig *, size_t);
|
* Indent future log output using the specified config by some
|
||||||
|
* arbitrary amount.
|
||||||
|
*/
|
||||||
|
extern void LogConfigIndentSet(LogConfig *, size_t);
|
||||||
|
|
||||||
extern void
|
extern void
|
||||||
LogConfigOutputSet(LogConfig *, Stream *);
|
LogConfigOutputSet(LogConfig *, Stream *);
|
||||||
|
|
Loading…
Reference in a new issue