Compare commits

...

10 Commits

23 changed files with 639 additions and 1073 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ build
gpp1
watcher
commit.h
compile_flags.txt
bundle.zip

3
.gitmodules vendored
View File

@ -16,3 +16,6 @@
[submodule "md5-c"]
path = md5-c
url = https://github.com/Zunawe/md5-c.git
[submodule "zip"]
path = zip
url = https://github.com/kuba--/zip.git

View File

@ -3,3 +3,6 @@
./gpp1
./watcher
./mongoose.o
./bundle.zip
./compile_flags.txt
./commit.h

View File

@ -7,4 +7,6 @@
# define CONFIG_LISTEN_URL "http://localhost:5000"
#endif
#define BUNDLE_ZIP_COMPRESSION 10
#endif // CONFIG_H_

96
baked.c
View File

@ -3,47 +3,16 @@
#include "gebs/gebs.h"
#include "incbin/incbin.h"
#include "stb/stb_ds.h"
#include "zip.h"
#include "baked.h"
#include "locked.h"
#include "CONFIG.h"
INCBIN(gpp1, "./gpp1");
INCBIN(home_html, "./tmpls/home.html");
INCBIN(page_missing_html, "./tmpls/page-missing.html");
INCBIN(template_blog_html, "./tmpls/template-blog.html");
INCBIN(blog_html, "./tmpls/blog.html");
INCBIN(simple_css, "./etc/simple.css");
INCBIN(favicon_ico, "./etc/favicon.ico");
#if MY_DEBUG
INCBIN(hotreload_js, "./etc/hotreload.js");
#endif
INCBIN(theme_js, "./etc/theme.js");
INCBIN(highlight_js, "./etc/highlight.js");
INCBIN(hljs_rainbow_css, "./etc/hljs-rainbow.css");
INCBIN(marked_js, "./etc/marked.js");
INCBIN(me_jpg, "./etc/me.jpg");
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
INCBIN(tmoa_garbage_jpg, "./etc/tmoa-garbage.jpg");
INCBIN(blog_welcome_md, "./blog/welcome.md");
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
INCBIN(bundle_zip, "./bundle.zip");
static locked(Baked_Resource *) baked_resources = locked_init(nil);
void lock_baked_resources(void)
{
lockx(&baked_resources);
}
void unlock_baked_resources(void)
{
unlockx(&baked_resources);
}
void add_baked_resource(char *key, const uchar *data, size_t size)
{
int fd = memfd_create(key, 0);
@ -52,54 +21,55 @@ void add_baked_resource(char *key, const uchar *data, size_t size)
abort();
}
write(fd, data, size);
shput(baked_resources.value, key, fd);
shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
LOGI("Added baked resource: %s\n", key);
}
void init_baked_resources(void)
{
lockx(&baked_resources);
add_baked_resource("home.html", home_html_data, home_html_size);
add_baked_resource("page-missing.html", page_missing_html_data, page_missing_html_size);
add_baked_resource("template-blog.html", template_blog_html_data, template_blog_html_size);
add_baked_resource("blog.html", blog_html_data, blog_html_size);
add_baked_resource("gpp1", gpp1_data, gpp1_size);
add_baked_resource("simple.css", simple_css_data, simple_css_size);
add_baked_resource("favicon.ico", favicon_ico_data, favicon_ico_size);
#if MY_DEBUG
add_baked_resource("hotreload.js", hotreload_js_data, hotreload_js_size);
#endif
add_baked_resource("theme.js", theme_js_data, theme_js_size);
add_baked_resource("highlight.js", highlight_js_data, highlight_js_size);
add_baked_resource("hljs-rainbow.css", hljs_rainbow_css_data, hljs_rainbow_css_size);
add_baked_resource("marked.js", marked_js_data, marked_js_size);
add_baked_resource("me.jpg", me_jpg_data, me_jpg_size);
add_baked_resource("tmoa-engine.jpg", tmoa_engine_jpg_data, tmoa_engine_jpg_size);
add_baked_resource("tmoa-garbage.jpg", tmoa_garbage_jpg_data, tmoa_garbage_jpg_size);
add_baked_resource("blog-welcome.md", blog_welcome_md_data, blog_welcome_md_size);
add_baked_resource("blog-weird-page.md", blog_weird_page_md_data, blog_weird_page_md_size);
add_baked_resource("blog-curious-case-of-gebs.md", blog_curious_case_of_gebs_md_data, blog_curious_case_of_gebs_md_size);
add_baked_resource("blog-the-making-of-aboba.md", blog_the_making_of_aboba_md_data, blog_the_making_of_aboba_md_size);
unlockx(&baked_resources);
LOGI("Initializing baked resources...\n");
struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
size_t n = zip_entries_total(zip);
for (size_t i = 0; i < n; i++) {
zip_entry_openbyindex(zip, i);
const char *name = strdup(zip_entry_name(zip));
size_t size = zip_entry_size(zip);
char *buf = malloc(size);
zip_entry_noallocread(zip, buf, size);
add_baked_resource((char *)name, buf, size);
zip_entry_close(zip);
}
zip_stream_close(zip);
LOGI("baked resources done\n");
}
void free_baked_resources(void)
{
lockx(&baked_resources);
LOGI("freeing baked resouces\n");
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
close(baked_resources.value[i].value);
close(baked_resources.value[i].value.memfd);
free(baked_resources.value[i].key);
free(baked_resources.value[i].value.bufptr);
}
shfree(baked_resources.value);
unlockx(&baked_resources);
LOGI("baked resources done\n");
}
bool get_baked_resource_path(char *key, char *buf, size_t size)
{
LOGI("Request for baked resource path: %s\n", key);
lockx(&baked_resources);
if (shgeti(baked_resources.value, key) != -1) {
int fd = shget(baked_resources.value, key);
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd);
Baked_Resource_Value brv = shget(baked_resources.value, key);
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), brv.memfd);
LOGI("Memfd path %s\n", buf);
unlockx(&baked_resources);
return true;
}
unlockx(&baked_resources);
return false;
}

34
baked.h
View File

@ -1,43 +1,19 @@
#ifndef BAKED_H_
#define BAKED_H_
#include "incbin/incbin.h"
INCBIN_EXTERN(gpp1);
INCBIN_EXTERN(home_html);
INCBIN_EXTERN(page_missing_html);
INCBIN_EXTERN(template_blog_html);
INCBIN_EXTERN(blog_html);
INCBIN_EXTERN(simple_css);
INCBIN_EXTERN(favicon_ico);
#if MY_DEBUG
INCBIN_EXTERN(hotreload_js);
#endif
INCBIN_EXTERN(theme_js);
INCBIN_EXTERN(highlight_js);
INCBIN_EXTERN(hljs_rainbow_css);
INCBIN_EXTERN(marked_js);
INCBIN_EXTERN(me_jpg);
INCBIN_EXTERN(tmoa_engine_jpg);
INCBIN_EXTERN(tmoa_garbage_jpg);
INCBIN_EXTERN(blog_welcome_md);
INCBIN_EXTERN(blog_weird_page_md);
INCBIN_EXTERN(blog_curious_case_of_gebs_md);
INCBIN_EXTERN(blog_the_making_of_aboba_md);
typedef struct {
int memfd;
void *bufptr;
} Baked_Resource_Value;
typedef struct {
char *key; // path
int value; // memfd
Baked_Resource_Value value;
} Baked_Resource;
void init_baked_resources(void);
void free_baked_resources(void);
bool get_baked_resource_path(char *key, char *buf, size_t size);
void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata);
void lock_baked_resources(void);
void unlock_baked_resources(void);
#endif // BAKED_H_

View File

@ -0,0 +1,273 @@
# Asset packing with [zip.c](https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c)
One of the technical decisions that I had to make when writing this website is the way that I'm going to
distribute the assets (images, GPP, page templates, etc.). I've decided to go with packing or baking-in
the assets into the main binary, so that I don't have to worry about updating an asset folder alongside
the application.
One flaw of this approach is that as the website gets bigger, the binary will too. Fortunately I don't
have to worry about this too much right now, since I've reimplemented asset packing with the excellent
zip library by \`kuba--\` (Polska GUROM!!!111!!). In this article I'd like to demonstrate how I've
minimised the size of our assets using \`zip\` and \`incbin\`.
![zip file](/bakedres/apwz-zip-icon.png)
## References
- zip: https://raw.githubusercontent.com/kuba--/zip/refs/heads/master/src/zip.c
- incbin: https://github.com/graphitemaster/incbin
- zip format: https://en.wikipedia.org/wiki/ZIP_(file_format)
- the source code: https://git.kamkow1lair.pl/kamkow1/aboba
## Generating the bundle - \`bundle.zip\`
To compress our assets, first we need to generate a bundle file. I've decided to call it bundle.zip - simple
and descriptive.
Generating the bundle requires some changes to our build program. See https://git.kamkow1lair.pl/kamkow1/aboba/src/branch/master/build.c for reference.
\`\`\`
#define BUNDLED_FILES \\
"./gpp1", \\
"./tmpls/home.html", \\
"./tmpls/page-missing.html", \\
"./tmpls/template-blog.html", \\
"./tmpls/blog.html", \\
"./etc/hotreload.js", \\
"./etc/theme.js", \\
"./etc/simple.css", \\
"./etc/highlight.js", \\
"./etc/hljs-rainbow.css", \\
"./etc/marked.js", \\
"./etc/favicon.ico", \\
"./etc/me.jpg", \\
"./etc/tmoa-engine.jpg", \\
"./etc/tmoa-garbage.jpg", \\
"./blog/blog-welcome.md", \\
"./blog/blog-weird-page.md", \\
"./blog/blog-curious-case-of-gebs.md", \\
"./blog/blog-the-making-of-aboba.md", \\
"./blog/blog-asset-packing-with-zip.c.md"
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\\n");
}
RULE("./aboba",
"./main.c",
"./routes.c",
"./routes.h",
"./baked.c",
"./baked.h",
"./commit.h",
"./timer.c",
"./timer.h",
"./CONFIG.h",
"./locked.h",
"./mongoose.o",
"./bundle.zip",
BUNDLED_FILES
) {
// build mongoose.o - skipped
// Generate commit.h - skipped
#define CC "cc"
#define TARGET "-o", "aboba"
#if MY_DEBUG
#define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
#define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
#else
#define CFLAGS "-fPIC"
#define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \\
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
#endif
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
#define OBJECTS "./mongoose.o"
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
#define INC_FLAGS "-I.", "-I./zip/src"
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
// generate compile_flags.txt - skipped
// #undef macros - skipped
}
\`\`\`
If you go through the commit history, you'll see that apart from just generating the bundle file, I've also cleaned up the
build commands a bit with \`#define\`s. Let's take a closer look at the bundle generation code.
\`\`\`
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\\n");
}
\`\`\`
We declare a dependency (using \`RULE\*()\` macro), which says that \`./bundle.zip\` depends on files defined by \`BUNDLED_FILES\`.
To generate the bundle we use \`zip_open()\` \`zip_close()\`. To call \`zip_open()\`, we have to provide a so called "compression level".
The zip library provides us only with \`ZIP_DEFAULT_COMPRESSION_LEVEL\`, which is a macro that evaluates to 6. I wasn't satisfied with
it, so after looking at \`miniz.h\` (a backing library that zip uses), I've found that zip uses \`MZ_DEFAULT_COMPRESSION_LEVEL\`, which
is 6, but we can use the value of \`MZ_UBER_COMPRESSION\`, which is 10. This way we can achieve the most size-efficient compression.
I've decided to \`#define\` the compression level to avoid using arbitrary magic numbers and we can also use that definition both in
the application and in the build program. Here's how \`CONFIG.h\` looks like now:
\`\`\`
#ifndef CONFIG_H_
#define CONFIG_H_
#if MY_DEBUG
# define CONFIG_LISTEN_URL "http://localhost:8080"
#else
# define CONFIG_LISTEN_URL "http://localhost:5000"
#endif
#define BUNDLE_ZIP_COMPRESSION 10
#endif // CONFIG_H_
\`\`\`
The only "downside" here is that, since we're compressing so hard, it's going to take more time to generate the bundle. I've put "downside"
in quotes for purpose, because this does not apply in our case. The files that we're packing are quite small already and there aren't
many of them. We're just doing this to sqeeze out *extra* spacial performance.
Previously we were baking-in the assets like so:
from https://git.kamkow1lair.pl/kamkow1/aboba/src/commit/447362c74dda85838d37d6ee3cb218697abf6106/baked.c
\`\`\`
INCBIN(gpp1, "./gpp1");
INCBIN(home_html, "./tmpls/home.html");
INCBIN(page_missing_html, "./tmpls/page-missing.html");
INCBIN(template_blog_html, "./tmpls/template-blog.html");
INCBIN(blog_html, "./tmpls/blog.html");
INCBIN(simple_css, "./etc/simple.css");
INCBIN(favicon_ico, "./etc/favicon.ico");
#if MY_DEBUG
INCBIN(hotreload_js, "./etc/hotreload.js");
#endif
INCBIN(theme_js, "./etc/theme.js");
INCBIN(highlight_js, "./etc/highlight.js");
INCBIN(hljs_rainbow_css, "./etc/hljs-rainbow.css");
INCBIN(marked_js, "./etc/marked.js");
INCBIN(me_jpg, "./etc/me.jpg");
INCBIN(tmoa_engine_jpg, "./etc/tmoa-engine.jpg");
INCBIN(tmoa_garbage_jpg, "./etc/tmoa-garbage.jpg");
INCBIN(blog_welcome_md, "./blog/welcome.md");
INCBIN(blog_weird_page_md, "./blog/weird-page.md");
INCBIN(blog_curious_case_of_gebs_md, "./blog/curious-case-of-gebs.md");
INCBIN(blog_the_making_of_aboba_md, "./blog/the-making-of-aboba.md");
\`\`\`
Now that we have our \`bundle.zip\`, we do it like this:
\`\`\`
INCBIN(bundle_zip, "./bundle.zip");
\`\`\`
And there we go, we have our bundle!
I've also had to slightly change the way we add the assets to the resource hash table:
\`\`\`
void add_baked_resource(char *key, const uchar *data, size_t size)
{
int fd = memfd_create(key, 0);
if (fd < 0) {
LOGE("Could not create resource %s. Aborting...\n", key);
abort();
}
write(fd, data, size);
shput(baked_resources.value, key, ((Baked_Resource_Value){ .memfd = fd, .bufptr = (void *)data }));
}
void init_baked_resources(void)
{
struct zip_t *zip = zip_stream_open(bundle_zip_data, bundle_zip_size, BUNDLE_ZIP_COMPRESSION, 'r');
size_t n = zip_entries_total(zip);
for (size_t i = 0; i < n; i++) {
zip_entry_openbyindex(zip, i);
const char *name = strdup(zip_entry_name(zip));
size_t size = zip_entry_size(zip);
char *buf = malloc(size);
zip_entry_noallocread(zip, buf, size);
add_baked_resource((char *)name, buf, size);
zip_entry_close(zip);
}
zip_stream_close(zip);
}
\`\`\`
\`add_baked_resource()\` hasn't changed here, but \`init_baked_resources()\` has. Here we use one of zip's abilities,
which is unpacking an in-memory .zip file. We iterate each entry in the bundle, get the name and size, preallocate
a buffer and read said entry into the buffer. We can then add the resource to the hash table as we did previously.
## Conclusion
![xkcd](/bakedres/apwz-xkcd1.png)
One question you may ask is, how much space are we saving? I don't remeber the exact sizes of the binary, but
I remember that before compression it was \`~1.2M\` and now after compression is implemented, it's \`~1.6M\`. How is
that an improvement if the binary gained weight? That \`~.4M\` is likely due to zip format overhead - metadata, file and
directory headers and so on. What we gain here is that the binary hasn't changed in size much despite adding more files,
like for eg. this article that I'm writing right now. It has stayed at \`~1.6M\` so far and the size doesn't go up.
*(So far)* We aren't even making byte-sized gains, which can be checked with \`stat --printf "%s" ./aboba\`. What
we're gaining here is slowed down size increase. Previously if I wanted to add a 30K jpeg, the binary would literally
go up by 30K.

123
build.c
View File

@ -1,6 +1,9 @@
#include <libgen.h>
#define GEBS_NO_PREFIX
#define GEBS_IMPLEMENTATION
#include "gebs/gebs.h"
#include "./zip/src/zip.c"
#include "CONFIG.h"
char *prog = NULL;
@ -15,13 +18,62 @@ int main(int argc, char ** argv)
prog = SHIFT(&argc, &argv);
char *cmd = SHIFT(&argc, &argv);
if (strcmp(cmd, "make") == 0) {
#define BUNDLED_FILES \
"./gpp1", \
"./tmpls/home.html", \
"./tmpls/page-missing.html", \
"./tmpls/template-blog.html", \
"./tmpls/blog.html", \
"./etc/hotreload.js", \
"./etc/theme.js", \
"./etc/simple.css", \
"./etc/highlight.js", \
"./etc/hljs-rainbow.css", \
"./etc/marked.js", \
"./etc/favicon.ico", \
"./etc/me.jpg", \
"./etc/tmoa-engine.jpg", \
"./etc/tmoa-garbage.jpg", \
"./etc/apwz-zip-icon.png", \
"./etc/apwz-xkcd1.png", \
"./blog/blog-welcome.md", \
"./blog/blog-weird-page.md", \
"./blog/blog-curious-case-of-gebs.md", \
"./blog/blog-the-making-of-aboba.md", \
"./blog/blog-asset-packing-with-zip.c.md"
const char *bundle_zip_deps[] = { BUNDLED_FILES };
RULE_ARRAY("./bundle.zip", bundle_zip_deps) {
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
struct zip_t *zip = zip_open("./bundle.zip", BUNDLE_ZIP_COMPRESSION, 'w');
defer { zip_close(zip); }
for (size_t i = 0; i < sizeof(bundle_zip_deps)/sizeof(bundle_zip_deps[0]); i++) {
char *copy = strdup(bundle_zip_deps[i]);
defer { free(copy); }
char *name = basename(copy);
String_Builder sb = {0};
defer { sb_free(&sb); }
sb_read_file(&sb, bundle_zip_deps[i]);
zip_entry_open(zip, name);
zip_entry_write(zip, sb.items, sb.count);
zip_entry_close(zip);
}
LOGI("Generated bundle.zip\n");
}
RULE("./aboba",
"./main.c",
"./routes.c",
"./routes.h",
"./baked.c",
"./baked.h",
"./clonestr.h",
"./commit.h",
"./timer.c",
"./timer.h",
@ -29,28 +81,9 @@ int main(int argc, char ** argv)
"./locked.h",
"./mongoose.o",
"./gpp1",
"./tmpls/home.html",
"./tmpls/page-missing.html",
"./tmpls/template-blog.html",
"./tmpls/blog.html",
"./etc/hotreload.js",
"./etc/theme.js",
"./etc/simple.css",
"./etc/highlight.js",
"./etc/hljs-rainbow.css",
"./etc/marked.js",
"./etc/favicon.ico",
"./etc/me.jpg",
"./etc/tmoa-engine.jpg",
"./etc/tmoa-garbage.jpg",
"./blog/welcome.md",
"./blog/weird-page.md",
"./blog/curious-case-of-gebs.md",
"./blog/the-making-of-aboba.md"
"./bundle.zip",
BUNDLED_FILES
) {
RULE("./mongoose.o", "./mongoose/mongoose.c") {
@ -63,10 +96,6 @@ int main(int argc, char ** argv)
#endif
}
RULE("./gpp1", "./gpp/gpp.c") {
CMD("cc", "-DHAVE_STRDUP", "-DHAVE_FNMATCH_H", "-o", "gpp1", "gpp/gpp.c");
}
RULE("./commit.h") {
String_Builder commit = {0};
defer { sb_free(&commit); }
@ -95,18 +124,39 @@ int main(int argc, char ** argv)
}
}
#define CC "cc"
#define TARGET "-o", "aboba"
#if MY_DEBUG
CMD("cc", "-fsanitize=address", "-fPIC", "-ggdb", "-I.", "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c",
"./md5-c/md5.c",
"-lpthread");
#define CFLAGS "-fsanitize=address", "-fPIC", "-ggdb"
#define DEFINES "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c", "./md5-c/md5.c"
#else
CMD("cc", "-fPIC", "-I.", "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
"-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", "-Wl,-z,execstack", "-o", "./aboba",
"./main.c", "./routes.c", "./baked.c", "./timer.c", "./mongoose.o", "./cJSON/cJSON.c",
"-lpthread");
#define CFLAGS "-fPIC"
#define DEFINES "-DMY_DEBUG=0", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX", "-DINCBIN_PREFIX=", "-DINCBIN_STYLE=INCBIN_STYLE_SNAKE", \
"-DGEBS_ENABLE_PTHREAD_FEATURES"
#define EXTRA_SOURCES "./cJSON/cJSON.c", "./zip/src/zip.c"
#endif
#define SOURCES "./main.c", "./routes.c", "./baked.c", "./timer.c"
#define OBJECTS "./mongoose.o"
#define LINK_FLAGS "-Wl,-z,execstack", "-lpthread"
#define INC_FLAGS "-I.", "-I./zip/src"
CMD(CC, TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
RULE("./compile_flags.txt") {
make_compile_flags(TARGET, CFLAGS, DEFINES, INC_FLAGS, SOURCES, OBJECTS, EXTRA_SOURCES, LINK_FLAGS);
}
#undef CC
#undef TARGET
#undef CFLAGS
#undef DEFINES
#undef EXTRA_SOURCES
#undef SOURCES
#undef OBJECTS
#undef LINK_FLAGS
#undef INC_FLAGS
}
} else if (strcmp(cmd, "clean") == 0) {
remove1("./build");
@ -114,6 +164,9 @@ int main(int argc, char ** argv)
remove1("./mongoose.o");
remove1("./gpp1");
remove1("./watcher");
remove1("./compile_flags.txt");
remove1("./bundle.zip");
remove1("./commit.h");
} else if (strcmp(cmd, "make-watcher") == 0) {
CMD("cc", "-o", "watcher", "watcher.c");
} else if (strcmp(cmd, "watch") == 0) {

View File

@ -1,11 +1,5 @@
#ifndef CLONESTR_H_
#define CLONESTR_H_
#define clonestr(str) \
({ \
char *__p = malloc(strlen((str))+1); \
strcpy(__p, (str)); \
(__p); \
})
#endif // CLONESTR_H_

BIN
etc/apwz-xkcd1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

BIN
etc/apwz-zip-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

View File

@ -82,12 +82,29 @@ function neon_cyber()
document.documentElement.style.setProperty("--disabled", "#333333");
}
function dark_easy()
{
document.documentElement.style.setProperty("--bg", "#1A1A1A");
document.documentElement.style.setProperty("--accent-bg", "#2E2E2E");
document.documentElement.style.setProperty("--text", "#DCDCDC");
document.documentElement.style.setProperty("--text-light", "#A3A3A3");
document.documentElement.style.setProperty("--border", "#585858");
document.documentElement.style.setProperty("--accent", "#FF5722");
document.documentElement.style.setProperty("--accent-hover", "#F4511E");
document.documentElement.style.setProperty("--accent-text", "#1A1A1A");
document.documentElement.style.setProperty("--code", "#8BC34A");
document.documentElement.style.setProperty("--preformatted", "#A3A3A3");
document.documentElement.style.setProperty("--marked", "#FF7043");
document.documentElement.style.setProperty("--disabled", "#383838");
}
const themes = {
"light-breeze": light_breeze,
"plan9-acme": plan9_acme,
"bold-navy": bold_navy,
"soft-lavander": soft_lavander,
"neon-cyber": neon_cyber,
"dark-easy": dark_easy,
};

2
gebs

Submodule gebs updated: 6db36114d5...db8389f7d5

View File

@ -3,7 +3,7 @@
#include <pthread.h>
#define locked_init(x) { .value = (x), .lock = PTHREAD_MUTEX_INITIALIZER }
#define locked_init(x) { .value = x, .lock = PTHREAD_MUTEX_INITIALIZER }
#define locked(T) struct { T value; pthread_mutex_t lock; }
#define lockx(x) pthread_mutex_lock(&(x)->lock)

166
main.c
View File

@ -9,23 +9,39 @@
#include "mongoose/mongoose.h"
#define STB_DS_IMPLEMENTATION
#include "stb/stb_ds.h"
#include "md5-c/md5.h"
#include "routes.h"
#include "baked.h"
#include "timer.h"
#include "CONFIG.h"
#include "clonestr.h"
#include "locked.h"
static locked(Route *) route_hashtable = locked_init(nil);
static locked(char *) baked_dump_path = locked_init(nil);
char *clonestr_alloc(Allocator *alloc, char *s)
{
char *p = gebs_malloc(alloc, strlen(s)+1);
strcpy(p, s);
return p;
}
void *zmalloc_alloc(Allocator *alloc, size_t size)
{
void *p = gebs_malloc(alloc, size);
memset(p, 0, size);
return p;
}
void run_in_thread(void *(*f)(void *), void *p)
{
pthread_t tid = 0;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, f, p);
pthread_attr_destroy(&attr);
}
@ -33,91 +49,91 @@ void run_in_thread(void *(*f)(void *), void *p)
void *route_thread_function(void *param)
{
Route_Thread_Data *data = (Route_Thread_Data *)param;
Arena *arena = data->arena;
struct mg_http_message http_msg = {0};
int r = mg_http_parse(data->message.buf, data->message.len, &http_msg);
if (r <= 0) {
Route_Result result = {0};
result.status_code = 400;
result.type = ROUTE_RESULT_DYNAMIC;
/* list_append(&result.headers, clonestr("Content-Type: text/plain")); */
sb_append_nstr(&result.body, "Could not parse HTTP request");
sb_finish(&result.body);
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result->arena = arena;
result->status_code = 400;
result->type = ROUTE_RESULT_DYNAMIC;
sb_append_nstr_alloc(arena, &result->body, "Could not parse HTTP request");
sb_finish_alloc(arena, &result->body);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return nil;
pthread_exit(nil);
} else {
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result->arena = arena;
result->type = ROUTE_RESULT_STATIC;
char *p = gebs_malloc(arena, http_msg.uri.len+1);
strncpy(p, http_msg.uri.buf, http_msg.uri.len);
p[http_msg.uri.len] = 0;
char *file = basename(p);
char *full_path = gebs_malloc(arena, PATH_MAX);
lockx(&baked_dump_path);
snprintf(full_path, PATH_MAX, "%s/%s", baked_dump_path.value, file);
unlockx(&baked_dump_path);
sb_append_nstr_alloc(arena, &result->body, full_path);
sb_finish_alloc(arena, &result->body);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
pthread_exit(nil);
} else {
char key[MG_PATH_MAX] = {0};
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
lockx(&route_hashtable);
ssize_t idx = shgeti(route_hashtable.value, key);
Route_Result *result = zmalloc_alloc((Allocator *)arena, sizeof(*result));
result->arena = arena;
route_hashtable.value[idx].value((Allocator *)arena, &http_msg, result, route_hashtable.value[idx].context_data);
unlockx(&route_hashtable);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
pthread_exit(nil);
}
}
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
Route_Result result = {0};
result.type = ROUTE_RESULT_STATIC;
char *p = malloc(http_msg.uri.len+1);
strncpy(p, http_msg.uri.buf, http_msg.uri.len);
p[http_msg.uri.len] = 0;
char *file = basename(p);
char *full_path = malloc(PATH_MAX);
lockx(&baked_dump_path);
snprintf(full_path, PATH_MAX, "%s/%s", baked_dump_path.value, file);
unlockx(&baked_dump_path);
sb_append_nstr(&result.body, full_path);
sb_finish(&result.body);
free(full_path);
free(p);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return nil;
}
char key[MG_PATH_MAX] = {0};
strncpy(key, http_msg.uri.buf, http_msg.uri.len);
lockx(&route_hashtable);
ssize_t idx = shgeti(route_hashtable.value, key);
Route_Result result = {0};
route_hashtable.value[idx].value(&http_msg, &result, route_hashtable.value[idx].context_data);
unlockx(&route_hashtable);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return nil;
}
void event_handler(struct mg_connection *conn, int ev, void *ev_data)
{
if (ev == MG_EV_HTTP_MSG) {
LOGI("HTTP EVENT\n");
struct mg_http_message *msg = (struct mg_http_message *)ev_data;
LOGI("Route: %.*s\n", msg->uri.len, msg->uri.buf);
Route_Thread_Data *data = calloc(1, sizeof(*data));
data->message = mg_strdup(msg->message);
data->conn_id = conn->id;
data->mgr = conn->mgr;
data->arena = malloc(sizeof(*data->arena));
*data->arena = arena_get();
LOGI("starting handler thread...\n");
run_in_thread(&route_thread_function, data);
} else if (ev == MG_EV_WAKEUP) {
LOGI("WAKEUP EVENT\n");
struct mg_str *data = (struct mg_str *)ev_data;
Route_Result *result = (Route_Result *)data->buf;
defer {
for (size_t i = 0; i < result->headers.count; i++) {
free(result->headers.items[i]);
}
list_free(&result->headers);
sb_free(&result->body);
}
Route_Result *result = *(Route_Result **)data->buf;
Arena *arena = result->arena;
Gebs_String_Builder sb = {0};
defer { sb_free(&sb); }
nsl_join(&result->headers, &sb, "\r\n");
nsl_join_alloc(arena, &result->headers, &sb, "\r\n");
if (result->headers.count > 0) {
sb_append_nstr(&sb, "\r\n");
sb_append_nstr_alloc(arena, &sb, "\r\n");
}
sb_finish(&sb);
sb_finish_alloc(arena, &sb);
if (result->type == ROUTE_RESULT_DYNAMIC) {
mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items);
@ -125,6 +141,11 @@ void event_handler(struct mg_connection *conn, int ev, void *ev_data)
char *path = result->body.items;
mg_http_serve_file(conn, ev_data, path, &(struct mg_http_serve_opts){0});
}
arena_destroy(arena);
free(arena);
LOGI("WAKEUP done, arena cleaned\n");
}
}
@ -142,27 +163,23 @@ void route_hashtable_put_blogs(Baked_Resource *resource, void *udata)
void init_route_hashtable(void)
{
lockx(&route_hashtable);
shdefault(route_hashtable.value, &route_page_not_found);
shput(route_hashtable.value, clonestr("/"), &route_home);
shput(route_hashtable.value, clonestr("/page-missing"), &route_page_not_found);
shput(route_hashtable.value, clonestr("/blog"), &route_blog);
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/"), &route_home);
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/page-missing"), &route_page_not_found);
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/blog"), &route_blog);
#if MY_DEBUG
shput(route_hashtable.value, clonestr("/build-id"), &route_build_id);
shput(route_hashtable.value, clonestr_alloc(&default_allocator, "/build-id"), &route_build_id);
#endif
baked_resource_each(&route_hashtable_put_blogs, nil);
unlockx(&route_hashtable);
}
void free_route_hashtable(void)
{
lockx(&route_hashtable);
for (size_t i = 0; i < shlen(route_hashtable.value); i++) {
free(route_hashtable.value[i].key);
}
shfree(route_hashtable.value);
unlockx(&route_hashtable);
}
char *init_baked_dump(void)
@ -246,27 +263,28 @@ void copy_baked_resources_to_baked_dump(Baked_Resource *resource, void *udata)
void populate_baked_dump(char *baked_dump)
{
lock_baked_resources();
baked_resource_each(&copy_baked_resources_to_baked_dump, baked_dump);
unlock_baked_resources();
}
volatile bool alive = true;
volatile sig_atomic_t alive = true;
void graceful_shutdown(int no) { alive = false; }
int main(int argc, char ** argv)
{
signal(SIGINT, &graceful_shutdown);
struct sigaction sa;
sa.sa_handler = &graceful_shutdown;
sa.sa_flags = 0;
sigemptyset(&sa.sa_mask);
sigaction(SIGINT, &sa, nil);
start_timer();
init_baked_resources();
lockx(&baked_dump_path);
if ((baked_dump_path.value = init_baked_dump()) == nil) {
return 1;
}
populate_baked_dump(baked_dump_path.value);
unlockx(&baked_dump_path);
mg_log_set(MG_LL_DEBUG);
struct mg_mgr mgr;
@ -284,9 +302,7 @@ int main(int argc, char ** argv)
mg_mgr_free(&mgr);
lockx(&baked_dump_path);
free_baked_dump(baked_dump_path.value);
unlockx(&baked_dump_path);
free_baked_resources();
return 0;

226
routes.c
View File

@ -7,10 +7,11 @@
#include "routes.h"
#include "baked.h"
#include "clonestr.h"
#include "commit.h"
#include "timer.h"
extern char *clonestr_alloc(Allocator *alloc, char *s);
#define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..."
#define META_TAGS \
@ -19,65 +20,65 @@
"<meta name=\"author\" content=\"kamkow1\">" \
"<meta name=\"application-name\" content=\"aboba\">"
void make_internal_server_error(Route_Result *result)
void make_application_json(Allocator *alloc, Route_Result *result, int code, cJSON *root)
{
result->status_code = 500;
result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, "Content-Type: text/plain");
sb_append_nstr(&result->body, INTERNAL_SERVER_ERROR_MSG);
sb_finish(&result->body);
}
size_t buf_size = 512;
char *buf = gebs_malloc(alloc, buf_size);
void make_application_json(Route_Result *result, int code, cJSON *root)
{
char *root_text = cJSON_PrintUnformatted(root);
defer { free(root_text); }
cJSON_PrintPreallocated(root, buf, buf_size, false);
result->status_code = code;
result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, clonestr("Content-Type: application/json"));
sb_append_nstr(&result->body, root_text);
sb_finish(&result->body);
list_append_alloc(alloc, &result->headers, clonestr_alloc(alloc, "Content-Type: application/json"));
sb_append_nstr_alloc(alloc, &result->body, buf);
sb_finish_alloc(alloc, &result->body);
}
void make_text(Route_Result *result, char *subtype, int code, char *in)
void make_text(Allocator *alloc, Route_Result *result, char *subtype, int code, char *in)
{
char type[100];
snprintf(type, sizeof(type), "Content-Type: text/%s", subtype);
result->status_code = code;
result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, clonestr(type));
sb_append_nstr(&result->body, in);
list_append_alloc(alloc, &result->headers,
clonestr_alloc(alloc, fmt("Content-Type: text/%s", subtype)));
sb_append_nstr_alloc(alloc, &result->body, in);
sb_finish(&result->body);
}
bool gpp_run(char *path, NString_List *env, String_Builder *out)
void make_internal_server_error(Allocator *alloc, Route_Result *result)
{
make_text(alloc, result, "plain", 500, INTERNAL_SERVER_ERROR_MSG);
}
bool gpp_run(Allocator *alloc, char *path, NString_List *env, String_Builder *out)
{
Cmd cmd = {0};
defer { cmd_free(&cmd); }
char gpp1[PATH_MAX];
if (!get_baked_resource_path("gpp1", gpp1, sizeof(gpp1))) {
return false;
}
cmd_append(&cmd, gpp1);
cmd_append(&cmd, "-H");
cmd_append(&cmd, "-x");
cmd_append(&cmd, "--nostdinc");
cmd_append(&cmd, path);
cmd_append_alloc(alloc, &cmd, gpp1);
cmd_append_alloc(alloc, &cmd, "-H");
cmd_append_alloc(alloc, &cmd, "-x");
cmd_append_alloc(alloc, &cmd, "--nostdinc");
cmd_append_alloc(alloc, &cmd, path);
for (size_t i = 0; i < env->count; i++) {
cmd_append(&cmd, env->items[i]);
cmd_append_alloc(alloc, &cmd, env->items[i]);
}
return cmd_run_collect(&cmd, out) == 0;
bool ok = cmd_run_collect_alloc(alloc, &cmd, out) == 0;
size_t len = out->count > 100 ? 100 : out->count;
LOGI("Output (first 100): \n-------------------------------\n%.*s\n-------------------------------\n", len, out->items);
return ok;
}
#if MY_DEBUG
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{
LOGI("handler build_id (%.*s)\n", msg->uri.len, msg->uri.buf);
// TODO: Somehow use our arena in here. This will require a slight rework of the way cJSON works.
cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); }
@ -85,226 +86,217 @@ void route_build_id(struct mg_http_message *msg, Route_Result *result, void *con
uchar md5_buf[16];
md5String(time, md5_buf);
String_Builder sb = {0};
defer { sb_free(&sb); }
for (size_t i = 0; i < 16; i++) {
sb_append_nstr(&sb, fmt("%02x", md5_buf[i]));
sb_append_nstr_alloc(alloc, &sb, clonestr_alloc(alloc, fmt("%02x", md5_buf[i])));
}
sb_finish(&sb);
sb_finish_alloc(alloc, &sb);
cJSON_AddItemToObject(root, "build_id", cJSON_CreateString(sb.items));
make_application_json(result, 200, root);
make_application_json(alloc, result, 200, root);
LOGI("handler build_id done\n");
}
#endif
void route_page_not_found(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
LOGI("handler page not found (%.*s)\n", msg->uri.len, msg->uri.buf);
NString_List env = {0};
defer { list_free(&env); }
list_append(&env, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DURL=%.*s", msg->uri.len, msg->uri.buf)));
String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("page-missing.html", path, sizeof(path))) {
make_internal_server_error(result);
LOGE("Failed to get baked resource page-missing.html\n");
make_internal_server_error(alloc, result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);
bool ok = gpp_run(alloc, path, &env, &out);
sb_finish_alloc(alloc, &out);
if (!ok) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
} else {
make_text(result, "html", 200, out.items);
make_text(alloc, result, "html", 200, out.items);
}
LOGI("handler page not found done\n");
}
void route_home(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
LOGI("handler home (%.*s)\n", msg->uri.len, msg->uri.buf);
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("home.html", path, sizeof(path))) {
make_internal_server_error(result);
LOGE("Failed to get baked resource home.html\n");
make_internal_server_error(alloc, result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);
bool ok = gpp_run(alloc, path, &env, &out);
sb_finish_alloc(alloc, &out);
if (!ok) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
} else {
make_text(result, "html", 200, out.items);
make_text(alloc, result, "html", 200, out.items);
}
LOGI("handler home done\n");
}
void route_generic_blog(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
LOGI("handler generic blog (%.*s)\n", msg->uri.len, msg->uri.buf);
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
Baked_Resource *resource = (Baked_Resource *)context_data;
char md_path[PATH_MAX] = {0};
if (!get_baked_resource_path(resource->key, md_path, sizeof(md_path))) {
make_internal_server_error(result);
LOGE("Failed to get baked resource %s\n", resource->key);
make_internal_server_error(alloc, result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif
String_Builder md = {0};
defer { sb_free(&md); }
if (!sb_read_file(&md, md_path)) {
make_internal_server_error(result);
if (!sb_read_file_alloc(alloc, &md, md_path)) {
make_internal_server_error(alloc, result);
return;
}
String_Builder md_prepared = {0};
defer { sb_free(&md_prepared); }
for (size_t i = 0; i < md.count; i++) {
char c = md.items[i];
switch (c) {
case '\"': sb_append_nstr(&md_prepared, "\\\""); break;
case '\'': sb_append_nstr(&md_prepared, "\\\'"); break;
case '\\': sb_append_nstr(&md_prepared, "\\\\"); break;
case '\a': sb_append_nstr(&md_prepared, "\\a"); break;
case '\b': sb_append_nstr(&md_prepared, "\\b"); break;
case '\r': sb_append_nstr(&md_prepared, "\\r"); break;
case '\n': sb_append_nstr(&md_prepared, "\\n"); break;
case '\t': sb_append_nstr(&md_prepared, "\\t"); break;
case '\"': sb_append_nstr_alloc(alloc, &md_prepared, "\\\""); break;
case '\'': sb_append_nstr_alloc(alloc, &md_prepared, "\\\'"); break;
case '\\': sb_append_nstr_alloc(alloc, &md_prepared, "\\\\"); break;
case '\a': sb_append_nstr_alloc(alloc, &md_prepared, "\\a"); break;
case '\b': sb_append_nstr_alloc(alloc, &md_prepared, "\\b"); break;
case '\r': sb_append_nstr_alloc(alloc, &md_prepared, "\\r"); break;
case '\n': sb_append_nstr_alloc(alloc, &md_prepared, "\\n"); break;
case '\t': sb_append_nstr_alloc(alloc, &md_prepared, "\\t"); break;
default:
if (iscntrl(md.items[i])) {
sb_append_nstr(&md_prepared, fmt("\\%03o", c));
sb_append_nstr_alloc(alloc, &md_prepared, clonestr_alloc(alloc, fmt("\\%03o", c)));
} else {
sb_append_char(&md_prepared, c);
sb_append_char_alloc(alloc, &md_prepared, c);
}
break;
}
}
sb_finish(&md_prepared);
sb_finish_alloc(alloc, &md_prepared);
list_append(&env, fmt("-DBLOG_POST_TITLE=%s", resource->key));
list_append(&env, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_TITLE=%s", resource->key)));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POST_MARKDOWN=%s", md_prepared.items)));
char tmpl_file[PATH_MAX] = {0};
if (!get_baked_resource_path("template-blog.html", tmpl_file, sizeof(tmpl_file))) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
return;
}
bool ok = gpp_run(tmpl_file, &env, &out);
sb_finish(&out);
bool ok = gpp_run(alloc, tmpl_file, &env, &out);
sb_finish_alloc(alloc, &out);
if (!ok) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
} else {
make_text(result, "html", 200, out.items);
make_text(alloc, result, "html", 200, out.items);
}
LOGI("handler generic blog done\n");
}
void collect_blogs(Baked_Resource *resource, void *udata)
{
struct { Arena *tmp; String_Builder *sb; } *ud = udata;
struct { Allocator *alloc; String_Builder *sb; } *ud = udata;
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
sb_append_nstr_alloc(ud->tmp, ud->sb, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key));
sb_append_nstr_alloc(ud->alloc, ud->sb,
clonestr_alloc(ud->alloc, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key)));
}
}
void route_blog(struct mg_http_message *msg, Route_Result *result, void *context_data)
void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data)
{
NString_List env = {0};
defer { list_free(&env); }
String_Builder out = {0};
defer { sb_free(&out); }
Arena tmp = arena_get();
defer { arena_destroy(&tmp); }
String_Builder sb = {0};
struct { Arena *tmp; String_Builder *sb; } collect_blogs_data = {
.tmp = &tmp,
struct { Allocator *alloc; String_Builder *sb; } collect_blogs_data = {
.alloc = alloc,
.sb = &sb,
};
baked_resource_each(&collect_blogs, &collect_blogs_data);
sb_finish_alloc(&tmp, &sb);
list_append(&env, fmt("-DBLOG_POSTS=%s", sb.items));
sb_finish_alloc(alloc, &sb);
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DBLOG_POSTS=%s", sb.items)));
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("blog.html", path, sizeof(path))) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
return;
}
list_append(&env, fmt("-DCOMMIT=%s", COMMIT_STRING));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DCOMMIT=%s", COMMIT_STRING)));
char timer[100];
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DRUNNING_SINCE=%s", timer)));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
list_append_alloc(alloc, &env, clonestr_alloc(alloc, fmt("-DMETA_TAGS=%s", META_TAGS)));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
list_append_alloc(alloc, &env, "-DHOTRELOAD");
#endif
bool ok = gpp_run(path, &env, &out);
sb_finish(&out);
bool ok = gpp_run(alloc, path, &env, &out);
sb_finish_alloc(alloc, &out);
if (!ok) {
make_internal_server_error(result);
make_internal_server_error(alloc, result);
} else {
make_text(result, "html", 200, out.items);
make_text(alloc, result, "html", 200, out.items);
}
}

View File

@ -12,15 +12,17 @@ typedef struct {
int status_code;
NString_List headers;
String_Builder body;
Arena *arena;
} Route_Result;
typedef struct {
struct mg_mgr *mgr;
ulong conn_id;
struct mg_str message;
Arena *arena;
} Route_Thread_Data;
typedef void (*Route_Handler)(struct mg_http_message *msg, Route_Result *result, void *context_data);
typedef void (*Route_Handler)(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
typedef struct {
char *key; // path
@ -29,11 +31,11 @@ typedef struct {
} Route;
#if MY_DEBUG
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_build_id(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
#endif
void route_page_not_found(struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_home(struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_generic_blog(struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_blog(struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_page_not_found(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_home(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_generic_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
void route_blog(Allocator *alloc, struct mg_http_message *msg, Route_Result *result, void *context_data);
#endif // ROUTES_H_

1
zip Submodule

Submodule zip added at 649a16c5ea