38-Moths

Introduction

38-Moths is a web-framework written for masochists and tribal rituals.

Greshunkel

Introduction

GRESHUNKEL is the super weird templating language built for 38-Moths. It was originally built as a stand-alone static site generator, but now it's morphed into several disparate implementations.

GRESHUNKEL uses the X-Box Live syntax, which is mostly just xXx sprinkled everywhere, for funsies. A good place to view different syntax and implementation details is on the GREHSUNKEL page for 38-Moths. Specific C-Level API details can be found on the Documentation page.

Filters

GRESHUNKEL has the ability to blend in custom filters (Think Django-style Python functions that can be called while evaluating a template). Here's a trivial example:

<!DOCTYPE html>
<!-- index.html -->
<html>
	<body>
		<p>XxX return_z "Test Argument" XxX</p>"
		<p>XxX return_z xXx @i xXx XxX</p>"
	</body>
<html>

And then for the C portion:

/* The filter function itself. */
char *return_z(const char *argument) {
	UNUSED(argument);
	return "z";
}

/* The template handling code. */ int template_example(const m38_http_request *request, m38_http_response *response) { greshunkel_ctext *ctext = gshkl_init_context(); gshkl_add_filter(ctext, "return_z", &return_z, NULL); gshkl_add_int(ctext, "i", 10); return m38_render_file(ctext, "./index.html", response); }

Theres a couple things going on here, but it's pretty simple. GRESHUNKEL filters are things that just take string arguments and return strings. You have the option to pass in a clean-up handler when adding the filter, which is an opportune time to add something to free() memory.

Python Bindings

GREHSUNKEL has Python bindings, which are AWESOME. I don't think I've used them for anything but I wrote them anyway. It's pretty simple to use, and very Flask/Django-esque. Heres an example, it's pretty much as you'd expect:

from greshunkel import Template, Context, GshklFilterFunc

test_template =\ """ <html> <body> xXx SCREAM _include.html xXx xXx LOOP i LOOP_TEST xXx <li>xXx @TEST xXx xXx @i xXx</li> xXx BBL xXx <span>This is the real xXx @TRICKY xXx xXx @ONE xXx</span> <p>This is a regular string: xXx @TEST xXx</p> <p>This is an integer: xXx @FAKEINT xXx</p> <ul> xXx LOOP i LOOP_TEST xXx <li>XxX return_z xXx @i xXx XxX</li> xXx BBL xXx </ul> <p>Context Interpolation:</p> <p>xXx @sub.name xXx - xXx @sub.other xXx</p> <p>XxX return_hello doesnt_matter_at_all XxX</p> xXx LOOP subs SUB_LOOP_TEST xXx <p>FILTERS IN FILTERS IN LOOPS: XxX return_z F XxX</p> <p>XxX return_hello f XxX</p> <p>xXx @subs.name xXx - xXx @subs.other xXx</p> xXx BBL xXx </body> </html> """

def return_hello(arg): return b"test"

def return_z(arg): return b"z"

def main(): return_helloc = GshklFilterFunc(return_hello) return_zc = GshklFilterFunc(return_z) context = Context({ "TEST": "This is a test.", "FAKEINT": 666, "TRICKY": "TrIcKy", "ONE": 1, "LOOP_TEST": ["a", "b", "c", 1, 2, 3], "SUB_LOOP_TEST": [ {"name": "One", "other": 1 }, {"name": "Two", "other": 2 }, {"name": "Three", "other": 3 }, ], "return_hello": return_helloc, "return_z": return_zc, "sub": { "name": "test", "other": 777 }, }) template = Template(test_template) print(template.render(context))

The main idea is that you define a Context(), a Template() and add whatever filters you want to the context as well (here we use return_zc and return_hello). As far as I know it didn't crash the last time I ran it.

Conditionals

GRESHUNKEL uses the UNLESS and UNLESS NOT operators from Ruby, which are objectively easier to think about than if and if not. If you're having trouble thinking about which is which because you grew up writing C, then just think of them as if statements with an extra inversion, so unless becomes if not and unless not becomes if not not becomes if. Get it? Easy!

xXx UNLESS @user xXx
    No user.
xXx ENDLESS xXx

This will display No user. if the @user variable is not set in the greshunkel_context. Heres a rough guide to truthy/falsey statements in GRESHUNKEL. There does not exist a boolean type, so good luck.

subcontexts = True
integers = False
strings = True as long as they aren't the string FALSE
arrays = False

38-Moths

Macros

VERSION

#define VERSION "0.4"

The current version of the 38-Moths.

VERB_SIZE

#define VERB_SIZE 16

The maximum size of an HTTP verb.

MAX_MATCHES

#define MAX_MATCHES 4

The maximum number of matches one can have on a given url.

MAX_READ_LEN

#define MAX_READ_LEN 1024

The maximum amount of bytes to be read when receiving a request.

MAX_REQUEST_SIZE

#define MAX_REQUEST_SIZE 2097152

The maximum amount of bytes to be read from any single request. Default set to 2Mb.

RESPONSE_OK

#define RESPONSE_OK(status_code) (status_code >= 200 && status_code < 400)

Macro used to check whether a status code is 'good'.

WISDOM_OF_WORDS

#define WISDOM_OF_WORDS 32

The maximum size of a greshunkel variable name.

MAX_GSHKL_STR_SIZE

#define MAX_GSHKL_STR_SIZE 512

The maximum size of a greshunkel string.

INT_LEN(x)

#define INT_LEN(x) x == 0 ? 1 : floor(log10(abs(x))) + 1

Returns the length of an integer if it were rendered as a string.

UINT_LEN(x)

#define UINT_LEN(x) x == 0 ? 1 : floor(log10(x)) + 1

Returns the length of an unsigned integer if it were rendered as a string.

UNUSED(x)

#define UNUSED(x) (void)x

If an argument is unused, use this to avoid compiler warnings. Use with care.

HASH_STR_SIZE

#define HASH_STR_SIZE 65

The length of a 64-character hash string + NULL terminator. Used for the fnv1a function.

Type Definitions

Enums

greshunkel_type

typedef enum {
	GSHKL_ARR,
	GSHKL_STR,
	GSHKL_SUBCTEXT
} greshunkel_type;

Used to tell greshunkel_vars apart.

GSHKL_ARR: A greshunkel array.

GSHKL_STR: A greshunkel string.

Structures

m38_range_header

typedef struct {
	const size_t limit;
	const size_t offset;
} m38_range_header;

Used to represent the range header in HTTP requests.

limit: The limit of the range, eg. the length.

offset: The offset, in the file, that the user has requested.

m38_http_request

typedef struct {
	char verb[VERB_SIZE];
	char resource[512];
	regmatch_t matches[MAX_MATCHES];
	char *full_header;
	size_t header_len;
	unsigned char *full_body;
	size_t body_len;
	struct sparse_dict *form_elements;
} m38_http_request;

A representation of an HTTP request object. This will be passed to views.

verb: The HTTP verb for the given request.

resource[128]: The path for this request. (eg. '/articles/182')

matches: Any REGEX matches from your path are stored here.

header_len: The length of the header, or 0.

*full_header: The full header text of the request.

body_len: The length of the POST body, or 0.

*full_body: The full body of the request.

*form_elements: If the requests was submitted as Form-Encoded, and parsed correctly, this is the populated dictionary of pairs.

m38_http_response

typedef struct {
	unsigned char *out;
	size_t outsize;
	char mimetype[32];
	void *extra_data;
	struct vector *extra_headers;
} m38_http_response;

Fill this out and return it, signed by your parents. Only *out and outsize are really necessary.

*out: A buffer of characters that will be written back to the requester.

outsize: The size of out.

mimetype[32]: Optional, will be inferred from the m38_http_request's file extension if left blank.

*extra_data: Optional, use this to pass things to the clean up function. For instance, mmap_file() uses extra_data to store the size of the file allocated.

*extra_headers: A vector containing extra headers. Use `insert_custom_header` to manage this parameter.

m38_header_pair

typedef struct {
	const char *header;
	const size_t header_len;
	const char *value;
	const size_t value_len;
} m38_header_pair;

Object used to hold extra header information in an m38_http_request object.

*header: The actual header, eg. "Content-Length"

header_len: Length of the header, in bytes.

*value: The value of the header, eg. "1762"

value_len: Length of the value, in bytes.

m38_route

typedef struct {
	char verb[VERB_SIZE];
	char name[64];
	char route_match[256];
	size_t expected_matches;
	int (*handler)(const m38_http_request *request, m38_http_response *response);
	void (*cleanup)(const int status_code, m38_http_response *response);
} m38_route;

An array of these is how 38-Moths knows how to route requests.

verb[VERB_SIZE]: The verb that this route will handle.

name[64]: The name of the route. Used only logging.

route_match[256]: The POSIX regular expression used to match the route to your handler.

(*handler): A function pointer to your route handler.

(*cleanup): If your route needs to do any cleanup (eg. de-allocating memory), this function will becalled when 38-Moths is done with it.

m38_app

typedef struct {
	int *main_sock_fd;
	int port;
	int num_threads;
	const m38_route *routes;
	const int num_routes;

State and data information for a 38-Moths instance.

*main_sock_fd: A pointer to the main socket fd. This is a pointer so you can handle SIG* cleanly and shut down the socket.

port: The main port to run the server on. Like 8080. Or something.

num_threads: The number of threads to use to handle requests.

*routes: The array of all routes for your application.

num_routes: The number of routes in *routes.

*r_404_route: A route representing a custom 404 handler.

*r_error_route: A route representing a custom 500/error handler.

greshunkel_ctext

typedef struct greshunkel_ctext {
	vector *values;
	vector *filter_functions;
	const struct greshunkel_ctext *parent;
} greshunkel_ctext;

The context is a collection of variables, filters and other stuff that will be used to render a file.

*values: The values stored in this context. Strings, arrays, etc.

*filter_functions: The filter functions stored in this context.

*parent: Used internally, this is used to recurse back up to the parent context if a variable is unavailable in the current one.

greshunkel_named_item

typedef struct greshunkel_named_item {
	char name[WISDOM_OF_WORDS];
} greshunkel_named_item;

Dirty fucking hack to have a generic baseclass-like thing for both greshunkel_tuple and greshunkel_filter objects.

name[WISDOM_OF_WORDS]: The name of this thing. Somehow works. C is fucking scary.

greshunkel_tuple

typedef struct greshunkel_tuple {
	char name[WISDOM_OF_WORDS];
	const greshunkel_type type;
	greshunkel_var value;
} greshunkel_tuple;

Basically the representation of a GRESHUNKEL variable inside of a context.

name[WISDOM_OF_WORDS]: The name of this thing.

type: The type of this thing.

value: The actual value of this thing.

greshunkel_filter

typedef struct greshunkel_filter {
	char name[WISDOM_OF_WORDS];
	char *(*filter_func)(const char *argument);
	void (*clean_up)(char *result);
} greshunkel_filter;

A function that can be applied during template rendering.

name[WISDOM_OF_WORDS]: The name of this thing.

*(*filter_func): Function pointer to the C function you want to have access to in the template.

(*clean_up): If your filter needs any kind of cleanup, set this to a function other than null and you can do whatever.

vector

typedef struct vector {
	const size_t item_size;
	size_t max_size;
	size_t count;
	void *items;
} vector;

A simple vector object. Auto-expands and whatever.

item_size: The maximum size of each item.

max_size: Used internally to track the current vector's maximum number of elements.

count: Used internally to track the current vector's current count of items.

*items: The actual memory used for the items stored.

Functions

m38_set_404_handler

int m38_set_404_handler(m38_app *app,
	int (*handler)(const m38_http_request *request, m38_http_response *response));

Sets the internal 404 handler. Useful for templating your 404 page.

*app: The app to serve.

*handler: The function pointer to your handler function..

Returns: 0 on success, -1 on failure.

m38_set_404_cleanup

int m38_set_404_cleanup(m38_app *app,
	void (*cleanup)(const int status_code, m38_http_response *response));

Sets the cleanup for your custom 404 handler.

*app: The app to serve.

*cleanup: The function pointer to your cleanup function.

Returns: 0 on success, -1 on failure.

m38_set_500_handler

//int m38_set_500_handler(m38_app *app,
//	int (*handler)(const m38_http_request *request, m38_http_response *response));

Sets the internal error handler. Useful for templating your internal error page.

*app: The app to serve.

Returns: 0 on success, -1 on failure.

m38_parse_range_header

m38_range_header m38_parse_range_header(const char *range_query);

Figures out the range header for a request, if present.

*range_query: The text from the header.

Returns: Returns a 0/0 range header on failure/absence, or the correct limit/offset if present.

m38_get_header_value_request

char *m38_get_header_value_request(const m38_http_request *req, const char header[static 1]);

Gets the value of `header` (eg. Content-Length) from an http_request object. Wraps get_header_value_raw.

Returns: The char string representing the header value, or NULL. Must be free'd.

m38_get_header_value_raw

char *m38_get_header_value_raw(const char *request, const size_t request_siz, const char header[static 1]);

Gets the value of `header` (eg. Content-Length) from a raw http request string.

Returns: The char string representing the header value, or NULL. Must be free'd.

m38_parse_request

int m38_parse_request(const unsigned char *, const size_t, m38_http_request *);

Turns a raw string buffer into an http_request object.

Returns: 0 on sucess, -1 on failure.

m38_parse_body

int m38_parse_body(const size_t received_body_len, const size_t content_length_num,
		const unsigned char *raw_request, m38_http_request *request);

Figures out the actual body on the HTTP request and sticks it into the request object.

Returns: 0 on sucess, -1 on failure.

m38_mmap_file

int m38_mmap_file(const char *file_path, m38_http_response *response);

The primary way of serving static assets in 38-Moths. mmap()'s a file into memory and writes it to the requester.

*file_path: The file to mmap().

*response: The m38_http_response object your handler was passed.

Returns: An HTTP status code. 200 on success, 404 on not found, etc.

m38_render_file

int m38_render_file(const struct greshunkel_ctext *ctext, const char *file_path, m38_http_response *response);

The easiest way to render a file with GRESHUNKEL.

*ctext: The context you want your file to have. This should contain all variables, loops, etc.

*file_path: The template to render.

*response: The m38_http_response object your handler was passed.

Returns: An HTTP status code. 200 on success, 404 on not found, etc.

m38_return_raw_buffer

int m38_return_raw_buffer(const char *buf, const size_t buf_size, m38_http_response *response);

If you just want to return a raw string, use this.

*buf: The string to send back to the client.

buf: The length of the buffer, in bytes.

*response: The m38_http_response object your handler was passed.

Returns: An HTTP status code. 200 on success, 404 on not found, etc.

m38_heap_cleanup

void m38_heap_cleanup(const int status_code, m38_http_response *response);

Simple function that free()'s memory in out, but only if the response code was OK.

status_code: The status code returned from the handler.

*response: The m38_http_response object returned from the handler.

Returns: Nothing.

m38_heap_cleanup_no_check

void m38_heap_cleanup_no_check(const int status_code, m38_http_response *response);

Simple function that free()'s memory in out, always. Good for a custom 404 handler. See m38_heap_cleanup.

status_code: The status code returned from the handler.

*response: The m38_http_response object returned from the handler.

Returns: Nothing.

m38_mmap_cleanup

void m38_mmap_cleanup(const int status_code, m38_http_response *response);

The cleanup handler for mmap_file. Expects *extradata to be a struct stat object.

status_code: The status code returned from the handler.

*response: The m38_http_response object returned from the handler.

Returns: Nothing.

m38_generate_response

m38_handled_request *m38_generate_response(const int accept_fd, const m38_app *app);

Generates and HTTP response from an accepted connection

accept_fd: The successfully accept(2)'d file descriptor for the requester's socket.

*app: The app to serve routes from.

Returns: A fully formatted HTTP response, NULL otherwise.

m38_send_response

m38_handled_request *m38_send_response(m38_handled_request *hreq);

Takes a handled_request object rom generate_response and sends chunks of it down the wire.

*hreq: A handled_request object either from send_response or generate_response.

Returns: If there is anything left to send, an updated handled_request will be returned. NULL will be reutnred when the object has either been fully sent, or errored out.

gshkl_init_context

greshunkel_ctext *gshkl_init_context();

Used to create a new, empty GRESHUNKEL context.

Returns: A new greshunkel context.

gshkl_free_context

void gshkl_free_context(greshunkel_ctext *ctext);

Frees an cleans up a previously created context.

Returns: Nothing.

gshkl_add_string

int gshkl_add_string(greshunkel_ctext *ctext, const char name[WISDOM_OF_WORDS], const char *value);

Adds a string with the given name to a context.

*ctext: The context to add the string to.

name[WISDOM_OF_WORDS]: The name used to reference this variable later.

*value: The NULL terminated string that will be returned later.

Returns: 0 on success, 1 otherwise.

gshkl_add_int

int gshkl_add_int(greshunkel_ctext *ctext, const char name[WISDOM_OF_WORDS], const int value);

Adds an integer with the given name to a context.

*ctext: The context to add the integer to.

name[WISDOM_OF_WORDS]: The name used to reference this variable later.

value: The integer that will be added to this context.

Returns: 0 on success, 1 otherwise.

gshkl_add_array

greshunkel_var gshkl_add_array(greshunkel_ctext *ctext, const char name[WISDOM_OF_WORDS]);

Creates a new array object inside of the given context.

*ctext: The context to add the array to.

name[WISDOM_OF_WORDS]: The name used to reference this variable later.

Returns: The newly created loop object.

gshkl_add_string_to_loop

int gshkl_add_string_to_loop(greshunkel_var *loop, const char *value);

Adds a string to a greshunkel array.

*loop: A pointer to a loop created with gshkl_add_array.

*value: The NULL terminated string to be added.

Returns: 0 on success, 1 otherwise.

gshkl_add_int_to_loop

int gshkl_add_int_to_loop(greshunkel_var *loop, const int value);

Adds an integer to a greshunkel array.

*loop: A pointer to a loop created with gshkl_add_array.

value: The integer to be added.

Returns: 0 on success, 1 otherwise.

gshkl_add_sub_context_to_loop

int gshkl_add_sub_context_to_loop(greshunkel_var *loop,
		const greshunkel_ctext *child);

Adds a name sub-context to a loop.

*loop: The loop to add the context to.

*child: The pre-built child context.

Returns: 0 on success, 1 otherwise.

gshkl_add_sub_context

int gshkl_add_sub_context(greshunkel_ctext *parent,
		const char name[WISDOM_OF_WORDS],
		const greshunkel_ctext *child);

Adds a name sub-context to a parent context. Sub-context values can be references via the 'x Xx @. xX x' syntax. Contexts added in this way will be freed by the parent.

*parent: The parent context to add the child to.

name: The name of the child context.

*child: The pre-built child context.

Returns: 0 on success, 1 otherwise.

gshkl_add_filter

int gshkl_add_filter(greshunkel_ctext *ctext,
		const char name[WISDOM_OF_WORDS],
		char *(*filter_func)(const char *argument),
		void (*clean_up)(char *filter_result));

Adds a filter function to the given context.

*ctext: The context that the filter will be added to.

name[WISDOM_OF_WORDS]: The name used to reference this filter function.

(*filter_func): The function that will be called from the template.

(*clean_up): The clean up function that will be called after GRESHUNKEL is done calling filter_func.

Returns: 0 on success, 1 otherwise.

gshkl_filter_cleanup

void gshkl_filter_cleanup(char *result);

Commonly used helper clean-up function that just calls free on the result.

*result: The value obtained from calling your filter function.

Returns: Nothing.

endswith

int endswith(const char *string, const char *suffix);

Helper function determine if a string ends with another string.

*string: The source string to test.

*suffix: The suffix to check for.

Returns: 1 if string ends with suffix.

strnstr

char *strnstr(const char *haystack, const char *needle, size_t len);

Like strstr, but only checks up to n chars.

*haystack: Go look at strstr().

*needle: Go look at strstr().

len: Maximum number of characters to look through.

Returns: Whatever the hell strstr() returns.

get_file_creation_date

time_t get_file_creation_date(const char *file_path);

Gets the file creation date.

*file_path: The path of the file to get the creation date of.

Returns: The file creation date.

get_file_size

size_t get_file_size(const char *file_path);

Gets the file size.

*file_path: The path of the file to get the size of.

Returns: The file size.

hash_string_fnv1a

int hash_string_fnv1a(const unsigned char *string, const size_t siz, char outbuf[static HASH_STR_SIZE]);

Hashes a string using the FNV-1a algorithm

*string: The string to hash.

siz: The size of the string.

outbuf[static HASH_STR_SIZE]: This buffer will be filled out with the hashed string.

Returns: 1. Always. I don't care what you think.

char *m38_get_cookie_value(const char *cookie_string, const size_t cookie_string_siz, const char *needle);

Gets a specific value out of a cookie, like the session ID.

*cookie_string: The value of the entire cookie string to parse, which comes from m38_get_header_value.

cookie_string_siz: The length of cookie_string.

needle: The value you're looking for inside the cookie, like `sessionid`.

Returns: NULL or the value of the specific cookie value.

vector_new

vector *vector_new(const size_t item_size, const size_t initial_element_count);

Creates a new vector object.

item_size: The maximum size of each item.

initial_element_count: If you know your amount of objects ahead of time, set this accordingly. Otherwise just guess. The closer you get the fewer mallocs will happen.

Returns: A new vector object.

vector_append

int vector_append(vector *vec, const void *item, const size_t item_size);

Adds a new element to a vector.

*vec: The vector to add the new item to.

*item: The item to add to the vector.

item_size: The size of the item to be added.

Returns: 1 on success.

vector_append_ptr

int vector_append_ptr(vector *vec, const void *pointer);

Similar to vector_append but copies just the pointer value, not what it points to.

*vec: The vector to add the pointer to.

*pointer: The item to add to the vector.

Returns: 1 on success.

vector_get

const void *vector_get(const vector *vec, const unsigned int i);

Gets the nth element of the given vector.

*vec: The vector to get the item from.

i: The item you want to retrieve.

Returns: A constant pointer to the nth item in the vector.

vector_reverse

int vector_reverse(vector *vec);

Reverses the vector, from beginning to end.

*vec: The vector to be reversed.

Returns: 1 on success.

vector_free

void vector_free(vector *to_free);

Cleans up and removes a vector's allocated memory.

*to_free: The vector to free.

Returns: Nothing.