Compare commits

...

16 Commits

Author SHA1 Message Date
447362c74d dark-easy theme 2025-06-26 14:37:52 +02:00
7a9d5aa371 Self-host marked.js 2025-06-26 00:11:14 +02:00
d9071a4947 Rename etc dump to bakedres dump 2025-06-26 00:07:12 +02:00
632d731118 Self-host highlight.js 2025-06-25 23:58:47 +02:00
bbd23cc8ba Add highlight.js 2025-06-25 23:19:10 +02:00
037b0a123a Themes and a theme picker 2025-06-25 22:47:07 +02:00
ab37785d2a add meta tags 2025-06-25 21:12:42 +02:00
4254c66aa3 Fix: make static pthread mutecies actually use static storage class 2025-06-25 02:47:29 +02:00
06574d86c9 Listen on localhost in release mode 2025-06-25 01:15:32 +02:00
7d911d73fa release port 5000 2025-06-25 00:51:05 +02:00
cb6ec2caa0 The making of aboba: fix wording 2025-06-23 20:47:38 +02:00
2f5465a0a0 The making of aboba article 2025-06-23 17:38:11 +02:00
e230bb01c0 Fix: store route_hashtable keys on the heap 2025-06-22 15:54:10 +02:00
68dcc80d1c Change static assets to /etc/* 2025-06-22 15:34:48 +02:00
5ee77b4628 Protect global data with locks 2025-06-22 15:32:13 +02:00
a7f8ebea09 Static assets finally 2025-06-22 14:57:24 +02:00
22 changed files with 2188 additions and 204 deletions

View File

@ -4,7 +4,7 @@
#if MY_DEBUG
# define CONFIG_LISTEN_URL "http://localhost:8080"
#else
# define CONFIG_LISTEN_URL "http://0.0.0.0:80"
# define CONFIG_LISTEN_URL "http://localhost:5000"
#endif
#endif // CONFIG_H_

50
baked.c
View File

@ -5,6 +5,7 @@
#include "stb/stb_ds.h"
#include "baked.h"
#include "locked.h"
INCBIN(gpp1, "./gpp1");
@ -18,13 +19,30 @@ 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");
Baked_Resource *baked_resources = NULL;
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)
{
@ -34,11 +52,12 @@ void add_baked_resource(char *key, const uchar *data, size_t size)
abort();
}
write(fd, data, size);
shput(baked_resources, key, fd);
shput(baked_resources.value, key, fd);
}
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);
@ -49,34 +68,45 @@ void init_baked_resources(void)
#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);
}
void free_baked_resources(void)
{
for (size_t i = 0; i < shlen(baked_resources); i++) {
close(baked_resources[i].value);
lockx(&baked_resources);
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
close(baked_resources.value[i].value);
}
shfree(baked_resources);
shfree(baked_resources.value);
unlockx(&baked_resources);
}
bool get_baked_resource_path(char *key, char *buf, size_t size)
{
if (shgeti(baked_resources, key) != -1) {
int fd = shget(baked_resources, key);
if (shgeti(baked_resources.value, key) != -1) {
int fd = shget(baked_resources.value, key);
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd);
unlockx(&baked_resources);
return true;
}
return false;
}
void baked_resource_each(void (*f)(Baked_Resource *resource))
void baked_resource_each(void (*f)(Baked_Resource *resource, void *udata), void *udata)
{
for (size_t i = 0; i < shlen(baked_resources); i++) {
f(&baked_resources[i]);
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
f(&baked_resources.value[i], udata);
}
}

11
baked.h
View File

@ -15,11 +15,18 @@ 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 {
char *key; // path
@ -29,6 +36,8 @@ typedef struct {
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 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_

407
blog/the-making-of-aboba.md Normal file
View File

@ -0,0 +1,407 @@
# The making of aboba (this website)
In this article I'd like to present to you the internals of this website,
how the code is architectured and some cool tricks that are used throughout the project.
## Our "engine"
![the engine](/bakedres/tmoa-engine.jpg)
This image is a joke, obviously.
The "engine" here is a web server that we're going to be using. I've decided to pick mongoose (-> [their page](https://mongoose.ws)),
partly because I didn't know about/couldn't find other solutions, but I'm really happy with my pick. The only "downside" here is that
mongoose is not http-specific, but also has websockets, MQTT, SNTP and even RPC. While that's really cool, I only need http and not
much else. I haven't dove deeper into mongoose, but I'd be cool if they provided some \`#ifdef\`s to just disable these protocols
(ie. strip down the code that implements them). That way I could make mongoose even more lightweight and only use the features that I need.
Here's roughly how we work with mongoose. Refer to their documentation for more context.
Let's start with \`main()\`
\`\`\`
volatile bool alive = true;
void graceful_shutdown(int no) { alive = false; }
int main(int argc, char ** argv)
{
signal(SIGINT, &graceful_shutdown);
// skip BS
mg_log_set(MG_LL_DEBUG);
struct mg_mgr mgr;
mg_mgr_init(&mgr);
// skip BS
mg_wakeup_init(&mgr); // We need this for multithreading
mg_http_listen(&mgr, CONFIG_LISTEN_URL, &event_handler, NULL);
while (alive) {
mg_mgr_poll(&mgr, 1000);
// skip BS
}
mg_mgr_free(&mgr);
// skip BS
return 0;
}
\`\`\`
As you can see it's quite simple to set up mongoose. Here's what the used functions do:
- \`mg_log_set()\` - set the log level. \`MG_LL_DEBUG\` is very verbose, but it's good for
when the application breaks and we have no clue why.
- \`struct mg_mgr\` & \`mg_mgr_init()\` - this is the mongoose "manager". The detailed explaination
can be found [here](https://mongoose.ws/documentation/#2-minute-integration-guide), but it can be
essentially boiled down to "overall state of the web server".
- \`mg_wakeup_init()\` - this is needed to make our application multithreaded. In the docs it says
that it's used to "initialize the *wakeup scheme*". This basically means that we can now talk between
multiple threads using \`mg_wakeup()\`, which is the only thread-safe function provided by mongoose.
- \`mg_mgr_poll()\` - handle the next conection if there's any incoming data to work with. We can
also specify the timeout for a connection. Here we provide 1 second (1000 ms).
That's all you really need to know to get started with mongoose. Let's get to the \`event_handler()\` now.
\`\`\`
void event_handler(struct mg_connection *conn, int ev, void *ev_data)
{
if (ev == MG_EV_HTTP_MSG) {
// Run handler in a new thread
} else if (ev == MG_EV_WAKEUP) {
// We've woken up from a handler by mg_wakeup(). Send the reply back to the client
}
}
\`\`\`
I've removed a lot of code here, because it's irrelevant at the current point. This allowes us to look at
the simplified image of the \`event_handler()\` function.
Let's stop to talk about the parameters for a second.
- \`struct mg_connection *conn\` - the structure that describes the incoming connection. We will also
use it to send back our reply.
- \`int ev\` - this is the event enumeration. Basically tells us what event we're currently handling
inside of mongoose's event loop.
- \`void *ev_data\` - additional event data. The value of this parameter differs based on the value of \`int ev\`.
More on that a little bit later.
What goes on inside the \`MG_EV_HTTP_MSG\` branch?
\`\`\`
if (ev == MG_EV_HTTP_MSG) {
struct mg_http_message *msg = (struct mg_http_message *)ev_data;
Route_Thread_Data *data = calloc(1, sizeof(*data));
data->message = mg_strdup(msg->message);
data->conn_id = conn->id;
data->mgr = conn->mgr;
run_in_thread(&route_thread_function, data);
\`\`\`
If we have an "HTTP Message" event incoming, ev_data is a pointer to \`struct mg_http_message\`.
This structure contains things like the message body, query parameters, the uri, the method and so on. Here we
duplicate the \`message\` field, which encompasses the entire HTTP message. We also save the reference to the
mongoose manager for later use when we will want to wake up from a thread.
Here's how the thread is spawned. This code is taken from the mongoose tutorial: https://github.com/cesanta/mongoose/blob/master/tutorials/core/multi-threaded/main.c#L11
\`\`\`
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);
}
\`\`\`
And then here's what goes on inside a thread:
\`\`\`
void *route_thread_function(void *param)
{
Route_Thread_Data *data = (Route_Thread_Data *)param;
struct mg_http_message http_msg = {0};
int r = mg_http_parse(data->message.buf, data->message.len, &http_msg);
if (r <= 0) {
// Unparsable HTTP request
}
if (mg_match(http_msg.uri, mg_str("/bakedres/*"), nil)) {
// Request for static resource
}
// Request for a dynamic page
}
\`\`\`
This is quite simple, so there's not much to explain here. One cool thing I'd like to mention is the
\`mg_match()\` function. Normally I'd have to implement uri string matching myself, but I'm glad that
this functionality comes with mongoose built-in. God bless you mongoose!
## Dynamic pages and static assets.
![the assets](/bakedres/tmoa-garbage.jpg)
Let's stop here now and talk about something different entirely (dont' worry, we'll come back later).
I'd like to show you how assets/pages are implemented in aboba, so we get the whole picture.
In most applications you typically have a distribution model that goes like this: you have your application's
binary ("aboba" or "aboba.exe" or "aboba.bin"...) and then you have an "assets"/"resources" directory, placed
somewhere within the filesystem. There are now 2 ways to go about this. You can have a fixed path to the assets
directory or let the user configure it based on how they've installed the application. In the first case the
app could require that it's assets must be located in \`/usr/share/my-app/assets\` and otherwise it will bail
out with an error "Err: Could not find assets directory blah blah" or something. In the other case the application
would require that the user configures the path to the assets directory themselves, which is not a bad solution
(it may sound like it at first). This really depends what audience are we targetting. "Are our users tech-savvy
enough to do it themselves?" is the question we'd have to ask ourselves.
**Enter embedded assets**
The downsides of both solutions is that we still have to distribute our application with an assets directory. We
have to manage 1 executable file + an entire directory. The *obvious* solution would be to just create a
windows-style setup wizard or a \`make install\` or some other install script thingy. This problem is easily
solvable by literally compiling the bytes of the assets into our program. That way we can distribute only the
executable since it's self contained and doesn't need to reach out to the filesystem at runtime to get it's resources.
This technique is fairly old and the most notable uses I can think of are: [XPM Format](https://en.wikipedia.org/wiki/X_PixMap),
[Windows resources](https://en.wikipedia.org/wiki/Resource_(Windows)) and raylib's [rres](https://github.com/raysan5/rres).
Even C23 has acknowledged the significance of embedded assets (-> [article](https://thephd.dev/finally-embed-in-c23)).
Of course there are some pitfalls of this approach. What if the files we're trying to embed are too *thicc*? We obviously
don't want to end up with a 5GB executable. Imagine the OS trying to load that program into the ram. That would just eat up 5
gigs before anything meaningful ever happens. This pattern of asset distribution is the most suitable for embedded applications
where there's no underlying filesystem to work with, games which are not too large, GUI apps that need some icons and fonts.
So how does aboba go about embedding it's assets? [incbin](https://github.com/graphitemaster/incbin) comes to the rescue!
Here are external declarations for our resources
\`\`\`
#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(me_jpg);
INCBIN_EXTERN(tmoa_engine_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);
\`\`\`
And here's where the actual inclusion happens:
\`\`\`
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(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");
\`\`\`
As you can see, with incbin embedding assets is extremely easy. In the past I've worked with my
own asset packer utility \`x2h.c\`, but it was kind of sloppy and I don't have the will to
rewrite it (maybe one day ;) ).
Incbin works (on GCC, which I'm using here) by calling into the assembler using \`__asm__()\` and then uses \`.incbin\`
directive to include the binary file. More on that can be found in the docs: https://sourceware.org/binutils/docs/as/Incbin.html
Now let the **trickery** begin...
## Tangent about templating
Don't worry, in this section I'll briefly explain how templating is implemented, so that we can circle back to our embedded assets system to better understand it.
How are templating engines implemented in general? You usually have a pseudo-HTML page (for eg. \`my-page.tmpl\`), which
contains plain old HTML, but also has some escaped blocks that can embed dynamic variables, loop over lists, include other pages
and whatnot. You then take that pseudo-page and run it through some kind of a (keyword) *preprocessor*, which given an environment,
can expand variable *macros*, unfold loops and other tricks.
Do you already see where I'm going with my wording?
I want to keep this a *mostly* a pure C project, where every bit of it that can be written in C, is written in C. Yes, even the templates
(*in a way*). In order to expand the page templates I've decided to use GPP or the General PreProcessor written by Denis Auroux and maintained
by Tristan Miller. Here's a link to GPP's website (-> [click](https://logological.org/gpp)). Why not use regular CPP that comes with the GCC
suite, you may ask. This is because GPP has a special mode - the HTML mode, which makes it so that it's better suited for working with HTML.
For example \`\#define\` vs. \`<\#define>\` or \`\#ifdef\` and \`\#endif\` vs. \`<\#ifdef>\` and \`<\#endif>\`.
How do we interact with GPP then? To preprocess our templates, we can call GPP via it's command line interface (CLI) - set HTML mode, give path
to the template file and collect the output, which we then can send back to the client.
But wait, if we call out to an external executable, doesn't that mean that we'll have to ship GPP alongside aboba? The answer is yes! Since
we follow the *principle of a single-executable-deployment*, why don't we pack GPP into our binary, just like any other asset? Another question
arises then - how do we call a packed program and how to we work with it's CLI? I'll answer this in the next part...
## Back to the assets
So now that we've established why and how we embed assets into an executable and the way we work with templates, we can now discuss the
**embedded assets system**.
Here's the core API of the system:
\`\`\`
typedef struct {
char *key; // path
int value; // memfd
} Baked_Resource;
void init_baked_resources(void);
void free_baked_resources(void);
// skip BS
bool get_baked_resource_path(char *key, char *buf, size_t size);
// skip BS
\`\`\`
The \`Baked_Resource\` struct is defined in such a way that works with \`stb_ds.h\`'s string hashmap. stb_ds.h can be found here: https://nothings.org/stb_ds/.
Let's take a closer look at the fields:
- \`key\` - a key to a file within our hashmap of baked resources
- \`value\` - a memfd associated with the baked resource
Here's the initialization and deinitialization of baked resources:
\`\`\`
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...", key);
abort();
}
write(fd, data, size);
shput(baked_resources.value, key, fd);
}
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("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);
}
void free_baked_resources(void)
{
lockx(&baked_resources);
for (size_t i = 0; i < shlen(baked_resources.value); i++) {
close(baked_resources.value[i].value);
}
shfree(baked_resources.value);
unlockx(&baked_resources);
}
\`\`\`
Here we use memfd API to convert a baked-in file into a file that has a file descriptor associated with it. Why? We do this, because
we have no way of passing files down to GPP's CLI. We only have the raw bytes of a file, which we can't really work with. memfds
allow us to create a virtual memory-mapped file and get it's file descriptor. Using said file descriptor we can then write our file's
bytes into the virtual file, making it accessible via Linux's VFS. The virtual file can be accessed via a path like \`/proc/<PID>/fd/<THE FILE>\`.
Now that we've successfully converted a baked-in file into a "*pathed*" file, we can then pass the path down to GPP. Heck, we can even run
GPP itself from a memory-mapped file!
Here's how we get the memory-mapped file's path in aboba:
\`\`\`
bool get_baked_resource_path(char *key, char *buf, size_t size)
{
if (shgeti(baked_resources.value, key) != -1) {
int fd = shget(baked_resources.value, key);
snprintf(buf, size, "/proc/%d/fd/%d", getpid(), fd);
unlockx(&baked_resources);
return true;
}
return false;
}
\`\`\`
And then we run GPP like so:
\`\`\`
bool gpp_run(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);
for (size_t i = 0; i < env->count; i++) {
cmd_append(&cmd, env->items[i]);
}
return cmd_run_collect(&cmd, out) == 0;
}
\`\`\`
In the logger we can now see commands like this: \`Info: cmd /proc/1210675/fd/8 -H -x --nostdinc /proc/1210675/fd/6 ...\`, where
/proc/1210675/fd/8 is memory-mapped file for GPP and /proc/1210675/fd/6 is a memory-mapped file for the template. Pretty cool, eh?
## Left out topics
I think this is out of scope of this article, so I'm not going to talk about it here, but a big part of this project was making
live hotreloading. I can basically edit the website inside of my editor and it auto-refreshes in the browser, kinda like a vite js
project. A video of this can be found here: https://www.reddit.com/r/C_Programming/comments/1lbzjvi/webdev_in_c_pt2_true_live_hotreloading_no_more/
## Summary
During this project I've learned a lot about Linux, the memfd API and webdev in general. Normally I wouldn't pick up a website project
simply because I'm tired of webdev. I've been through a webdev phase and it sucked. Using JavaScript, 300MB of node_modules for a bare
react hello world project, npm installing countless slop libraries and so on.
Then on the backend you have the same amount of C# or Java or TypeScript slop, but now you can call yourself a *backend engineer* or
whatever the hell. Writing this website in C put me on a different view of webdev and made it actually fun to write.
You can go check out the code for aboba @ http://git.kamkow1lair.pl/kamkow1/aboba.git.

12
build.c
View File

@ -26,6 +26,7 @@ int main(int argc, char ** argv)
"./timer.c",
"./timer.h",
"./CONFIG.h",
"./locked.h",
"./mongoose.o",
"./gpp1",
@ -36,13 +37,20 @@ int main(int argc, char ** argv)
"./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/curious-case-of-gebs.md",
"./blog/the-making-of-aboba.md"
) {
RULE("./mongoose.o", "./mongoose/mongoose.c") {
@ -88,7 +96,7 @@ int main(int argc, char ** argv)
}
#if MY_DEBUG
CMD("cc", "-fPIC", "-ggdb", "-I.", "-DMY_DEBUG=1", "-D_GNU_SOURCE", "-DGEBS_NO_PREFIX",
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",

1244
etc/highlight.js Normal file

File diff suppressed because one or more lines are too long

1
etc/hljs-rainbow.css Normal file
View File

@ -0,0 +1 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}

69
etc/marked.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -8,18 +8,18 @@
--standard-border-radius: 5px;
--border-width: 1px;
--bg: #FFFFEC;
--accent-bg: #EEEEA7;
--text: #424242;
--text-light: #999957;
--border: #B7B19C;
--accent: #030093;
--accent-hover: #2A8DC5;
--accent-text: var(--bg);
--code: #57864E;
--preformatted: #424242;
--marked: #B85C57;
--disabled: #EAEBDB;
--bg: #F0F2F5;
--accent-bg: #E3E6EA;
--text: #2C2C2C;
--text-light: #9A9A9A;
--border: #D1D7DC;
--accent: #3454D1;
--accent-hover: #6A8ED8;
--accent-text: var(--bg);
--code: #3C9E47;
--preformatted: #2C2C2C;
--marked: #D6674B;
--disabled: #F1F3F6;
}
/* Reset box-sizing */

131
etc/theme.js Normal file
View File

@ -0,0 +1,131 @@
if (!localStorage.getItem("theme")) {
localStorage.setItem("theme", "light-breeze");
}
function plan9_acme()
{
document.documentElement.style.setProperty("--bg", "#FFFFEC");
document.documentElement.style.setProperty("--accent-bg", "#EEEEA7");
document.documentElement.style.setProperty("--text", "#424242");
document.documentElement.style.setProperty("--text-light", "#999957");
document.documentElement.style.setProperty("--border", "#B7B19C");
document.documentElement.style.setProperty("--accent", "#030093");
document.documentElement.style.setProperty("--accent-hover", "#2A8DC5");
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
document.documentElement.style.setProperty("--code", "#57864E");
document.documentElement.style.setProperty("--preformatted", "#424242");
document.documentElement.style.setProperty("--marked", "#B85C57");
document.documentElement.style.setProperty("--disabled", "#EAEBDB");
}
function light_breeze()
{
document.documentElement.style.setProperty("--bg", "#F0F2F5");
document.documentElement.style.setProperty("--accent-bg", "#E3E6EA");
document.documentElement.style.setProperty("--text", "#2C2C2C");
document.documentElement.style.setProperty("--text-light", "#9A9A9A");
document.documentElement.style.setProperty("--border", "#D1D7DC");
document.documentElement.style.setProperty("--accent", "#3454D1");
document.documentElement.style.setProperty("--accent-hover", "#6A8ED8");
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
document.documentElement.style.setProperty("--code", "#3C9E47");
document.documentElement.style.setProperty("--preformatted", "#2C2C2C");
document.documentElement.style.setProperty("--marked", "#D6674B");
document.documentElement.style.setProperty("--disabled", "#F1F3F6");
}
function bold_navy()
{
document.documentElement.style.setProperty("--bg", "#F0F4FF");
document.documentElement.style.setProperty("--accent-bg", "#E0E6F1");
document.documentElement.style.setProperty("--text", "#2D3A58");
document.documentElement.style.setProperty("--text-light", "#7A8C9E");
document.documentElement.style.setProperty("--border", "#BCC6D8");
document.documentElement.style.setProperty("--accent", "#1E2A5B");
document.documentElement.style.setProperty("--accent-hover", "#2A3F80");
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
document.documentElement.style.setProperty("--code", "#4A7C2F");
document.documentElement.style.setProperty("--preformatted", "#2D3A58");
document.documentElement.style.setProperty("--marked", "#F18F5D");
document.documentElement.style.setProperty("--disabled", "#E8ECF5");
}
function soft_lavander()
{
document.documentElement.style.setProperty("--bg", "#F5F2FF");
document.documentElement.style.setProperty("--accent-bg", "#E1D1FF");
document.documentElement.style.setProperty("--text", "#3D2B6F");
document.documentElement.style.setProperty("--text-light", "#8D8B9B");
document.documentElement.style.setProperty("--border", "#C1B5D6");
document.documentElement.style.setProperty("--accent", "#7A4FE3");
document.documentElement.style.setProperty("--accent-hover", "#8D6DE8");
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
document.documentElement.style.setProperty("--code", "#5D7C42");
document.documentElement.style.setProperty("--preformatted", "#3D2B6F");
document.documentElement.style.setProperty("--marked", "#F77A63");
document.documentElement.style.setProperty("--disabled", "#E9E4FF");
}
function neon_cyber()
{
document.documentElement.style.setProperty("--bg", "#121212");
document.documentElement.style.setProperty("--accent-bg", "#1C1C1C");
document.documentElement.style.setProperty("--text", "#00FF00");
document.documentElement.style.setProperty("--text-light", "#A1FF3D");
document.documentElement.style.setProperty("--border", "#006400");
document.documentElement.style.setProperty("--accent", "#FF007F");
document.documentElement.style.setProperty("--accent-hover", "#FF3399");
document.documentElement.style.setProperty("--accent-text", "var(--bg)");
document.documentElement.style.setProperty("--code", "#2AFF32");
document.documentElement.style.setProperty("--preformatted", "#00FF00");
document.documentElement.style.setProperty("--marked", "#FF6F00");
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,
};
const footer = document.getElementsByTagName("footer")[0];
const select = document.createElement("select");
select.id = "theme-picker";
footer.children[0].appendChild(select);
for (const prop in themes) {
const option = document.createElement("option");
option.value = prop;
option.innerHTML = prop;
if (prop == localStorage.getItem("theme")) {
option.selected = true;
}
select.appendChild(option);
}
const theme_picker = document.getElementById("theme-picker");
theme_picker.addEventListener("change", function (e) {
const theme_name = e.target.value;
localStorage.setItem("theme", theme_name);
themes[localStorage.getItem("theme")]()
});
themes[localStorage.getItem("theme")]()

BIN
etc/tmoa-engine.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

BIN
etc/tmoa-garbage.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

12
locked.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef LOCKED_H_
#define LOCKED_H_
#include <pthread.h>
#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)
#define unlockx(x) pthread_mutex_unlock(&(x)->lock)
#endif // LOCKED_H_

217
main.c
View File

@ -1,5 +1,8 @@
#include <libgen.h>
#include <pthread.h>
#include <ftw.h>
#include <signal.h>
#include <sys/sendfile.h>
#define GEBS_IMPLEMENTATION
#include "gebs/gebs.h"
@ -11,8 +14,11 @@
#include "baked.h"
#include "timer.h"
#include "CONFIG.h"
#include "clonestr.h"
#include "locked.h"
Route *route_hashtable = NULL;
static locked(Route *) route_hashtable = locked_init(nil);
static locked(char *) baked_dump_path = locked_init(nil);
void run_in_thread(void *(*f)(void *), void *p)
{
@ -33,16 +39,49 @@ void *route_thread_function(void *param)
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);
mg_wakeup(data->mgr, data->conn_id, &result, sizeof(result));
free(data->message.buf);
free(data);
return 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);
ssize_t idx = shgeti(route_hashtable, key);
lockx(&route_hashtable);
ssize_t idx = shgeti(route_hashtable.value, key);
Route_Result result = {0};
route_hashtable[idx].value(&http_msg, &result, route_hashtable[idx].context_data);
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);
@ -80,69 +119,154 @@ void event_handler(struct mg_connection *conn, int ev, void *ev_data)
}
sb_finish(&sb);
if (result->type == ROUTE_RESULT_TEXT) {
if (result->type == ROUTE_RESULT_DYNAMIC) {
mg_http_reply(conn, result->status_code, sb.items, "%s", result->body.items);
} else if (result->type == ROUTE_RESULT_BINARY) {
char *reply = fmt(
"HTTP/1.1 %d OK\r\n"
"%s"
"Content-Length: %zu\r\n"
"\r\n",
result->status_code,
sb.items,
result->body.count
);
printf("%s\n", reply);
mg_printf(conn, "%s", reply);
mg_send(conn, result->body.items, result->body.count);
} else if (result->type == ROUTE_RESULT_STATIC) {
char *path = result->body.items;
mg_http_serve_file(conn, ev_data, path, &(struct mg_http_serve_opts){0});
}
}
}
void route_hashtable_put_blogs(Baked_Resource *resource, void *udata)
{
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
char *path = malloc(MG_PATH_MAX);
snprintf(path, MG_PATH_MAX, "/%s", resource->key);
shput(route_hashtable.value, path, &route_generic_blog);
route_hashtable.value[shgeti(route_hashtable.value, path)].context_data = (void *)resource;
}
}
void init_route_hashtable(void)
{
shdefault(route_hashtable, &route_page_not_found);
shput(route_hashtable, "/", &route_home);
shput(route_hashtable, "/page-missing", &route_page_not_found);
shput(route_hashtable, "/simple.css", &route_simple_css);
shput(route_hashtable, "/favicon.ico", &route_favicon);
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);
#if MY_DEBUG
shput(route_hashtable, "/hotreload.js", &route_hotreload_js);
shput(route_hashtable, "/build-id", &route_build_id);
shput(route_hashtable.value, clonestr("/build-id"), &route_build_id);
#endif
shput(route_hashtable, "/me.jpg", &route_me_jpg);
shput(route_hashtable, "/blog", &route_blog);
void put_blogs(Baked_Resource *resource)
{
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
char *path = malloc(MG_PATH_MAX);
snprintf(path, MG_PATH_MAX, "/%s", resource->key);
shput(route_hashtable, path, &route_generic_blog);
route_hashtable[shgeti(route_hashtable, path)].context_data = (void *)resource;
}
}
baked_resource_each(&put_blogs);
baked_resource_each(&route_hashtable_put_blogs, nil);
unlockx(&route_hashtable);
}
void free_route_hashtable(void)
{
for (size_t i = 0; i < shlen(route_hashtable); i++) {
if (strlen(route_hashtable[i].key) >= strlen("blog-")
&& strncmp(route_hashtable[i].key, "blog-", strlen("blog-")) == 0) {
free(route_hashtable[i].key);
lockx(&route_hashtable);
for (size_t i = 0; i < shlen(route_hashtable.value); i++) {
free(route_hashtable.value[i].key);
}
}
shfree(route_hashtable);
shfree(route_hashtable.value);
unlockx(&route_hashtable);
}
char *init_baked_dump(void)
{
char template[] = "/tmp/aboba-bakedres.XXXXXX";
char *baked_dump1 = mkdtemp(template);
char *baked_dump = malloc(strlen(baked_dump1)+1);
strcpy(baked_dump, baked_dump1);
if (baked_dump == nil) {
LOGE("Could not create bakedres dump\n");
return nil;
}
LOGI("bakedres dump dir is %s\n", baked_dump);
return baked_dump;
}
int baked_dump_rm_cb(const char *path,
discard const struct stat *st,
discard int type,
discard struct FTW *ftw)
{
return remove(path);
}
bool free_baked_dump(char *baked_dump)
{
LOGI("Removing bakedres dump %s\n", baked_dump);
bool ok = nftw(baked_dump, baked_dump_rm_cb, FOPEN_MAX, FTW_DEPTH | FTW_MOUNT | FTW_PHYS) != -1;
if (!ok) {
LOGE("Could not remove %s\n", baked_dump);
}
free(baked_dump);
return ok;
}
// https://stackoverflow.com/questions/2180079/how-can-i-copy-a-file-on-unix-using-c
int cp(const char* source, const char* destination)
{
int result = 0;
int input, output;
if ((input = open(source, O_RDONLY)) == -1)
{
return -1;
}
if ((output = creat(destination, 0660)) == -1)
{
close(input);
return -1;
}
// sendfile will work with non-socket output (i.e. regular file) under
// Linux 2.6.33+ and some other unixy systems.
struct stat file_stat = {0};
result = fstat(input, &file_stat);
off_t copied = 0;
while (result == 0 && copied < file_stat.st_size) {
ssize_t written = sendfile(output, input, &copied, SSIZE_MAX);
copied += written;
if (written == -1) {
result = -1;
}
}
close(input);
close(output);
return result;
}
void copy_baked_resources_to_baked_dump(Baked_Resource *resource, void *udata)
{
char *baked_dump = (char *)udata;
char path[PATH_MAX];
get_baked_resource_path(resource->key, path, sizeof(path));
char dest[PATH_MAX];
snprintf(dest, sizeof(dest), "%s/%s", baked_dump, resource->key);
cp(path, dest);
}
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;
void graceful_shutdown(int no) { alive = false; }
int main(int argc, char ** argv)
{
signal(SIGINT, &graceful_shutdown);
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;
@ -153,13 +277,16 @@ int main(int argc, char ** argv)
mg_wakeup_init(&mgr);
mg_http_listen(&mgr, CONFIG_LISTEN_URL, &event_handler, NULL);
for (;;) {
while (alive) {
mg_mgr_poll(&mgr, 1000);
scratch_arena_reset();
}
mg_mgr_free(&mgr);
lockx(&baked_dump_path);
free_baked_dump(baked_dump_path.value);
unlockx(&baked_dump_path);
free_baked_resources();
return 0;

148
routes.c
View File

@ -13,10 +13,16 @@
#define INTERNAL_SERVER_ERROR_MSG "Internal server error ;(. Try again later..."
#define META_TAGS \
"<meta name=\"description\" content=\"kamkow1's personal website and blog. Focused on C programming, weight lifting and personal thoughts.\" />" \
"<meta name=\"keywords\" content=\"C, C Programming, Blog, kamkow1, Linux\" />" \
"<meta name=\"author\" content=\"kamkow1\">" \
"<meta name=\"application-name\" content=\"aboba\">"
void make_internal_server_error(Route_Result *result)
{
result->status_code = 500;
result->type = ROUTE_RESULT_TEXT;
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);
@ -28,32 +34,19 @@ void make_application_json(Route_Result *result, int code, cJSON *root)
defer { free(root_text); }
result->status_code = code;
result->type = ROUTE_RESULT_TEXT;
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);
}
void make_binary(Route_Result *result, char *content_type, int code, char *data, size_t size)
{
char type[100];
snprintf(type, sizeof(type), "Content-Type: %s", content_type);
result->status_code = code;
result->type = ROUTE_RESULT_BINARY;
list_append(&result->headers, clonestr(type));
for (size_t i = 0; i < size; i++) {
sb_append_char(&result->body, data[i]);
}
sb_finish(&result->body);
}
void make_text(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_TEXT;
result->type = ROUTE_RESULT_DYNAMIC;
list_append(&result->headers, clonestr(type));
sb_append_nstr(&result->body, in);
sb_finish(&result->body);
@ -83,7 +76,7 @@ bool gpp_run(char *path, NString_List *env, String_Builder *out)
}
#if MY_DEBUG
ROUTE_HANDLER(build_id)
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data)
{
cJSON *root = cJSON_CreateObject();
defer { cJSON_Delete(root); }
@ -104,8 +97,10 @@ ROUTE_HANDLER(build_id)
}
#endif
ROUTE_HANDLER(page_not_found)
void route_page_not_found(struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
NString_List env = {0};
defer { list_free(&env); }
@ -125,6 +120,8 @@ ROUTE_HANDLER(page_not_found)
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
#endif
@ -139,84 +136,10 @@ ROUTE_HANDLER(page_not_found)
}
}
ROUTE_HANDLER(simple_css)
{
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("simple.css", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
String_Builder sb = {0};
defer { sb_free(&sb); }
if (!sb_read_file(&sb, path)) {
make_internal_server_error(result);
return;
}
sb_finish(&sb);
make_text(result, "css", 200, sb.items);
}
ROUTE_HANDLER(me_jpg)
{
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("me.jpg", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
String_Builder sb = {0};
defer { sb_free(&sb); }
if (!sb_read_file(&sb, path)) {
make_internal_server_error(result);
return;
}
make_binary(result, "image/jpeg", 200, sb.items, sb.count);
}
ROUTE_HANDLER(favicon)
{
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("favicon.ico", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
String_Builder sb = {0};
defer { sb_free(&sb); }
if (!sb_read_file(&sb, path)) {
make_internal_server_error(result);
return;
}
make_binary(result, "image/x-icon", 200, sb.items, sb.count);
}
#if MY_DEBUG
ROUTE_HANDLER(hotreload_js)
{
char path[PATH_MAX] = {0};
if (!get_baked_resource_path("hotreload.js", path, sizeof(path))) {
make_internal_server_error(result);
return;
}
String_Builder sb = {0};
defer { sb_free(&sb); }
if (!sb_read_file(&sb, path)) {
make_internal_server_error(result);
return;
}
sb_finish(&sb);
make_text(result, "javascript", 200, sb.items);
}
#endif
ROUTE_HANDLER(home)
void route_home(struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
NString_List env = {0};
defer { list_free(&env); }
@ -234,6 +157,8 @@ ROUTE_HANDLER(home)
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
#endif
@ -248,8 +173,10 @@ ROUTE_HANDLER(home)
}
}
ROUTE_HANDLER(generic_blog)
void route_generic_blog(struct mg_http_message *msg, Route_Result *result, void *context_data)
{
lock_baked_resources();
defer { unlock_baked_resources(); }
NString_List env = {0};
defer { list_free(&env); }
@ -269,6 +196,8 @@ ROUTE_HANDLER(generic_blog)
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
#endif
@ -323,7 +252,16 @@ ROUTE_HANDLER(generic_blog)
}
}
ROUTE_HANDLER(blog)
void collect_blogs(Baked_Resource *resource, void *udata)
{
struct { Arena *tmp; 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));
}
}
void route_blog(struct mg_http_message *msg, Route_Result *result, void *context_data)
{
NString_List env = {0};
defer { list_free(&env); }
@ -335,15 +273,11 @@ ROUTE_HANDLER(blog)
defer { arena_destroy(&tmp); }
String_Builder sb = {0};
void collect_blogs(Baked_Resource *resource)
{
if ((strlen(resource->key) >= strlen("blog-"))
&& strncmp(resource->key, "blog-", strlen("blog-")) == 0) {
sb_append_nstr_alloc(&tmp, &sb, fmt("<li><a href=\"/%s\">%s</a></li>", resource->key, resource->key));
}
}
baked_resource_each(&collect_blogs);
struct { Arena *tmp; String_Builder *sb; } collect_blogs_data = {
.tmp = &tmp,
.sb = &sb,
};
baked_resource_each(&collect_blogs, &collect_blogs_data);
sb_finish_alloc(&tmp, &sb);
list_append(&env, fmt("-DBLOG_POSTS=%s", sb.items));
@ -358,6 +292,8 @@ ROUTE_HANDLER(blog)
get_timer_string(timer, sizeof(timer));
list_append(&env, fmt("-DRUNNING_SINCE=%s", timer));
list_append(&env, fmt("-DMETA_TAGS=%s", META_TAGS));
#if MY_DEBUG
list_append(&env, "-DHOTRELOAD");
#endif

View File

@ -6,8 +6,8 @@
typedef struct {
enum {
ROUTE_RESULT_TEXT,
ROUTE_RESULT_BINARY,
ROUTE_RESULT_DYNAMIC,
ROUTE_RESULT_STATIC,
} type;
int status_code;
NString_List headers;
@ -28,18 +28,12 @@ typedef struct {
void *context_data;
} Route;
#define ROUTE_HANDLER(name) void route_##name(struct mg_http_message *msg, Route_Result *result, void *context_data)
ROUTE_HANDLER(page_not_found);
ROUTE_HANDLER(simple_css);
ROUTE_HANDLER(favicon);
ROUTE_HANDLER(me_jpg);
#if MY_DEBUG
ROUTE_HANDLER(hotreload_js);
ROUTE_HANDLER(build_id);
void route_build_id(struct mg_http_message *msg, Route_Result *result, void *context_data);
#endif
ROUTE_HANDLER(home);
ROUTE_HANDLER(generic_blog);
ROUTE_HANDLER(blog);
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);
#endif // ROUTES_H_

11
timer.c
View File

@ -3,22 +3,27 @@
#include <stdio.h>
#include "timer.h"
#include "locked.h"
time_t rawtime;
static locked(time_t) rawtime;
void start_timer(void)
{
time(&rawtime);
lockx(&rawtime);
time(&rawtime.value);
unlockx(&rawtime);
}
void get_timer_string(char *output, size_t size)
{
lockx(&rawtime);
struct tm * timeinfo;
timeinfo = localtime(&rawtime);
timeinfo = localtime(&rawtime.value);
snprintf(output, size, "[%02d %02d %d %02d:%02d:%02d]", timeinfo->tm_mday,
timeinfo->tm_mon + 1, timeinfo->tm_year + 1900,
timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec);
unlockx(&rawtime);
}

View File

@ -3,7 +3,8 @@
<head>
<title>Blog</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/simple.css" />
<link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS>
</head>
<body>
<h1>Blog posts</h1>
@ -23,7 +24,8 @@
</footer>
<#ifdef HOTRELOAD>
<script src="/hotreload.js"></script>
<script src="/bakedres/hotreload.js"></script>
<#endif>
<script src="/bakedres/theme.js"></script>
</body>
</html>

View File

@ -3,7 +3,8 @@
<head>
<title>Kamil's personal website</title>
<meta charset="utf-8" />
<link rel="stylesheet" href="/simple.css" />
<link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS>
</head>
<body>
<h1>Kamil's personal website</h1>
@ -53,7 +54,7 @@
</ul>
</section>
<section>
<img src="/me.jpg" alt="literally me" />
<img src="/bakedres/me.jpg" alt="literally me" />
</section>
<footer>
<div style="float: left;">
@ -64,7 +65,8 @@
</footer>
<#ifdef HOTRELOAD>
<script src="/hotreload.js"></script>
<script src="/bakedres/hotreload.js"></script>
<#endif>
<script src="/bakedres/theme.js"></script>
</body>
</html>

View File

@ -2,7 +2,8 @@
<html>
<head>
<title>404 - Page not found</title>
<link rel="stylesheet" href="/simple.css" />
<link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS>
</head>
<body>
<h1>The page you were looking for doesn't exist!</h1>
@ -18,7 +19,8 @@
</footer>
<#ifdef HOTRELOAD>
<script src="/hotreload.js"></script>
<script src="/bakedres/hotreload.js"></script>
<#endif>
<script src="/bakedres/theme.js"></script>
</body>
</html>

View File

@ -3,7 +3,10 @@
<head>
<meta charset="utf-8" />
<title><#BLOG_POST_TITLE></title>
<link rel="stylesheet" href="/simple.css" />
<link rel="stylesheet" href="/bakedres/simple.css" />
<#META_TAGS>
<link rel="stylesheet" href="/bakedres/hljs-rainbow.css">
<script src="/bakedres/highlight.js"></script>
</head>
<body>
<div id="content"></div>
@ -14,13 +17,15 @@
<span>Running since: <#RUNNING_SINCE></span>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="/bakedres/marked.js"></script>
<script>
document.getElementById("content").innerHTML = marked.parse(`<#BLOG_POST_MARKDOWN>`);
</script>
<#ifdef HOTRELOAD>
<script src="/hotreload.js"></script>
<script src="/bakedres/hotreload.js"></script>
<#endif>
<script src="/bakedres/theme.js"></script>
<script>hljs.highlightAll();</script>
</body>
</html>

View File

@ -183,7 +183,7 @@ int main(int argc, char ** argv)
if (cmd_pid != -1) {
LOGI("Killing %d\n", cmd_pid);
kill(cmd_pid, SIGKILL);
kill(cmd_pid, SIGINT);
}
cmd_pid = cmd_run_async(&cmd);