The project is structured like this:
website/ | Makefile | website.c | compile_tmpl.awk | static/ \_ favicon.ico \_ style.css \_ ... more static files ... | tmpl/ \_ index.html \_ req2long.html \_ ... more templates ...
The full source code can be viewed at github.com/Flying-Toast/website.exe
Dynamic templates (located in tmpl/
) are precompiled into
a printf()
call and included in the server executable itself.
The compile_tmpl.awk
script runs during
compilation (see tmplfuncs.gen
rule in Makefile) and converts the
contents of template files into a C string literal + corresponding
printf()
call.
Suppose the following are the contents of the file
tmpl/my_example_tmpl.html
:
<!DOCTYPE html> <html> <head> <title><%= %s pagename %> - Tiny Website</title> </head> <body> <h1><%= %s pagename %></h1> <p> <%= %d first %> + <%= %d second %> = <%= %d sum %> </p> </body> </html>During compilation, the command
awk -f compile_tmpl.awk tmpl/my_example_tmpl.html
is run, which outputs the following:
struct _tmplargs_my_example_tmpl_html { char *pagename; int first; int second; int sum; }; static void _tmplfunc_my_example_tmpl_html(int fd, struct _tmplargs_my_example_tmpl_html *args) { dprintf( fd, "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>%s - Tiny Website</title>\n\t</head>\n\n\t<body>\n<h1>%s</h1>\n\t\t<p>\n\t\t\t%d + %d = %d\n\t\t</p>\n\t</body>\n</html>\n", args->pagename, args->pagename, args->first, args->second, args->sum ); }This is then written to a file called
tmplfuncs.gen
that gets #include
d
by website.c
. Within website.c
, the template can
be rendered using the render_html()
macro like so:
render_html( sockfd, my_example_tmpl_html, { .pagename = "Welcome", .first = 12, .second = 34, .sum = 12 + 34 } );This in turn expands into a direct call to the generated template function:
const char *__resp = RESP_200 CONTENT_TYPE_HTML END_HDRS; write(sockfd, __resp, strlen(__resp)); _tmplfunc_my_example_tmpl_html( sockfd, &(struct _tmplargs_my_example_tmpl_html) { .pagename = "Welcome", .first = 12, .second = 34, .sum = 12 + 34 } );
Unlike templates, files in static/
are not
compiled into the executable's text. Instead they are read off
the filesystem on-demand. When deploying the server, the
website
executable must be run from within the
directory that contains static/
.
The main process runs accept()
in a loop to accept incoming TCP connections.
Each time a new connection comes in, we fork()
a process to handle the connection
concurrently to any others. Within this process, we read a buffer of data from the socket and
then parse (bool parse_request(char *req, enum method *method_out, char **uri_out,
char **vsn_out, char **hdrs_out)
in website.c
) and validate
(bool validate_request(int fd, enum method method, char *uri, char *vsn, char *hdrs)
)
the request. Finally, void handle_request(int fd, struct sockaddr_in *sockip, enum method
method, char *uri)
is called, which runs the main request/response logic.
The main request/response logic consists of matching the request URI and returning the requested
content. A request handler process is automatically killed if it hasn't finished handling its
request after a timeout. This mitigates some DoS attacks like
slowloris.