How Everything Works

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

HTML Templates

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 #included 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
		}
	);

Static Files

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/.

Request Lifecycle

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.



Back to homepage