Browse Source

H2O: Add implementation

This implementation embeds the HTTP server in the application using
libh2o instead of running the server separately. All test types have
been implemented.
Anton Kirilov 9 years ago
parent
commit
29ef2fd38e

+ 1 - 0
.travis.yml

@@ -20,6 +20,7 @@ env:
     - "TESTDIR=C/duda"
     - "TESTDIR=C/haywire"
     - "TESTDIR=C/onion"
+    - "TESTDIR=C/h2o"
     - "TESTDIR=CSharp/aspnet"
     ## - "TESTDIR=CSharp/aspnet-stripped"
     - "TESTDIR=CSharp/evhttp-sharp"

+ 23 - 0
frameworks/C/h2o/CMakeLists.txt

@@ -0,0 +1,23 @@
+cmake_minimum_required(VERSION 2.8.0)
+project(h2o_app)
+find_library(H2O_LIB h2o-evloop)
+find_library(MUSTACHE_C_LIB mustache_c)
+find_library(YAJL_LIB yajl)
+find_path(H2O_INCLUDE h2o.h)
+find_path(MUSTACHE_C_INCLUDE mustache.h)
+find_path(YAJL_INCLUDE yajl/yajl_gen.h)
+set(COMMON_OPTIONS -flto -pthread)
+add_compile_options(-std=gnu11 -pedantic -Wall -Wextra ${COMMON_OPTIONS})
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fsanitize=address")
+set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fstack-protector-all -D_FORTIFY_SOURCE=2")
+set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Ofast")
+set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Ofast")
+add_definitions(-DH2O_USE_LIBUV=0)
+include_directories(src ${H2O_INCLUDE} ${MUSTACHE_C_INCLUDE} ${YAJL_INCLUDE})
+file(GLOB SOURCES "src/*.c")
+add_executable(${PROJECT_NAME} ${SOURCES})
+target_link_libraries(${PROJECT_NAME} ${COMMON_OPTIONS})
+target_link_libraries(${PROJECT_NAME} ${H2O_LIB} ssl crypto pq ${MUSTACHE_C_LIB} ${YAJL_LIB})
+install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin)
+file(GLOB TEMPLATES "template/*")
+install(FILES ${TEMPLATES} DESTINATION share/${PROJECT_NAME}/template)

+ 11 - 0
frameworks/C/h2o/README.md

@@ -0,0 +1,11 @@
+# h2o
+
+This is a framework implementation using the [H2O](https://h2o.examp1e.net/) HTTP server.
+
+## Requirements
+
+CMake, H2O, libpq, mustache-c, OpenSSL, YAJL
+
+## Contact
+
+Anton Kirilov <[email protected]>

+ 27 - 0
frameworks/C/h2o/benchmark_config.json

@@ -0,0 +1,27 @@
+{
+  "framework": "h2o",
+  "tests": [{
+    "default": {
+      "setup_file": "setup",
+      "json_url": "/json",
+      "db_url": "/db",
+      "query_url": "/queries?queries=",
+      "fortune_url": "/fortunes",
+      "update_url": "/updates?queries=",
+      "plaintext_url": "/plaintext",
+      "port": 8080,
+      "approach": "Realistic",
+      "classification": "Platform",
+      "database": "Postgres",
+      "framework": "H2O",
+      "language": "C",
+      "orm": "Raw",
+      "platform": "H2O",
+      "webserver": "H2O",
+      "os": "Linux",
+      "database_os": "Linux",
+      "display_name": "h2o",
+      "notes": ""
+    }
+  }]
+}

+ 57 - 0
frameworks/C/h2o/setup.sh

@@ -0,0 +1,57 @@
+#!/bin/bash
+
+fw_depends h2o mustache-c yajl
+
+H2O_APP_HOME="${IROOT}/h2o_app"
+BUILD_DIR="${H2O_APP_HOME}_build"
+H2O_APP_PROFILE_PORT="54321"
+H2O_APP_PROFILE_URL="http://127.0.0.1:$H2O_APP_PROFILE_PORT"
+
+build_h2o_app()
+{
+	cmake -DCMAKE_INSTALL_PREFIX="$H2O_APP_HOME" -DCMAKE_BUILD_TYPE=Release \
+		-DCMAKE_LIBRARY_PATH="${H2O_HOME}/lib;${MUSTACHE_C_HOME}/lib;${YAJL_HOME}/lib" \
+		-DCMAKE_INCLUDE_PATH="${H2O_HOME}/include;${MUSTACHE_C_HOME}/include;${YAJL_HOME}/include" \
+		-DCMAKE_C_FLAGS="-march=native $1" "$TROOT"
+	make -j "$(nproc)"
+}
+
+run_curl()
+{
+	for ((i = 0; i < 10; i++)); do
+		curl "${H2O_APP_PROFILE_URL}/$1" > /dev/null 2>&1
+	done
+}
+
+run_h2o_app()
+{
+	"$1/h2o_app" -a2 -f "$2/template/fortunes.mustache" -m2 "$3" "$4" \
+		-d "host=$DBHOST dbname=hello_world user=benchmarkdbuser password=benchmarkdbpass" &
+}
+
+generate_profile_data()
+{
+	run_h2o_app . "${TROOT}" -p$H2O_APP_PROFILE_PORT -t1
+	local -r H2O_APP_PROFILE_PID=$!
+	while ! curl ${H2O_APP_PROFILE_URL} > /dev/null 2>&1; do sleep 1; done
+	run_curl json
+	run_curl db
+	run_curl queries?queries=20
+	run_curl fortunes
+	run_curl updates?queries=20
+	run_curl plaintext
+	kill -s SIGTERM $H2O_APP_PROFILE_PID
+	wait $H2O_APP_PROFILE_PID
+}
+
+install -d "$BUILD_DIR"
+pushd "$BUILD_DIR"
+build_h2o_app "-fprofile-generate"
+generate_profile_data
+make clean
+rm -f CMakeCache.txt
+build_h2o_app "-fprofile-use"
+make -j "$(nproc)" install
+popd
+rm -rf "$BUILD_DIR"
+run_h2o_app "${H2O_APP_HOME}/bin" "${H2O_APP_HOME}/share/h2o_app"

+ 23 - 0
frameworks/C/h2o/source_code

@@ -0,0 +1,23 @@
+./src/bitset.h
+./src/database.c
+./src/database.h
+./src/error.c
+./src/error.h
+./src/event_loop.c
+./src/event_loop.h
+./src/fortune.c
+./src/fortune.h
+./src/list.h
+./src/main.c
+./src/request_handler.c
+./src/request_handler.h
+./src/template.c
+./src/template.h
+./src/thread.c
+./src/thread.h
+./src/tls.c
+./src/tls.h
+./src/utility.c
+./src/utility.h
+./src/world.c
+./src/world.h

+ 60 - 0
frameworks/C/h2o/src/bitset.h

@@ -0,0 +1,60 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef BITSET_H_
+
+#define BITSET_H_
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "utility.h"
+
+typedef uint_fast32_t bitset_base_t;
+
+#define BITSET_ISSET(i, b) bitset_isset((i), (b), sizeof(b) * CHAR_BIT)
+#define BITSET_SET(i, b) bitset_set((i), (b), sizeof(b) * CHAR_BIT)
+// Use a designated initializer to set all array elements to zero.
+#define DEFINE_BITSET(b, s) \
+	assert(s); \
+	bitset_base_t (b)[((s) - 1) / (sizeof(bitset_base_t) * CHAR_BIT) + 1] = {[0] = 0}
+
+static inline void bitset_set(size_t i, bitset_base_t *b, size_t num)
+{
+	assert(i < num);
+
+	IGNORE_FUNCTION_PARAMETER(num);
+
+	const bitset_base_t mask = ((bitset_base_t) 1) << (i % (sizeof(*b) * CHAR_BIT));
+
+	b[i / (sizeof(*b) * CHAR_BIT)] |= mask;
+}
+
+static inline bool bitset_isset(size_t i, bitset_base_t *b, size_t num)
+{
+	assert(i < num);
+
+	IGNORE_FUNCTION_PARAMETER(num);
+
+	return (b[i / (sizeof(*b) * CHAR_BIT)] >> (i % (sizeof(*b) * CHAR_BIT))) & (bitset_base_t) 1;
+}
+
+#endif // BITSET_H_

+ 518 - 0
frameworks/C/h2o/src/database.c

@@ -0,0 +1,518 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <h2o.h>
+#include <stdlib.h>
+#include <string.h>
+#include <postgresql/libpq-fe.h>
+
+#include "database.h"
+#include "error.h"
+#include "list.h"
+#include "thread.h"
+#include "utility.h"
+
+#define IS_RESETTING 1
+#define IS_WRITING 2
+
+typedef struct {
+	list_t l;
+	PGconn *conn;
+	thread_context_t *ctx;
+	void (*on_write_ready)(void *);
+	db_query_param_t *param;
+	h2o_socket_t *sock;
+	size_t prep_stmt_idx;
+	uint_fast32_t flags;
+	h2o_timeout_entry_t h2o_timeout_entry;
+} db_conn_t;
+
+static int do_database_write(db_conn_t *db_conn);
+static void do_execute_query(db_conn_t *db_conn);
+static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string);
+static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string);
+static void on_database_connect_timeout(h2o_timeout_entry_t *entry);
+static void on_database_error(db_conn_t *db_conn, const char *error_string);
+static void on_database_read_ready(h2o_socket_t *db_sock, const char *err);
+static void on_database_timeout(h2o_timeout_entry_t *entry);
+static void on_database_write_ready(void *data);
+static void poll_database_connection(h2o_socket_t *db_sock, const char *err);
+static void process_query(db_conn_t *db_conn);
+static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn);
+static int start_database_write_polling(db_conn_t *db_conn);
+static void stop_database_write_polling(db_conn_t *db_conn);
+
+static const struct {
+	const char *name;
+	const char *query;
+} prepared_statement[] = {
+	{FORTUNE_TABLE_NAME, "SELECT * FROM " FORTUNE_TABLE_NAME ";"},
+	{WORLD_TABLE_NAME,
+	 "SELECT * FROM " WORLD_TABLE_NAME " "
+	 "WHERE " WORLD_TABLE_NAME "." ID_FIELD_NAME " = $1::integer;"},
+};
+
+static void do_execute_query(db_conn_t *db_conn)
+{
+	const int ec = db_conn->param->flags & IS_PREPARED ?
+	               PQsendQueryPrepared(db_conn->conn,
+	                                   db_conn->param->command,
+	                                   db_conn->param->nParams,
+	                                   db_conn->param->paramValues,
+	                                   db_conn->param->paramLengths,
+	                                   db_conn->param->paramFormats,
+	                                   db_conn->param->resultFormat) :
+	               PQsendQuery(db_conn->conn, db_conn->param->command);
+
+	if (ec) {
+		if (db_conn->param->flags & IS_SINGLE_ROW)
+			PQsetSingleRowMode(db_conn->conn);
+
+		db_conn->h2o_timeout_entry.cb = on_database_timeout;
+		h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
+		                 &db_conn->ctx->db_state.h2o_timeout,
+		                 &db_conn->h2o_timeout_entry);
+		h2o_socket_read_start(db_conn->sock, on_database_read_ready);
+		on_database_write_ready(&db_conn->on_write_ready);
+	}
+	else
+		on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+}
+
+static void error_notification(thread_context_t *ctx, bool timeout, const char *error_string)
+{
+	if (!--ctx->db_state.db_conn_num) {
+		// We don't want to keep requests waiting for an unbounded amount of time.
+		list_t *iter = ctx->db_state.queries.head;
+
+		ctx->db_state.queries.head = NULL;
+		ctx->db_state.queries.tail = &ctx->db_state.queries.head;
+		ctx->db_state.query_num = 0;
+
+		if (iter)
+			do {
+				db_query_param_t * const param = H2O_STRUCT_FROM_MEMBER(db_query_param_t, l, iter);
+
+				// The callback may free the db_query_param_t structure.
+				iter = iter->next;
+
+				if (timeout)
+					param->on_timeout(param);
+				else
+					param->on_error(param, error_string);
+			} while (iter);
+	}
+}
+
+static void on_database_connect_error(db_conn_t *db_conn, bool timeout, const char *error_string)
+{
+	thread_context_t * const ctx = db_conn->ctx;
+
+	error_notification(ctx, timeout, error_string);
+	h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+	h2o_socket_read_stop(db_conn->sock);
+	h2o_socket_close(db_conn->sock);
+	PQfinish(db_conn->conn);
+	free(db_conn);
+}
+
+static void on_database_connect_timeout(h2o_timeout_entry_t *entry)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
+
+	on_database_connect_error(db_conn, true, DB_TIMEOUT_ERROR);
+}
+
+static void on_database_error(db_conn_t *db_conn, const char *error_string)
+{
+	if (db_conn->prep_stmt_idx < ARRAY_SIZE(prepared_statement))
+		on_database_connect_error(db_conn, false, error_string);
+	else {
+		if (db_conn->param) {
+			db_conn->param->on_error(db_conn->param, error_string);
+			db_conn->param = NULL;
+		}
+
+		if (PQstatus(db_conn->conn) == CONNECTION_OK) {
+			h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+			h2o_socket_read_stop(db_conn->sock);
+			process_query(db_conn);
+		}
+		else
+			start_database_connect(db_conn->ctx, db_conn);
+	}
+}
+
+static void on_database_read_ready(h2o_socket_t *db_sock, const char *err)
+{
+	db_conn_t * const db_conn = db_sock->data;
+
+	if (!err) {
+		if (PQconsumeInput(db_conn->conn)) {
+			const int send_status = PQflush(db_conn->conn);
+
+			if (send_status > 0 && start_database_write_polling(db_conn)) {
+				on_database_error(db_conn, EPOLL_ERR_MSG);
+				return;
+			}
+
+			if (send_status >= 0) {
+				while (!PQisBusy(db_conn->conn)) {
+					PGresult * const result = PQgetResult(db_conn->conn);
+
+					if (db_conn->param)
+						switch (db_conn->param->on_result(db_conn->param, result)) {
+							case WANT_WRITE:
+								db_conn->flags |= IS_WRITING;
+
+								if (do_database_write(db_conn))
+									return;
+
+								break;
+							case DONE:
+								db_conn->param = NULL;
+								h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+								break;
+							default:
+								break;
+						}
+					else if (result)
+						PQclear(result);
+
+					if (!result) {
+						assert(!db_conn->param);
+						h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+						h2o_socket_read_stop(db_conn->sock);
+						process_query(db_conn);
+						break;
+					}
+				}
+
+				return;
+			}
+		}
+
+		err = PQerrorMessage(db_conn->conn);
+	}
+
+	on_database_error(db_conn, err);
+}
+
+static void on_database_timeout(h2o_timeout_entry_t *entry)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, h2o_timeout_entry, entry);
+
+	if (db_conn->param) {
+		db_conn->param->on_timeout(db_conn->param);
+		db_conn->param = NULL;
+	}
+
+	start_database_connect(db_conn->ctx, db_conn);
+}
+
+static void on_database_write_ready(void *data)
+{
+	db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, on_write_ready, data);
+
+	if (db_conn->prep_stmt_idx) {
+		const int send_status = PQflush(db_conn->conn);
+
+		if (!send_status) {
+			if (db_conn->flags & IS_WRITING && db_conn->param)
+				do_database_write(db_conn);
+		}
+		else if (send_status < 0)
+			on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+		else if (send_status > 0 && start_database_write_polling(db_conn))
+			on_database_error(db_conn, EPOLL_ERR_MSG);
+	}
+	else
+		poll_database_connection(db_conn->sock, NULL);
+}
+
+static void poll_database_connection(h2o_socket_t *db_sock, const char *err)
+{
+	db_conn_t * const db_conn = db_sock->data;
+
+	if (!err) {
+		const PostgresPollingStatusType status = db_conn->flags & IS_RESETTING ?
+		                                         PQresetPoll(db_conn->conn) :
+		                                         PQconnectPoll(db_conn->conn);
+
+		switch (status) {
+			case PGRES_POLLING_WRITING:
+				if (start_database_write_polling(db_conn)) {
+					err = EPOLL_ERR_MSG;
+					break;
+				}
+
+				return;
+			case PGRES_POLLING_OK:
+				if (PQsetnonblocking(db_conn->conn, 1)) {
+					err = PQerrorMessage(db_conn->conn);
+					break;
+				}
+
+				h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+				h2o_socket_read_stop(db_conn->sock);
+				process_query(db_conn);
+				return;
+			case PGRES_POLLING_READING:
+				return;
+			default:
+				err = PQerrorMessage(db_conn->conn);
+		}
+	}
+
+	on_database_connect_error(db_conn, false, err);
+}
+
+static void process_query(db_conn_t *db_conn)
+{
+	if (db_conn->prep_stmt_idx < ARRAY_SIZE(prepared_statement)) {
+		if (PQsendPrepare(db_conn->conn,
+		                  prepared_statement[db_conn->prep_stmt_idx].name,
+		                  prepared_statement[db_conn->prep_stmt_idx].query,
+		                  0,
+		                  NULL)) {
+			db_conn->prep_stmt_idx++;
+			db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
+			h2o_timeout_link(db_conn->ctx->event_loop.h2o_ctx.loop,
+			                 &db_conn->ctx->db_state.h2o_timeout,
+			                 &db_conn->h2o_timeout_entry);
+			h2o_socket_read_start(db_conn->sock, on_database_read_ready);
+			on_database_write_ready(&db_conn->on_write_ready);
+		}
+		else
+			on_database_connect_error(db_conn, false, PQerrorMessage(db_conn->conn));
+	}
+	else if (db_conn->ctx->db_state.query_num) {
+		db_conn->ctx->db_state.query_num--;
+
+		if (db_conn->ctx->db_state.queries.tail == &db_conn->ctx->db_state.queries.head->next) {
+			assert(!db_conn->ctx->db_state.query_num);
+			db_conn->ctx->db_state.queries.tail = &db_conn->ctx->db_state.queries.head;
+		}
+
+		db_conn->param = H2O_STRUCT_FROM_MEMBER(db_query_param_t,
+		                                        l,
+		                                        db_conn->ctx->db_state.queries.head);
+		db_conn->ctx->db_state.queries.head = db_conn->ctx->db_state.queries.head->next;
+		do_execute_query(db_conn);
+	}
+	else {
+		db_conn->l.next = db_conn->ctx->db_state.db_conn;
+		db_conn->ctx->db_state.db_conn = &db_conn->l;
+		db_conn->ctx->db_state.free_db_conn_num++;
+	}
+}
+
+static void start_database_connect(thread_context_t *ctx, db_conn_t *db_conn)
+{
+	char buf[128] = "";
+	const char *error_string = buf;
+
+	if (db_conn) {
+		db_conn->prep_stmt_idx = 0;
+		db_conn->flags = IS_RESETTING;
+		h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+		stop_database_write_polling(db_conn);
+		h2o_socket_read_stop(db_conn->sock);
+		h2o_socket_close(db_conn->sock);
+
+		if (!PQresetStart(db_conn->conn)) {
+			strncpy(buf, PQerrorMessage(db_conn->conn), sizeof(buf));
+			buf[sizeof(buf) - 1] = '\0';
+			goto error_dup;
+		}
+	}
+	else {
+		ctx->db_state.db_conn_num++;
+		db_conn = calloc(1, sizeof(*db_conn));
+
+		if (!db_conn) {
+			error_string = MEM_ALLOC_ERR_MSG;
+			goto error;
+		}
+
+		const char * const conninfo =
+			ctx->global_data->config->db_host ? ctx->global_data->config->db_host : "";
+
+		db_conn->conn = PQconnectStart(conninfo);
+
+		if (!db_conn->conn) {
+			error_string = MEM_ALLOC_ERR_MSG;
+			goto error_connect;
+		}
+
+		if (PQstatus(db_conn->conn) == CONNECTION_BAD) {
+			strncpy(buf, PQerrorMessage(db_conn->conn), sizeof(buf));
+			buf[sizeof(buf) - 1] = '\0';
+			goto error_dup;
+		}
+	}
+
+	const int sd = dup(PQsocket(db_conn->conn));
+
+	if (sd < 0) {
+		if (strerror_r(errno, buf, sizeof(buf)))
+			*buf = '\0';
+
+		goto error_dup;
+	}
+
+	const int flags = fcntl(sd, F_GETFD);
+
+	if (flags < 0 || fcntl(sd, F_SETFD, flags | FD_CLOEXEC)) {
+		if (strerror_r(errno, buf, sizeof(buf)))
+			*buf = '\0';
+
+		goto error_dup;
+	}
+
+	db_conn->sock = h2o_evloop_socket_create(ctx->event_loop.h2o_ctx.loop,
+	                                         sd,
+	                                         H2O_SOCKET_FLAG_DONT_READ);
+
+	if (db_conn->sock) {
+		db_conn->sock->data = db_conn;
+		db_conn->ctx = ctx;
+		db_conn->h2o_timeout_entry.cb = on_database_connect_timeout;
+		h2o_timeout_link(ctx->event_loop.h2o_ctx.loop,
+		                 &ctx->db_state.h2o_timeout,
+		                 &db_conn->h2o_timeout_entry);
+		h2o_socket_read_start(db_conn->sock, poll_database_connection);
+
+		if (!start_database_write_polling(db_conn))
+			return;
+
+		h2o_socket_read_stop(db_conn->sock);
+		h2o_timeout_unlink(&db_conn->h2o_timeout_entry);
+		h2o_socket_close(db_conn->sock);
+		error_string = "socket write polling failure";
+	}
+	else {
+		error_string = "could not allocate H2O socket";
+		close(sd);
+	}
+
+error_dup:
+	PQfinish(db_conn->conn);
+error_connect:
+	free(db_conn);
+error:
+	error_notification(ctx, false, error_string);
+}
+
+static int start_database_write_polling(db_conn_t *db_conn)
+{
+	const bool rearm = !!db_conn->on_write_ready;
+
+	db_conn->on_write_ready = on_database_write_ready;
+	return start_write_polling(PQsocket(db_conn->conn),
+	                           &db_conn->on_write_ready,
+	                           rearm,
+	                           &db_conn->ctx->event_loop);
+}
+
+static void stop_database_write_polling(db_conn_t *db_conn)
+{
+	db_conn->on_write_ready = NULL;
+	stop_write_polling(PQsocket(db_conn->conn), &db_conn->ctx->event_loop);
+}
+
+void connect_to_database(thread_context_t *ctx)
+{
+	for (size_t i = ctx->db_state.db_conn_num; i < ctx->global_data->config->max_db_conn_num; i++)
+		start_database_connect(ctx, NULL);
+}
+
+static int do_database_write(db_conn_t *db_conn)
+{
+	assert(db_conn->param);
+
+	int ret = db_conn->param->on_write_ready(db_conn->param, db_conn->conn);
+
+	if (!ret)
+		db_conn->flags &= ~IS_WRITING;
+	else if (ret < 0)
+		on_database_error(db_conn, PQerrorMessage(db_conn->conn));
+	else if (start_database_write_polling(db_conn))
+		on_database_error(db_conn, EPOLL_ERR_MSG);
+	else
+		ret = 0;
+
+	return ret;
+}
+
+int execute_query(thread_context_t *ctx, db_query_param_t *param)
+{
+	int ret = EXIT_SUCCESS;
+
+	if (ctx->db_state.free_db_conn_num) {
+		db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, ctx->db_state.db_conn);
+
+		ctx->db_state.db_conn = db_conn->l.next;
+		ctx->db_state.free_db_conn_num--;
+		db_conn->param = param;
+		do_execute_query(db_conn);
+	}
+	else if (ctx->db_state.query_num < ctx->global_data->config->max_query_num) {
+		param->l.next = NULL;
+		*ctx->db_state.queries.tail = &param->l;
+		ctx->db_state.queries.tail = &param->l.next;
+		ctx->db_state.query_num++;
+
+		if (ctx->db_state.db_conn_num < ctx->global_data->config->max_db_conn_num)
+			start_database_connect(ctx, NULL);
+	}
+	else
+		ret = EXIT_FAILURE;
+
+	return ret;
+}
+
+void free_database_state(h2o_loop_t *loop, db_state_t *db_state)
+{
+	assert(!db_state->query_num && db_state->free_db_conn_num == db_state->db_conn_num);
+
+	list_t *iter = db_state->db_conn;
+
+	if (iter)
+		do {
+			db_conn_t * const db_conn = H2O_STRUCT_FROM_MEMBER(db_conn_t, l, iter);
+
+			iter = iter->next;
+			assert(!db_conn->param && !h2o_timeout_is_linked(&db_conn->h2o_timeout_entry));
+			h2o_socket_close(db_conn->sock);
+			PQfinish(db_conn->conn);
+			free(db_conn);
+		} while (iter);
+
+	h2o_timeout_dispose(loop, &db_state->h2o_timeout);
+}
+
+void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state)
+{
+	memset(db_state, 0, sizeof(*db_state));
+	db_state->queries.tail = &db_state->queries.head;
+	h2o_timeout_init(loop, &db_state->h2o_timeout, H2O_DEFAULT_HTTP1_REQ_TIMEOUT);
+}

+ 88 - 0
frameworks/C/h2o/src/database.h

@@ -0,0 +1,88 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef DATABASE_H_
+
+#define DATABASE_H_
+
+#include <h2o.h>
+#include <stdint.h>
+#include <postgresql/libpq-fe.h>
+
+#include "list.h"
+
+#define COPY_HEADER "PGCOPY\n\377\r\n\0\0\0\0\0\0\0\0\0"
+#define DB_REQ_ERROR "too many concurrent database requests\n"
+#define DB_TIMEOUT_ERROR "database timeout\n"
+#define FORTUNE_TABLE_NAME "Fortune"
+#define ID_FIELD_NAME "id"
+#define IS_PREPARED 1
+#define IS_SINGLE_ROW 2
+#define MAX_ID 10000
+#define MESSAGE_FIELD_NAME "message"
+#define WORLD_TABLE_NAME "World"
+
+#define UPDATE_QUERY \
+	"CREATE TEMP TABLE input(LIKE World INCLUDING ALL) ON COMMIT DROP;" \
+	"COPY input FROM STDIN WITH (FORMAT binary);" \
+	"UPDATE World SET randomNumber = input.randomNumber FROM input " \
+	"WHERE World." ID_FIELD_NAME " = input." ID_FIELD_NAME ";"
+
+typedef enum {
+	SUCCESS,
+	DONE,
+	WANT_WRITE
+} result_return_t;
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct db_query_param_t db_query_param_t;
+
+typedef result_return_t (*on_result_t)(db_query_param_t *, PGresult *);
+
+struct db_query_param_t {
+	list_t l;
+	void (*on_error)(db_query_param_t *, const char *);
+	on_result_t on_result;
+	void (*on_timeout)(db_query_param_t *);
+	int (*on_write_ready)(db_query_param_t *, PGconn *);
+	const char *command;
+	const char * const *paramValues;
+	const int *paramLengths;
+	const int *paramFormats;
+	size_t nParams;
+	uint_fast32_t flags;
+	int resultFormat;
+};
+
+typedef struct {
+	list_t *db_conn;
+	queue_t queries;
+	size_t db_conn_num;
+	size_t free_db_conn_num;
+	size_t query_num;
+	h2o_timeout_t h2o_timeout;
+} db_state_t;
+
+void connect_to_database(thread_context_t *ctx);
+int execute_query(thread_context_t *ctx, db_query_param_t *param);
+void free_database_state(h2o_loop_t *loop, db_state_t *db_state);
+void initialize_database_state(h2o_loop_t *loop, db_state_t *db_state);
+
+#endif // DATABASE_H_

+ 66 - 0
frameworks/C/h2o/src/error.c

@@ -0,0 +1,66 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <libgen.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include "error.h"
+
+void print_error(const char *file,
+                 unsigned line,
+                 const char *function,
+                 const char *error_string, ...)
+{
+	char * const file_name = strdup(file);
+
+	if (file_name)
+		file = basename(file_name);
+
+	flockfile(stderr);
+	fprintf(stderr, "[%d] %s: %u: %s(): ", (int) syscall(SYS_gettid), file, line, function);
+
+	va_list arg;
+
+	va_start(arg, error_string);
+	vfprintf(stderr, error_string, arg);
+	va_end(arg);
+	putc_unlocked('\n', stderr);
+	funlockfile(stderr);
+
+	if (file_name)
+		free(file_name);
+}
+
+void print_library_error(const char *file,
+                         unsigned line,
+                         const char *function,
+                         int error_code)
+{
+	char error_string[128];
+
+	if (strerror_r(error_code, error_string, sizeof(error_string)))
+		*error_string = '\0';
+
+	print_error(file, line, function, "%s (%d)", error_string, error_code);
+}

+ 85 - 0
frameworks/C/h2o/src/error.h

@@ -0,0 +1,85 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef ERROR_H_
+
+#define ERROR_H_
+
+#include <errno.h>
+#include <stdarg.h>
+#include <yajl/yajl_gen.h>
+
+#define EPOLL_ERR_MSG "epoll_ctl() failure\n"
+#define MEM_ALLOC_ERR_MSG "memory allocation failure\n"
+
+#define CHECK_ERRNO(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			abort(); \
+		} \
+	} while(0)
+
+#define CHECK_ERRNO_RETURN(out, function, ...) \
+	do { \
+		const int return_value = (function)(__VA_ARGS__); \
+		\
+		if (return_value == -1) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			abort(); \
+		} \
+		\
+		(out) = return_value; \
+	} while(0)
+
+#define CHECK_ERROR(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, error_code); \
+			abort(); \
+		} \
+	} while(0)
+
+#define CHECK_YAJL_STATUS(function, ...) \
+	do { \
+		const yajl_gen_status status_code = (function)(__VA_ARGS__); \
+		\
+		if (status_code != yajl_gen_status_ok) { \
+			print_error(__FILE__, __LINE__, #function, "Error (%d)", (int) status_code); \
+			goto error_yajl; \
+		} \
+	} while(0)
+
+#define ERROR(...) print_error(__FILE__, __LINE__, __func__, __VA_ARGS__)
+#define LIBRARY_ERROR(function) print_library_error(__FILE__, __LINE__, (function), errno)
+
+void print_error(const char *file,
+                 unsigned line,
+                 const char *function,
+                 const char *error_string, ...);
+void print_library_error(const char *file,
+                         unsigned line,
+                         const char *function,
+                         int error_code);
+
+#endif // ERROR_H_

+ 205 - 0
frameworks/C/h2o/src/event_loop.c

@@ -0,0 +1,205 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <h2o.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/epoll.h>
+#include <sys/syscall.h>
+
+#include "error.h"
+#include "event_loop.h"
+#include "thread.h"
+#include "utility.h"
+
+static void accept_connection(h2o_socket_t *listener, const char *err);
+static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err);
+static void on_close_connection(void *data);
+static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages);
+static void shutdown_server(h2o_socket_t *listener, const char *err);
+
+static void accept_connection(h2o_socket_t *listener, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      listener->data);
+
+		if (!ctx->global_data->shutdown) {
+			size_t accepted = 0;
+
+			do {
+				h2o_socket_t * const sock = h2o_evloop_socket_accept(listener);
+
+				if (!sock)
+					break;
+
+				ctx->event_loop.conn_num++;
+				sock->on_close.cb = on_close_connection;
+				sock->on_close.data = &ctx->event_loop.conn_num;
+				h2o_accept(&ctx->event_loop.h2o_accept_ctx, sock);
+			} while (++accepted < ctx->global_data->config->max_accept);
+		}
+	}
+}
+
+static void do_epoll_wait(h2o_socket_t *epoll_sock, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      epoll_sock->data);
+		const size_t num_event = ctx->db_state.db_conn_num - ctx->db_state.free_db_conn_num;
+		int ready;
+		struct epoll_event event[num_event];
+
+		do
+			ready = epoll_wait(ctx->event_loop.epoll_fd, event, num_event, 0);
+		while (ready < 0 && errno == EINTR);
+
+		if (ready > 0)
+			for (size_t i = 0; i < (size_t) ready; i++) {
+				void (** const on_write_ready)(void *) = event[i].data.ptr;
+
+				(*on_write_ready)(on_write_ready);
+			}
+	}
+}
+
+static void on_close_connection(void *data)
+{
+	size_t * const conn_num = data;
+
+	(*conn_num)--;
+}
+
+static void process_messages(h2o_multithread_receiver_t *receiver, h2o_linklist_t *messages)
+{
+	IGNORE_FUNCTION_PARAMETER(messages);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_receiver,
+	                                                      receiver);
+
+	h2o_socket_read_stop(ctx->event_loop.h2o_socket);
+}
+
+static void shutdown_server(h2o_socket_t *listener, const char *err)
+{
+	if (!err) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop,
+		                                                      listener->data);
+
+		ctx->global_data->shutdown = true;
+		h2o_socket_read_stop(ctx->event_loop.h2o_socket);
+
+		for (size_t i = 1; i < ctx->global_data->config->thread_num; i++)
+			h2o_multithread_send_message(&ctx[i].event_loop.h2o_receiver, NULL);
+	}
+}
+
+void event_loop(thread_context_t *ctx)
+{
+	ctx->tid = syscall(SYS_gettid);
+	ctx->random_seed = ctx->tid;
+
+	while (!ctx->global_data->shutdown || ctx->event_loop.conn_num)
+		h2o_evloop_run(ctx->event_loop.h2o_ctx.loop);
+}
+
+void free_event_loop(event_loop_t *event_loop)
+{
+	h2o_multithread_unregister_receiver(event_loop->h2o_ctx.queue, &event_loop->h2o_receiver);
+	h2o_socket_close(event_loop->h2o_socket);
+	h2o_socket_close(event_loop->epoll_socket);
+	h2o_context_dispose(&event_loop->h2o_ctx);
+}
+
+void initialize_event_loop(bool is_main_thread,
+                           global_data_t *global_data,
+                           event_loop_t *loop)
+{
+	memset(loop, 0, sizeof(*loop));
+	h2o_context_init(&loop->h2o_ctx, h2o_evloop_create(), &global_data->h2o_config);
+	loop->h2o_accept_ctx.ctx = &loop->h2o_ctx;
+	loop->h2o_accept_ctx.hosts = global_data->h2o_config.hosts;
+	loop->h2o_accept_ctx.ssl_ctx = global_data->ssl_ctx;
+
+	int listener_sd;
+
+	if (is_main_thread)
+		listener_sd = global_data->listener_sd;
+	else {
+		int flags;
+
+		CHECK_ERRNO_RETURN(listener_sd, dup, global_data->listener_sd);
+		CHECK_ERRNO_RETURN(flags, fcntl, listener_sd, F_GETFD);
+		CHECK_ERRNO(fcntl, listener_sd, F_SETFD, flags | FD_CLOEXEC);
+	}
+
+	// Let all the threads race to call accept() on the socket; since the latter is
+	// non-blocking, that will effectively act as load balancing.
+	loop->h2o_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+	                                            listener_sd,
+	                                            H2O_SOCKET_FLAG_DONT_READ);
+	loop->h2o_socket->data = loop;
+	h2o_socket_read_start(loop->h2o_socket, accept_connection);
+	h2o_multithread_register_receiver(loop->h2o_ctx.queue,
+	                                  &loop->h2o_receiver,
+	                                  process_messages);
+	// libh2o's event loop does not support write polling unless it
+	// controls sending the data as well, so do read polling on the
+	// epoll file descriptor as a work-around.
+	CHECK_ERRNO_RETURN(loop->epoll_fd, epoll_create1, EPOLL_CLOEXEC);
+	loop->epoll_socket = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+	                                              loop->epoll_fd,
+	                                              H2O_SOCKET_FLAG_DONT_READ);
+	loop->epoll_socket->data = loop;
+	h2o_socket_read_start(loop->epoll_socket, do_epoll_wait);
+
+	if (is_main_thread) {
+		global_data->signals = h2o_evloop_socket_create(loop->h2o_ctx.loop,
+		                                                global_data->signal_fd,
+		                                                H2O_SOCKET_FLAG_DONT_READ);
+		global_data->signals->data = loop;
+		h2o_socket_read_start(global_data->signals, shutdown_server);
+	}
+}
+
+int start_write_polling(int fd,
+                        void (**on_write_ready)(void *),
+                        bool rearm,
+                        event_loop_t *event_loop)
+{
+	struct epoll_event event;
+
+	memset(&event, 0, sizeof(event));
+	event.data.ptr = on_write_ready;
+	event.events = EPOLLET | EPOLLONESHOT | EPOLLOUT | EPOLLRDHUP;
+	return epoll_ctl(event_loop->epoll_fd, rearm ? EPOLL_CTL_MOD : EPOLL_CTL_ADD, fd, &event);
+}
+
+void stop_write_polling(int fd, event_loop_t *event_loop)
+{
+	epoll_ctl(event_loop->epoll_fd, EPOLL_CTL_DEL, fd, NULL);
+}

+ 52 - 0
frameworks/C/h2o/src/event_loop.h

@@ -0,0 +1,52 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef EVENT_LOOP_H_
+
+#define EVENT_LOOP_H_
+
+#include <h2o.h>
+#include <stdbool.h>
+
+#include "utility.h"
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct {
+	h2o_socket_t *epoll_socket;
+	h2o_socket_t *h2o_socket;
+	size_t conn_num;
+	int epoll_fd;
+	h2o_accept_ctx_t h2o_accept_ctx;
+	h2o_context_t h2o_ctx;
+	h2o_multithread_receiver_t h2o_receiver;
+} event_loop_t;
+
+void event_loop(thread_context_t *ctx);
+void free_event_loop(event_loop_t *event_loop);
+void initialize_event_loop(bool is_main_thread,
+                           global_data_t *global_data,
+                           event_loop_t *loop);
+int start_write_polling(int fd,
+                        void (**on_write_ready)(void *),
+                        bool rearm,
+                        event_loop_t *event_loop);
+void stop_write_polling(int fd, event_loop_t *event_loop);
+
+#endif // EVENT_LOOP_H_

+ 422 - 0
frameworks/C/h2o/src/fortune.c

@@ -0,0 +1,422 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <postgresql/libpq-fe.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <mustache.h>
+
+#include "database.h"
+#include "error.h"
+#include "fortune.h"
+#include "list.h"
+#include "request_handler.h"
+#include "thread.h"
+#include "utility.h"
+
+#define MAX_IOVEC 64
+#define NEW_FORTUNE_ID "0"
+#define NEW_FORTUNE_MESSAGE "Additional fortune added at request time."
+
+typedef struct {
+	list_t l;
+	size_t iovcnt;
+	size_t max_iovcnt;
+	h2o_iovec_t iov[];
+} iovec_list_t;
+
+typedef struct {
+	const fortune_t *fortune_iter;
+	list_t *iovec_list;
+	iovec_list_t *iovec_list_iter;
+	h2o_req_t *req;
+	list_t *result;
+	size_t num_result;
+	db_query_param_t param;
+	h2o_generator_t generator;
+} fortune_ctx_t;
+
+static uintmax_t add_iovec(mustache_api_t *api,
+                           void *userdata,
+                           char *buffer,
+                           uintmax_t buffer_size);
+static void cleanup_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req);
+static int compare_fortunes(const list_t *x, const list_t *y);
+static void complete_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req);
+static list_t *get_sorted_sublist(list_t *head);
+static list_t *merge_lists(list_t *head1, list_t *head2);
+static void on_fortune_error(db_query_param_t *param, const char *error_string);
+static result_return_t on_fortune_result(db_query_param_t *param, PGresult *result);
+static uintmax_t on_fortune_section(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_section_t *token);
+static void on_fortune_timeout(db_query_param_t *param);
+static uintmax_t on_fortune_variable(mustache_api_t *api,
+                                     void *userdata,
+                                     mustache_token_variable_t *token);
+static list_t *sort_fortunes(list_t *head);
+
+static uintmax_t add_iovec(mustache_api_t *api,
+                           void *userdata,
+                           char *buffer,
+                           uintmax_t buffer_size)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	fortune_ctx_t * const fortune_ctx = userdata;
+	iovec_list_t *iovec_list = fortune_ctx->iovec_list_iter;
+	uintmax_t ret = 1;
+
+	if (iovec_list->iovcnt >= iovec_list->max_iovcnt) {
+		const size_t sz = offsetof(iovec_list_t, iov) + MAX_IOVEC * sizeof(h2o_iovec_t);
+
+		iovec_list = h2o_mem_alloc_pool(&fortune_ctx->req->pool, sz);
+
+		if (iovec_list) {
+			memset(iovec_list, 0, offsetof(iovec_list_t, iov));
+			iovec_list->max_iovcnt = MAX_IOVEC;
+			fortune_ctx->iovec_list_iter->l.next = &iovec_list->l;
+			fortune_ctx->iovec_list_iter = iovec_list;
+		}
+		else
+			ret = 0;
+	}
+
+	if (ret) {
+		iovec_list->iov[iovec_list->iovcnt].base = buffer;
+		iovec_list->iov[iovec_list->iovcnt++].len = buffer_size;
+	}
+
+	return ret;
+}
+
+static void cleanup_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           generator,
+	                                                           self);
+	const list_t *iter = fortune_ctx->result;
+
+	if (iter)
+		do {
+			const fortune_t * const fortune = H2O_STRUCT_FROM_MEMBER(fortune_t, l, iter);
+
+			if (fortune->data)
+				PQclear(fortune->data);
+
+			iter = iter->next;
+		} while (iter);
+}
+
+static int compare_fortunes(const list_t *x, const list_t *y)
+{
+	const fortune_t * const f1 = H2O_STRUCT_FROM_MEMBER(fortune_t, l, x);
+	const fortune_t * const f2 = H2O_STRUCT_FROM_MEMBER(fortune_t, l, y);
+	const size_t sz = MIN(f1->message.len, f2->message.len);
+	int ret = memcmp(f1->message.base, f2->message.base, sz);
+
+	if (!ret)
+		ret = f1->message.len < f2->message.len ? -1 : f1->message.len > f2->message.len;
+
+	return ret;
+}
+
+static void complete_fortunes(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           generator,
+	                                                           self);
+
+	if (fortune_ctx->iovec_list) {
+		iovec_list_t * const iovec_list = H2O_STRUCT_FROM_MEMBER(iovec_list_t,
+		                                                         l,
+		                                                         fortune_ctx->iovec_list);
+
+		fortune_ctx->iovec_list = iovec_list->l.next;
+		h2o_send(fortune_ctx->req, iovec_list->iov, iovec_list->iovcnt, false);
+	}
+	else {
+		h2o_send(req, NULL, 0, true);
+		cleanup_fortunes(self, req);
+	}
+}
+
+static list_t *get_sorted_sublist(list_t *head)
+{
+	list_t *tail = head;
+
+	if (head) {
+		head = head->next;
+
+		while (head && compare_fortunes(tail, head) < 0) {
+			tail = head;
+			head = head->next;
+		}
+
+		while (head && !compare_fortunes(tail, head)) {
+			tail = head;
+			head = head->next;
+		}
+	}
+
+	return tail;
+}
+
+static list_t *merge_lists(list_t *head1, list_t *head2)
+{
+	list_t *ret = NULL;
+	list_t **current = &ret;
+
+	while (1) {
+		if (!head1) {
+			*current = head2;
+			break;
+		}
+		else if (!head2) {
+			*current = head1;
+			break;
+		}
+		// Checking for equality makes this algorithm a stable sort.
+		else if (compare_fortunes(head1, head2) <= 0) {
+			*current = head1;
+			current = &head1->next;
+			head1 = head1->next;
+		}
+		else {
+			*current = head2;
+			current = &head2->next;
+			head2 = head2->next;
+		}
+	}
+
+	return ret;
+}
+
+static void on_fortune_error(db_query_param_t *param, const char *error_string)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+
+	cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+	send_error(BAD_GATEWAY, error_string, fortune_ctx->req);
+}
+
+static result_return_t on_fortune_result(db_query_param_t *param, PGresult *result)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+	int ret = DONE;
+	const ExecStatusType status = PQresultStatus(result);
+
+	if (status == PGRES_TUPLES_OK) {
+		const size_t num_rows = PQntuples(result);
+
+		ret = SUCCESS;
+
+		for (size_t i = 0; i < num_rows; i++) {
+			fortune_t * const fortune = h2o_mem_alloc_pool(&fortune_ctx->req->pool,
+			                                               sizeof(*fortune));
+
+			if (fortune) {
+				memset(fortune, 0, sizeof(*fortune));
+				fortune->id.base = PQgetvalue(result, i, 0);
+				fortune->id.len = PQgetlength(result, i, 0);
+				fortune->message = h2o_htmlescape(&fortune_ctx->req->pool,
+				                                  PQgetvalue(result, i, 1),
+				                                  PQgetlength(result, i, 1));
+				fortune->l.next = fortune_ctx->result;
+				fortune_ctx->result = &fortune->l;
+				fortune_ctx->num_result++;
+
+				if (!i)
+					fortune->data = result;
+			}
+			else {
+				cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+				send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, fortune_ctx->req);
+				ret = DONE;
+
+				if (!i)
+					PQclear(result);
+
+				break;
+			}
+		}
+	}
+	else if (result)
+		PQclear(result);
+	else {
+		mustache_api_t api = {.sectget = on_fortune_section,
+		                      .varget = on_fortune_variable,
+		                      .write = add_iovec};
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop.h2o_ctx,
+		                                                      fortune_ctx->req->conn->ctx);
+		const size_t iovcnt = MIN(MAX_IOVEC, fortune_ctx->num_result * 5 + 2);
+		const size_t sz = offsetof(iovec_list_t, iov) + iovcnt * sizeof(h2o_iovec_t);
+		char _Alignas(iovec_list_t) mem[sz];
+		iovec_list_t * const iovec_list = (iovec_list_t *) mem;
+
+		memset(iovec_list, 0, offsetof(iovec_list_t, iov));
+		iovec_list->max_iovcnt = iovcnt;
+		fortune_ctx->iovec_list_iter = iovec_list;
+		fortune_ctx->result = sort_fortunes(fortune_ctx->result);
+
+		if (mustache_render(&api, fortune_ctx, ctx->global_data->fortunes_template)) {
+			fortune_ctx->iovec_list = iovec_list->l.next;
+			set_default_response_param(HTML, fortune_ctx->req);
+			h2o_start_response(fortune_ctx->req, &fortune_ctx->generator);
+			h2o_send(fortune_ctx->req,
+			         iovec_list->iov,
+			         iovec_list->iovcnt,
+			         false);
+		}
+		else {
+			cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+			send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, fortune_ctx->req);
+			ret = DONE;
+		}
+	}
+
+	return ret;
+}
+
+static uintmax_t on_fortune_section(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_section_t *token)
+{
+	fortune_ctx_t * const fortune_ctx = userdata;
+	uintmax_t ret = 1;
+
+	if (fortune_ctx->num_result) {
+		assert(fortune_ctx->result);
+
+		const list_t *iter = fortune_ctx->result;
+
+		do {
+			fortune_ctx->fortune_iter = H2O_STRUCT_FROM_MEMBER(fortune_t, l, iter);
+			iter = iter->next;
+
+			if (!mustache_render(api, fortune_ctx, token->section)) {
+				ret = 0;
+				break;
+			}
+		} while (iter);
+	}
+
+	return ret;
+}
+
+static void on_fortune_timeout(db_query_param_t *param)
+{
+	fortune_ctx_t * const fortune_ctx = H2O_STRUCT_FROM_MEMBER(fortune_ctx_t,
+	                                                           param,
+	                                                           param);
+
+	cleanup_fortunes(&fortune_ctx->generator, fortune_ctx->req);
+	send_error(GATEWAY_TIMEOUT, DB_TIMEOUT_ERROR, fortune_ctx->req);
+}
+
+static uintmax_t on_fortune_variable(mustache_api_t *api,
+                                     void *userdata,
+                                     mustache_token_variable_t *token)
+{
+	fortune_ctx_t * const fortune_ctx = userdata;
+	const h2o_iovec_t * const iovec = (const h2o_iovec_t *)
+		((const char *) fortune_ctx->fortune_iter + (size_t) token->userdata);
+
+	return add_iovec(api, fortune_ctx, iovec->base, iovec->len);
+}
+
+// merge sort
+static list_t *sort_fortunes(list_t *head)
+{
+	list_t **new_head;
+
+	do {
+		new_head = &head;
+
+		for (list_t *iter = head; iter;) {
+			list_t * const tail1 = get_sorted_sublist(iter);
+			list_t * const head2 = tail1->next;
+
+			if (!head2) {
+				*new_head = iter;
+				break;
+			}
+
+			list_t * const tail2 = get_sorted_sublist(head2);
+			list_t * const head1 = iter;
+
+			iter = tail2->next;
+			tail1->next = NULL;
+			tail2->next = NULL;
+			*new_head = merge_lists(head1, head2);
+			new_head = tail1->next ? &tail2->next : &tail1->next;
+		}
+	} while (new_head != &head);
+
+	return head;
+}
+
+int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	fortune_ctx_t * const fortune_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*fortune_ctx));
+
+	if (fortune_ctx) {
+		fortune_t * const fortune = h2o_mem_alloc_pool(&req->pool, sizeof(*fortune));
+
+		if (fortune) {
+			memset(fortune, 0, sizeof(*fortune));
+			fortune->id.base = NEW_FORTUNE_ID;
+			fortune->id.len = sizeof(NEW_FORTUNE_ID) - 1;
+			fortune->message.base = NEW_FORTUNE_MESSAGE;
+			fortune->message.len = sizeof(NEW_FORTUNE_MESSAGE) - 1;
+			memset(fortune_ctx, 0, sizeof(*fortune_ctx));
+			fortune_ctx->generator.proceed = complete_fortunes;
+			fortune_ctx->generator.stop = cleanup_fortunes;
+			fortune_ctx->num_result = 1;
+			fortune_ctx->param.command = FORTUNE_TABLE_NAME;
+			fortune_ctx->param.on_error = on_fortune_error;
+			fortune_ctx->param.on_result = on_fortune_result;
+			fortune_ctx->param.on_timeout = on_fortune_timeout;
+			fortune_ctx->param.flags = IS_PREPARED;
+			fortune_ctx->req = req;
+			fortune_ctx->result = &fortune->l;
+
+			if (execute_query(ctx, &fortune_ctx->param))
+				send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+	else
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+
+	return 0;
+}

+ 38 - 0
frameworks/C/h2o/src/fortune.h

@@ -0,0 +1,38 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef FORTUNE_H_
+
+#define FORTUNE_H_
+
+#include <h2o.h>
+#include <postgresql/libpq-fe.h>
+
+#include "list.h"
+
+typedef struct {
+	list_t l;
+	PGresult *data;
+	h2o_iovec_t id;
+	h2o_iovec_t message;
+} fortune_t;
+
+int fortunes(struct st_h2o_handler_t *self, h2o_req_t *req);
+
+#endif // FORTUNE_H_

+ 35 - 0
frameworks/C/h2o/src/list.h

@@ -0,0 +1,35 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef LIST_H_
+
+#define LIST_H_
+
+typedef struct list_t list_t;
+
+struct list_t {
+	list_t *next;
+};
+
+typedef struct {
+	list_t *head;
+	list_t **tail;
+} queue_t;
+
+#endif // LIST_H_

+ 366 - 0
frameworks/C/h2o/src/main.c

@@ -0,0 +1,366 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <errno.h>
+#include <h2o.h>
+#include <mustache.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <h2o/serverutil.h>
+#include <netinet/tcp.h>
+#include <sys/resource.h>
+#include <sys/signalfd.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include "error.h"
+#include "event_loop.h"
+#include "request_handler.h"
+#include "template.h"
+#include "thread.h"
+#include "tls.h"
+#include "utility.h"
+
+#define DEFAULT_TCP_FASTOPEN_QUEUE_LEN 4096
+#define USAGE_MESSAGE \
+	"Usage:\n%s [-a <max connections accepted simultaneously>] [-b <bind address>] " \
+	"[-c <certificate file>] [-d <database connection string>] [-f fortunes template file path] " \
+	"[-k <private key file>] [-l <log path>] " \
+	"[-m <max database connections per thread>] [-p <port>] " \
+	"[-q <max enqueued database queries per thread>] [-r <root directory>] " \
+	"[-t <thread number>]\n"
+
+static void free_global_data(global_data_t *global_data);
+static int get_listener_socket(const config_t *config);
+static size_t get_maximum_cache_line_size(void);
+static int initialize_global_data(const config_t *config, global_data_t *global_data);
+static int parse_options(int argc, char *argv[], config_t *config);
+static void set_default_options(config_t *config);
+static void setup_process(void);
+
+static void free_global_data(global_data_t *global_data)
+{
+	if (global_data->ctx)
+		free_thread_contexts(global_data);
+
+	if (global_data->file_logger)
+		global_data->file_logger->dispose(global_data->file_logger);
+
+	if (global_data->fortunes_template) {
+		mustache_api_t api = {.freedata = NULL};
+
+		mustache_free(&api, global_data->fortunes_template);
+	}
+
+	h2o_config_dispose(&global_data->h2o_config);
+
+	if (global_data->ssl_ctx)
+		cleanup_openssl(global_data);
+}
+
+static int get_listener_socket(const config_t *config)
+{
+	int ret = -1;
+	char port[16];
+
+	if ((size_t) snprintf(port, sizeof(port), "%u", (unsigned) config->port) >= sizeof(port)) {
+		print_error(__FILE__, __LINE__, "snprintf", "Truncated output.");
+		return ret;
+	}
+
+	struct addrinfo *res = NULL;
+	struct addrinfo hints = {.ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE};
+	const int error_code = getaddrinfo(config->bind_address, port, &hints, &res);
+
+	if (error_code) {
+		print_error(__FILE__, __LINE__, "getaddrinfo", gai_strerror(error_code));
+		return ret;
+	}
+
+	struct addrinfo *iter = res;
+
+	for (; iter; iter = iter->ai_next) {
+		const int s = socket(iter->ai_family,
+		                     iter->ai_socktype | SOCK_NONBLOCK | SOCK_CLOEXEC,
+		                     iter->ai_protocol);
+
+		if (s == -1) {
+			LIBRARY_ERROR("socket");
+			continue;
+		}
+
+#define LOCAL_CHECK_ERRNO(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code) { \
+			print_library_error(__FILE__, __LINE__, #function, errno); \
+			goto error; \
+		} \
+	} while(0)
+
+		int option = 1;
+
+		LOCAL_CHECK_ERRNO(setsockopt, s, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_QUICKACK, &option, sizeof(option));
+		option = H2O_DEFAULT_HANDSHAKE_TIMEOUT_IN_SECS;
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_DEFER_ACCEPT, &option, sizeof(option));
+		option = DEFAULT_TCP_FASTOPEN_QUEUE_LEN;
+		LOCAL_CHECK_ERRNO(setsockopt, s, IPPROTO_TCP, TCP_FASTOPEN, &option, sizeof(option));
+		LOCAL_CHECK_ERRNO(bind, s, iter->ai_addr, iter->ai_addrlen);
+		LOCAL_CHECK_ERRNO(listen, s, INT_MAX);
+		ret = s;
+		break;
+
+#undef LOCAL_CHECK_ERRNO
+
+error:
+		close(s);
+	}
+
+	freeaddrinfo(res);
+	return ret;
+}
+
+static size_t get_maximum_cache_line_size(void)
+{
+	const int name[] = {_SC_LEVEL1_DCACHE_LINESIZE,
+	                    _SC_LEVEL2_CACHE_LINESIZE,
+	                    _SC_LEVEL3_CACHE_LINESIZE,
+	                    _SC_LEVEL4_CACHE_LINESIZE};
+	size_t ret = 0;
+
+	for (size_t i = 0; i < ARRAY_SIZE(name); i++) {
+		errno = 0;
+
+		const long rc = sysconf(name[i]);
+
+		if (rc < 0) {
+			if (errno)
+				LIBRARY_ERROR("sysconf");
+		}
+		else if ((size_t) rc > ret)
+			ret = rc;
+	}
+
+	if (!ret)
+		ret = DEFAULT_CACHE_LINE_SIZE;
+
+	return ret;
+}
+
+static int initialize_global_data(const config_t *config, global_data_t *global_data)
+{
+	sigset_t signals;
+
+	memset(global_data, 0, sizeof(*global_data));
+	global_data->config = config;
+	global_data->memory_alignment = get_maximum_cache_line_size();
+	assert(global_data->memory_alignment <= DEFAULT_CACHE_LINE_SIZE);
+	CHECK_ERRNO(sigemptyset, &signals);
+#ifdef NDEBUG
+	CHECK_ERRNO(sigaddset, &signals, SIGINT);
+#endif // NDEBUG
+	CHECK_ERRNO(sigaddset, &signals, SIGTERM);
+	CHECK_ERRNO_RETURN(global_data->signal_fd, signalfd, -1, &signals, SFD_NONBLOCK | SFD_CLOEXEC);
+	global_data->fortunes_template = get_fortunes_template(config->template_path);
+	h2o_config_init(&global_data->h2o_config);
+	global_data->listener_sd = get_listener_socket(config);
+
+	if (global_data->listener_sd == -1)
+		goto error;
+
+	if (config->cert && config->key)
+		initialize_openssl(global_data);
+
+	const h2o_iovec_t host = h2o_iovec_init(H2O_STRLIT("default"));
+	h2o_hostconf_t * const hostconf = h2o_config_register_host(&global_data->h2o_config,
+	                                                           host,
+	                                                           config->port);
+	h2o_access_log_filehandle_t *log_handle = NULL;
+
+	if (config->log) {
+		log_handle = h2o_access_log_open_handle(config->log, NULL);
+
+		if (!log_handle)
+			goto error;
+	}
+
+	register_request_handlers(hostconf, log_handle);
+
+	// Must be registered after the rest of the request handlers.
+	if (config->root) {
+		h2o_pathconf_t * const pathconf = h2o_config_register_path(hostconf, "/", 0);
+		h2o_file_register(pathconf, config->root, NULL, NULL, 0);
+
+		if (log_handle)
+			global_data->file_logger = h2o_access_log_register(pathconf, log_handle);
+	}
+
+	global_data->ctx = initialize_thread_contexts(global_data);
+
+	if (global_data->ctx)
+		return EXIT_SUCCESS;
+
+error:
+	free_global_data(global_data);
+	return EXIT_FAILURE;
+}
+
+static int parse_options(int argc, char *argv[], config_t *config)
+{
+	memset(config, 0, sizeof(*config));
+	opterr = 0;
+
+	while (1) {
+		const int opt = getopt(argc, argv, "?a:b:c:d:f:k:l:m:p:q:r:t:");
+
+		if (opt == -1)
+			break;
+
+		switch (opt) {
+
+#define PARSE_NUMBER(out) \
+	do { \
+		errno = 0; \
+		\
+		const long long n = strtoll(optarg, NULL, 10); \
+		\
+		if (errno) { \
+			print_library_error(__FILE__, __LINE__, "strtoll", errno); \
+			return EXIT_FAILURE; \
+		} \
+		\
+		(out) = n; \
+	} while(0)
+
+			case 'a':
+				PARSE_NUMBER(config->max_accept);
+				break;
+			case 'b':
+				config->bind_address = optarg;
+				break;
+			case 'c':
+				config->cert = optarg;
+				break;
+			case 'd':
+				config->db_host = optarg;
+				break;
+			case 'f':
+				config->template_path = optarg;
+				break;
+			case 'k':
+				config->key = optarg;
+				break;
+			case 'l':
+				config->log = optarg;
+				break;
+			case 'm':
+				PARSE_NUMBER(config->max_db_conn_num);
+				break;
+			case 'p':
+				PARSE_NUMBER(config->port);
+				break;
+			case 'q':
+				PARSE_NUMBER(config->max_query_num);
+				break;
+			case 'r':
+				config->root = optarg;
+				break;
+			case 't':
+				PARSE_NUMBER(config->thread_num);
+				break;
+			default:
+				fprintf(stderr, USAGE_MESSAGE, *argv);
+				return EXIT_FAILURE;
+
+#undef PARSE_NUMBER
+		}
+	}
+
+	set_default_options(config);
+	return EXIT_SUCCESS;
+}
+
+static void set_default_options(config_t *config)
+{
+	if (!config->max_accept)
+		config->max_accept = 10;
+
+	if (!config->max_db_conn_num)
+		config->max_db_conn_num = 10;
+
+	if (!config->max_query_num)
+		config->max_query_num = 10000;
+
+	if (!config->port)
+		config->port = 8080;
+
+	if (!config->thread_num)
+		config->thread_num = h2o_numproc();
+}
+
+static void setup_process(void)
+{
+	sigset_t signals;
+
+	CHECK_ERRNO(sigfillset, &signals);
+	CHECK_ERRNO(sigdelset, &signals, SIGBUS);
+	CHECK_ERRNO(sigdelset, &signals, SIGFPE);
+	CHECK_ERRNO(sigdelset, &signals, SIGILL);
+	CHECK_ERRNO(sigdelset, &signals, SIGSEGV);
+#ifndef NDEBUG
+	CHECK_ERRNO(sigdelset, &signals, SIGINT);
+#endif // NDEBUG
+	CHECK_ERROR(pthread_sigmask, SIG_BLOCK, &signals, NULL);
+
+	struct rlimit rlim = {.rlim_cur = 0};
+
+	CHECK_ERRNO(getrlimit, RLIMIT_NOFILE, &rlim);
+	rlim.rlim_cur = rlim.rlim_max;
+	CHECK_ERRNO(setrlimit, RLIMIT_NOFILE, &rlim);
+}
+
+int main(int argc, char *argv[])
+{
+	config_t config;
+	int rc = EXIT_FAILURE;
+
+	if (parse_options(argc, argv, &config) == EXIT_SUCCESS) {
+		global_data_t global_data;
+
+		if (initialize_global_data(&config, &global_data) == EXIT_SUCCESS) {
+			setup_process();
+			start_threads(global_data.ctx);
+			connect_to_database(global_data.ctx);
+			event_loop(global_data.ctx);
+			free_global_data(&global_data);
+			rc = EXIT_SUCCESS;
+		}
+	}
+
+	return rc;
+}

+ 255 - 0
frameworks/C/h2o/src/request_handler.c

@@ -0,0 +1,255 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stdalign.h>
+#include <string.h>
+#include <yajl/yajl_gen.h>
+
+#include "error.h"
+#include "fortune.h"
+#include "request_handler.h"
+#include "utility.h"
+#include "world.h"
+
+typedef struct {
+	yajl_gen gen;
+	h2o_generator_t h2o_generator;
+} json_ctx_t;
+
+static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req);
+static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req);
+static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req);
+static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req);
+static const char *status_code_to_string(http_status_code_t status_code);
+
+static void cleanup_json_response(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	json_ctx_t * const json_ctx = H2O_STRUCT_FROM_MEMBER(json_ctx_t, h2o_generator, self);
+
+	yajl_gen_free(json_ctx->gen);
+}
+
+static void complete_json_response(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	h2o_send(req, NULL, 0, true);
+	cleanup_json_response(self, req);
+}
+
+static int json_serializer(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	json_ctx_t * const json_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*json_ctx));
+
+	if (json_ctx) {
+		memset(json_ctx, 0, sizeof(*json_ctx));
+		json_ctx->gen = get_json_generator(&req->pool);
+		json_ctx->h2o_generator.proceed = complete_json_response;
+		json_ctx->h2o_generator.stop = cleanup_json_response;
+
+		if (json_ctx->gen) {
+			CHECK_YAJL_STATUS(yajl_gen_map_open, json_ctx->gen);
+			CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("message"));
+			CHECK_YAJL_STATUS(yajl_gen_string, json_ctx->gen, YAJL_STRLIT("Hello, World!"));
+			CHECK_YAJL_STATUS(yajl_gen_map_close, json_ctx->gen);
+
+			if (!send_json_response(json_ctx->gen, &json_ctx->h2o_generator, req))
+				return 0;
+
+error_yajl:
+			yajl_gen_free(json_ctx->gen);
+		}
+	}
+
+	send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	return 0;
+}
+
+static int plaintext(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+	set_default_response_param(PLAIN, req);
+	h2o_send_inline(req, H2O_STRLIT("Hello, World!"));
+	return 0;
+}
+
+static const char *status_code_to_string(http_status_code_t status_code)
+{
+	const char *ret;
+
+	switch (status_code) {
+		case BAD_GATEWAY:
+			ret = "Bad Gateway";
+			break;
+		case GATEWAY_TIMEOUT:
+			ret = "Gateway Timeout";
+			break;
+		case INTERNAL_SERVER_ERROR:
+			ret = "Internal Server Error";
+			break;
+		case OK:
+			ret = "OK";
+			break;
+		case SERVICE_UNAVAILABLE:
+			ret = "Service Unavailable";
+			break;
+		default:
+			ret = "";
+	}
+
+	return ret;
+}
+
+const char *get_query_param(const char *query,
+                            size_t query_len,
+                            const char *param,
+                            size_t param_len)
+{
+	const char *ret = NULL;
+
+	while (param_len < query_len) {
+		if (!memcmp(param, query, param_len)) {
+			ret = query + param_len;
+			break;
+		}
+
+		const char * const next = memchr(query, '&', query_len);
+
+		if (!next)
+			break;
+
+		query_len -= next + 1 - query;
+		query = next + 1;
+	}
+
+	return ret;
+}
+
+void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle)
+{
+	h2o_pathconf_t *pathconf = h2o_config_register_path(hostconf, "/json", 0);
+	h2o_handler_t *handler = h2o_create_handler(pathconf, sizeof(*handler));
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	handler->on_req = json_serializer;
+	pathconf = h2o_config_register_path(hostconf, "/db", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = single_query;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/queries", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = multiple_queries;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/fortunes", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = fortunes;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/updates", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = updates;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+
+	pathconf = h2o_config_register_path(hostconf, "/plaintext", 0);
+	handler = h2o_create_handler(pathconf, sizeof(*handler));
+	handler->on_req = plaintext;
+
+	if (log_handle)
+		h2o_access_log_register(pathconf, log_handle);
+}
+
+void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req)
+{
+	h2o_send_error_generic(req, status_code, status_code_to_string(status_code), body, 0);
+}
+
+int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req)
+{
+	h2o_iovec_t h2o_iovec = {.len = 0};
+	int ret = EXIT_FAILURE;
+
+	static_assert(sizeof(h2o_iovec.base) == sizeof(const unsigned char *) &&
+	              alignof(h2o_iovec.base) == alignof(const unsigned char *),
+	              "Types must be compatible.");
+
+	if (yajl_gen_get_buf(gen,
+	                     (const unsigned char **) &h2o_iovec.base,
+	                     &h2o_iovec.len) == yajl_gen_status_ok) {
+		set_default_response_param(JSON, req);
+		h2o_start_response(req, h2o_generator);
+		h2o_send(req, &h2o_iovec, 1, false);
+		ret = EXIT_SUCCESS;
+	}
+
+	return ret;
+}
+
+void send_service_unavailable_error(const char *body, h2o_req_t *req)
+{
+	h2o_add_header(&req->pool,
+	               &req->res.headers,
+	               H2O_TOKEN_RETRY_AFTER,
+	               H2O_STRLIT(MKSTR(H2O_DEFAULT_HTTP1_REQ_TIMEOUT_IN_SECS)));
+	h2o_send_error_503(req,
+	                   status_code_to_string(SERVICE_UNAVAILABLE),
+	                   body,
+	                   H2O_SEND_ERROR_KEEP_HEADERS);
+}
+
+void set_default_response_param(content_type_t content_type, h2o_req_t *req)
+{
+	req->res.status = OK;
+	req->res.reason = status_code_to_string(req->res.status);
+
+	switch (content_type) {
+		case JSON:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("application/json"));
+			break;
+		case PLAIN:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("text/plain"));
+			break;
+		default:
+			h2o_add_header(&req->pool,
+			               &req->res.headers,
+			               H2O_TOKEN_CONTENT_TYPE,
+			               H2O_STRLIT("text/html; charset=utf-8"));
+	}
+}

+ 51 - 0
frameworks/C/h2o/src/request_handler.h

@@ -0,0 +1,51 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef REQUEST_H_
+
+#define REQUEST_H_
+
+#include <h2o.h>
+#include <yajl/yajl_gen.h>
+
+typedef enum {
+	HTML,
+	JSON,
+	PLAIN
+} content_type_t;
+
+typedef enum {
+	OK = 200,
+	INTERNAL_SERVER_ERROR = 500,
+	BAD_GATEWAY = 502,
+	SERVICE_UNAVAILABLE = 503,
+	GATEWAY_TIMEOUT = 504
+} http_status_code_t;
+
+const char *get_query_param(const char *query,
+                            size_t query_len,
+                            const char *param,
+                            size_t param_len);
+void register_request_handlers(h2o_hostconf_t *hostconf, h2o_access_log_filehandle_t *log_handle);
+void send_error(http_status_code_t status_code, const char *body, h2o_req_t *req);
+int send_json_response(yajl_gen gen, h2o_generator_t *h2o_generator, h2o_req_t *req);
+void send_service_unavailable_error(const char *body, h2o_req_t *req);
+void set_default_response_param(content_type_t content_type, h2o_req_t *req);
+
+#endif // REQUEST_H_

+ 138 - 0
frameworks/C/h2o/src/template.c

@@ -0,0 +1,138 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <errno.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <mustache.h>
+
+#include "database.h"
+#include "error.h"
+#include "fortune.h"
+#include "template.h"
+#include "utility.h"
+
+typedef struct {
+	FILE *input;
+	const char *name;
+} template_input_t;
+
+static uintmax_t prerender_section(mustache_api_t *api,
+                                   void *userdata,
+                                   mustache_token_section_t *token);
+static uintmax_t prerender_variable(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_variable_t *token);
+static uintmax_t read_template(mustache_api_t *api,
+                               void *userdata,
+                               char *buffer,
+                               uintmax_t buffer_size);
+static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error);
+
+static uintmax_t prerender_section(mustache_api_t *api,
+                                   void *userdata,
+                                   mustache_token_section_t *token)
+{
+	bool * const in_section = userdata;
+	uintmax_t ret = 0;
+
+	if (!*in_section && !strcmp(token->name, FORTUNE_TABLE_NAME)) {
+		*in_section = true;
+		ret = mustache_prerender(api, userdata, token->section);
+		*in_section = false;
+	}
+
+	return ret;
+}
+
+static uintmax_t prerender_variable(mustache_api_t *api,
+                                    void *userdata,
+                                    mustache_token_variable_t *token)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	bool * const in_section = userdata;
+	uintmax_t ret = 0;
+
+	if (*in_section) {
+		if (token->text_length == sizeof(ID_FIELD_NAME) - 1 &&
+		    !memcmp(token->text, ID_FIELD_NAME, sizeof(ID_FIELD_NAME) - 1)) {
+			token->userdata = (void *) offsetof(fortune_t, id);
+			ret = 1;
+		}
+		else if (token->text_length == sizeof(MESSAGE_FIELD_NAME) - 1 &&
+		         !memcmp(token->text, MESSAGE_FIELD_NAME, sizeof(MESSAGE_FIELD_NAME) - 1)) {
+			token->userdata = (void *) offsetof(fortune_t, message);
+			ret = 1;
+		}
+	}
+
+	return ret;
+}
+
+static uintmax_t read_template(mustache_api_t *api,
+                               void *userdata,
+                               char *buffer,
+                               uintmax_t buffer_size)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	const template_input_t * const template_input = userdata;
+
+	return fread(buffer, sizeof(*buffer), buffer_size, template_input->input);
+}
+
+static void template_error(mustache_api_t *api, void *userdata, uintmax_t lineno, char *error)
+{
+	IGNORE_FUNCTION_PARAMETER(api);
+
+	const template_input_t * const template_input = userdata;
+
+	print_error(template_input->name, lineno, "mustache_compile", error);
+}
+
+mustache_template_t *get_fortunes_template(const char *path)
+{
+	mustache_template_t *ret = NULL;
+	template_input_t template_input = {.input = fopen(path, "rb"), .name = path};
+
+	if (template_input.input) {
+		mustache_api_t api = {.error = template_error,
+		                      .read = read_template,
+		                      .sectget = prerender_section,
+		                      .varget = prerender_variable};
+		bool in_section = false;
+
+		ret = mustache_compile(&api, &template_input);
+
+		if (ret && !mustache_prerender(&api, &in_section, ret)) {
+			mustache_free(&api, ret);
+			ret = NULL;
+		}
+
+		fclose(template_input.input);
+	}
+	else
+		LIBRARY_ERROR("fopen");
+
+	return ret;
+}

+ 28 - 0
frameworks/C/h2o/src/template.h

@@ -0,0 +1,28 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef TEMPLATE_H_
+
+#define TEMPLATE_H_
+
+#include <mustache.h>
+
+mustache_template_t *get_fortunes_template(const char *path);
+
+#endif // TEMPLATE_H_

+ 107 - 0
frameworks/C/h2o/src/thread.c

@@ -0,0 +1,107 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <h2o.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <h2o/serverutil.h>
+#include <sys/epoll.h>
+
+#include "database.h"
+#include "error.h"
+#include "event_loop.h"
+#include "thread.h"
+
+static void *run_thread(void *arg);
+
+static void *run_thread(void *arg)
+{
+	connect_to_database(arg);
+	event_loop(arg);
+	pthread_exit(NULL);
+}
+
+void free_thread_contexts(global_data_t *global_data)
+{
+	thread_context_t * const ctx = global_data->ctx;
+
+	for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) {
+		if (i)
+			CHECK_ERROR(pthread_join, ctx[i].thread, NULL);
+		else
+			// Even though this is global data, we need to close
+			// it before the associated event loop is cleaned up.
+			h2o_socket_close(global_data->signals);
+
+		free_database_state(ctx[i].event_loop.h2o_ctx.loop, &ctx[i].db_state);
+		free_event_loop(&ctx[i].event_loop);
+	}
+
+	free(ctx);
+}
+
+thread_context_t *initialize_thread_contexts(global_data_t *global_data)
+{
+	const size_t sz = global_data->config->thread_num * sizeof(thread_context_t);
+	thread_context_t * const ret = aligned_alloc(global_data->memory_alignment, sz);
+
+	if (ret) {
+		memset(ret, 0, sz);
+
+		for (size_t i = 0; i < global_data->config->thread_num; i++) {
+			ret[i].global_data = global_data;
+			initialize_event_loop(!i, global_data, &ret[i].event_loop);
+			initialize_database_state(ret[i].event_loop.h2o_ctx.loop, &ret[i].db_state);
+		}
+	}
+
+	return ret;
+}
+
+void start_threads(thread_context_t *ctx)
+{
+	const size_t num_cpus = h2o_numproc();
+
+	// The first thread context is used by the main thread.
+	ctx->thread = pthread_self();
+
+	for (size_t i = 1; i < ctx->global_data->config->thread_num; i++)
+		CHECK_ERROR(pthread_create, &ctx[i].thread, NULL, run_thread, ctx + i);
+
+	// If the number of threads is not equal to the number of processors, then let the scheduler
+	// decide how to balance the load.
+	if (ctx->global_data->config->thread_num == num_cpus) {
+		const size_t cpusetsize = CPU_ALLOC_SIZE(num_cpus);
+		cpu_set_t * const cpuset = CPU_ALLOC(num_cpus);
+
+		if (!cpuset)
+			abort();
+
+		for (size_t i = 0; i < ctx->global_data->config->thread_num; i++) {
+			CPU_ZERO_S(cpusetsize, cpuset);
+			CPU_SET_S(i, cpusetsize, cpuset);
+			CHECK_ERROR(pthread_setaffinity_np, ctx[i].thread, cpusetsize, cpuset);
+		}
+
+		CPU_FREE(cpuset);
+	}
+}

+ 56 - 0
frameworks/C/h2o/src/thread.h

@@ -0,0 +1,56 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef THREAD_H_
+
+#define THREAD_H_
+
+#include <assert.h>
+#include <h2o.h>
+#include <pthread.h>
+#include <sys/types.h>
+
+#include "database.h"
+#include "event_loop.h"
+#include "utility.h"
+
+#define DEFAULT_CACHE_LINE_SIZE 64
+
+typedef struct thread_context_t thread_context_t;
+
+struct thread_context_t {
+	global_data_t *global_data;
+	unsigned random_seed;
+	pid_t tid;
+	db_state_t db_state;
+	event_loop_t event_loop;
+	pthread_t thread;
+	// Align on the cache line size to prevent false sharing.
+	char padding[49];
+};
+
+static_assert(!(sizeof(thread_context_t) % DEFAULT_CACHE_LINE_SIZE),
+              "The size of the thread_context_t structure must be a "
+              "multiple of the cache line size.");
+
+void free_thread_contexts(global_data_t *global_data);
+thread_context_t *initialize_thread_contexts(global_data_t *global_data);
+void start_threads(thread_context_t *ctx);
+
+#endif // THREAD_H_

+ 169 - 0
frameworks/C/h2o/src/tls.c

@@ -0,0 +1,169 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <openssl/conf.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/ssl.h>
+
+#include "error.h"
+#include "tls.h"
+#include "utility.h"
+
+#define CHECK_OPENSSL_ERROR(function, ...) \
+	do { \
+		const int error_code = (function)(__VA_ARGS__); \
+		\
+		if (error_code != 1) { \
+			const unsigned long openssl_error = ERR_get_error(); \
+			char buf[128] = ""; \
+			\
+			ERR_error_string_n(openssl_error, buf, sizeof(buf)); \
+			print_error(__FILE__, __LINE__, #function, "%s (%lu)", buf, openssl_error); \
+			abort(); \
+		} \
+	} while(0)
+
+struct CRYPTO_dynlock_value {
+	pthread_mutex_t mutex;
+};
+
+static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line);
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line);
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line);
+static void locking_function(int mode, int n, const char *file, int line);
+
+static struct {
+	pthread_mutex_t *lock;
+	size_t num_lock;
+	pthread_mutexattr_t lock_attr;
+} openssl_global_data;
+
+static struct CRYPTO_dynlock_value *dyn_create_function(const char *file, int line)
+{
+	struct CRYPTO_dynlock_value *ret = malloc(sizeof(*ret));
+
+	if (ret) {
+		const int error_code = pthread_mutex_init(&ret->mutex, &openssl_global_data.lock_attr);
+
+		if (error_code) {
+			print_library_error(file, line, "pthread_mutex_init", error_code);
+			free(ret);
+			ret = NULL;
+		}
+	}
+
+	return ret;
+}
+
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+	const int error_code = pthread_mutex_destroy(&l->mutex);
+
+	if (error_code)
+		print_library_error(file, line, "pthread_mutex_destroy", error_code);
+
+	free(l);
+}
+
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line)
+{
+	const char *function;
+	int error_code;
+
+	if (mode & CRYPTO_LOCK) {
+		function = "pthread_mutex_lock";
+		error_code = pthread_mutex_lock(&l->mutex);
+	}
+	else {
+		function = "pthread_mutex_unlock";
+		error_code = pthread_mutex_unlock(&l->mutex);
+	}
+
+	if (error_code) {
+		print_library_error(file, line, function, error_code);
+		abort();
+	}
+}
+
+static void locking_function(int mode, int n, const char *file, int line)
+{
+	assert((size_t) n < openssl_global_data.num_lock);
+	static_assert(!offsetof(struct CRYPTO_dynlock_value, mutex),
+	              "The mutex must be the first field in struct CRYPTO_dynlock_value.");
+
+	dyn_lock_function(mode, (struct CRYPTO_dynlock_value *) (openssl_global_data.lock + n), file, line);
+}
+
+void cleanup_openssl(global_data_t *global_data)
+{
+	SSL_CTX_free(global_data->ssl_ctx);
+	CRYPTO_set_locking_callback(NULL);
+	CRYPTO_set_id_callback(NULL);
+	CRYPTO_set_dynlock_create_callback(NULL);
+	CRYPTO_set_dynlock_destroy_callback(NULL);
+	CRYPTO_set_dynlock_lock_callback(NULL);
+	ERR_remove_state(0);
+	ERR_free_strings();
+	CONF_modules_unload(1);
+	EVP_cleanup();
+	CRYPTO_cleanup_all_ex_data();
+
+	for (size_t i = 0; i < openssl_global_data.num_lock; i++)
+		CHECK_ERROR(pthread_mutex_destroy, openssl_global_data.lock + i);
+
+	free(openssl_global_data.lock);
+	CHECK_ERROR(pthread_mutexattr_destroy, &openssl_global_data.lock_attr);
+}
+
+void initialize_openssl(global_data_t *global_data)
+{
+	SSL_library_init();
+	SSL_load_error_strings();
+	openssl_global_data.num_lock = CRYPTO_num_locks();
+	openssl_global_data.lock = malloc(openssl_global_data.num_lock *
+	                                  sizeof(*openssl_global_data.lock));
+	CHECK_ERROR(pthread_mutexattr_init, &openssl_global_data.lock_attr);
+	CHECK_ERROR(pthread_mutexattr_settype,
+	            &openssl_global_data.lock_attr,
+	            PTHREAD_MUTEX_ADAPTIVE_NP);
+
+	for (size_t i = 0; i < openssl_global_data.num_lock; i++)
+		CHECK_ERROR(pthread_mutex_init,
+		            openssl_global_data.lock + i,
+		            &openssl_global_data.lock_attr);
+
+	CRYPTO_set_locking_callback(locking_function);
+	CRYPTO_set_dynlock_create_callback(dyn_create_function);
+	CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+	CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+	global_data->ssl_ctx = SSL_CTX_new(TLSv1_2_server_method());
+	CHECK_OPENSSL_ERROR(SSL_CTX_use_certificate_file,
+	                    global_data->ssl_ctx,
+	                    global_data->config->cert,
+	                    SSL_FILETYPE_PEM);
+	CHECK_OPENSSL_ERROR(SSL_CTX_use_PrivateKey_file,
+	                    global_data->ssl_ctx,
+	                    global_data->config->key,
+	                    SSL_FILETYPE_PEM);
+}

+ 29 - 0
frameworks/C/h2o/src/tls.h

@@ -0,0 +1,29 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef TLS_H_
+
+#define TLS_H_
+
+#include "utility.h"
+
+void cleanup_openssl(global_data_t *global_data);
+void initialize_openssl(global_data_t *global_data);
+
+#endif // TLS_H_

+ 95 - 0
frameworks/C/h2o/src/utility.c

@@ -0,0 +1,95 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stdalign.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <yajl/yajl_gen.h>
+
+#include "utility.h"
+
+static void mem_pool_free(void *ctx, void *ptr);
+static void *mem_pool_malloc(void *ctx, size_t sz);
+static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz);
+
+static void mem_pool_free(void *ctx, void *ptr)
+{
+	// The memory pool will free all allocations in one go.
+	IGNORE_FUNCTION_PARAMETER(ctx);
+	IGNORE_FUNCTION_PARAMETER(ptr);
+}
+
+static void *mem_pool_malloc(void *ctx, size_t sz)
+{
+	size_t * const p = h2o_mem_alloc_pool(ctx, sz + sizeof(*p));
+	void * const ret = p + 1;
+
+	*p = sz;
+	// check alignment
+	assert(!(((uintptr_t) ret) & (alignof(void *) - 1)));
+	return ret;
+}
+
+static void *mem_pool_realloc(void *ctx, void *ptr, size_t sz)
+{
+	void *ret;
+
+	if (ptr) {
+		const size_t old_sz = ((const size_t *) ptr)[-1];
+
+		if (sz > old_sz) {
+			ret = mem_pool_malloc(ctx, sz);
+			memcpy(ret, ptr, old_sz);
+		}
+		else
+			ret = ptr;
+	}
+	else
+		ret = mem_pool_malloc(ctx, sz);
+
+	return ret;
+}
+
+yajl_gen get_json_generator(h2o_mem_pool_t *pool)
+{
+	const yajl_alloc_funcs mem_pool_alloc_funcs = {mem_pool_malloc,
+	                                               mem_pool_realloc,
+	                                               mem_pool_free,
+	                                               pool};
+
+	return yajl_gen_alloc(&mem_pool_alloc_funcs);
+}
+
+uint32_t get_random_number(uint32_t max_rand, unsigned int *seed)
+{
+	// In general, RAND_MAX + 1 is not a multiple of max_rand,
+	// so rand_r() % max_rand would be biased.
+	const unsigned bucket_size = (RAND_MAX + 1U) / max_rand;
+	const unsigned unbiased_rand_max = bucket_size * max_rand;
+	unsigned ret;
+
+	do
+		ret = rand_r(seed);
+	while (ret >= unbiased_rand_max);
+
+	return ret / bucket_size;
+}

+ 73 - 0
frameworks/C/h2o/src/utility.h

@@ -0,0 +1,73 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef UTILITY_H_
+
+#define UTILITY_H_
+
+#include <h2o.h>
+#include <stdint.h>
+#include <openssl/ssl.h>
+#include <stdbool.h>
+#include <yajl/yajl_gen.h>
+#include <mustache.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+// mainly used to silence compiler warnings about unused function parameters
+#define IGNORE_FUNCTION_PARAMETER(p) ((void) (p))
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MKSTR(x) TOSTRING(x)
+#define TOSTRING(x) # x
+#define YAJL_STRLIT(s) (const unsigned char *) (s), sizeof(s) - 1
+
+typedef struct thread_context_t thread_context_t;
+
+typedef struct {
+	const char *bind_address;
+	const char *cert;
+	const char *db_host;
+	const char *key;
+	const char *log;
+	const char *root;
+	const char *template_path;
+	size_t max_accept;
+	size_t max_db_conn_num;
+	size_t max_query_num;
+	size_t thread_num;
+	uint16_t port;
+} config_t;
+
+typedef struct {
+	const config_t *config;
+	thread_context_t *ctx;
+	h2o_logger_t *file_logger;
+	mustache_template_t *fortunes_template;
+	h2o_socket_t *signals;
+	SSL_CTX *ssl_ctx;
+	size_t memory_alignment;
+	int listener_sd;
+	int signal_fd;
+	bool shutdown;
+	h2o_globalconf_t h2o_config;
+} global_data_t;
+
+yajl_gen get_json_generator(h2o_mem_pool_t *pool);
+uint32_t get_random_number(uint32_t max_rand, unsigned int *seed);
+
+#endif // UTILITY_H_

+ 497 - 0
frameworks/C/h2o/src/world.c

@@ -0,0 +1,497 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include <assert.h>
+#include <h2o.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <arpa/inet.h>
+#include <postgresql/libpq-fe.h>
+#include <yajl/yajl_gen.h>
+
+#include "bitset.h"
+#include "database.h"
+#include "error.h"
+#include "request_handler.h"
+#include "thread.h"
+#include "utility.h"
+#include "world.h"
+
+#define ID_KEY "id"
+#define MAX_QUERIES 500
+#define QUERIES_PARAMETER "queries="
+#define RANDOM_NUM_KEY "randomNumber"
+
+typedef enum {
+	NO_UPDATE = 0,
+	CREATE,
+	COPY_1,
+	COPY_2,
+	UPDATE
+} update_state_t;
+
+typedef struct {
+	uint32_t id;
+	uint32_t random_number;
+} query_result_t;
+
+typedef struct {
+	db_query_param_t param;
+	yajl_gen gen;
+	h2o_generator_t h2o_generator;
+	const char *id_pointer;
+	h2o_req_t *req;
+	uint32_t id;
+	int id_format;
+	int id_len;
+} single_query_ctx_t;
+
+typedef struct {
+	single_query_ctx_t single;
+	size_t num_query;
+	size_t num_result;
+	update_state_t update_state;
+	query_result_t res[];
+} multiple_query_ctx_t;
+
+static void complete_request(struct st_h2o_generator_t *self, h2o_req_t *req);
+static void cleanup_request(struct st_h2o_generator_t *self, h2o_req_t *req);
+static int do_multiple_queries(update_state_t update_state, h2o_req_t *req);
+static int initialize_single_query_context(h2o_req_t *req,
+                                           on_result_t on_result,
+                                           single_query_ctx_t *query_ctx);
+static void on_database_error(db_query_param_t *param, const char *error_string);
+static void on_database_timeout(db_query_param_t *param);
+static result_return_t on_multiple_query_result(db_query_param_t *param, PGresult *result);
+static result_return_t on_single_query_result(db_query_param_t *param, PGresult *result);
+static result_return_t on_update_result(db_query_param_t *param, PGresult *result);
+static int on_update_write_ready(db_query_param_t *param, PGconn *db_conn);
+static int serialize_item(uint32_t id, uint32_t random_number, yajl_gen gen);
+static void serialize_items(const query_result_t *res,
+                            size_t num_result,
+                            h2o_generator_t *h2o_generator,
+                            yajl_gen gen,
+                            h2o_req_t *req);
+
+static void cleanup_request(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(req);
+
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              h2o_generator,
+	                                                              self);
+
+	yajl_gen_free(query_ctx->gen);
+}
+
+static void complete_request(struct st_h2o_generator_t *self, h2o_req_t *req)
+{
+	h2o_send(req, NULL, 0, true);
+	cleanup_request(self, req);
+}
+
+static int do_multiple_queries(update_state_t update_state, h2o_req_t *req)
+{
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	int num_query = 0;
+
+	if (req->query_at < SIZE_MAX) {
+		const char * const n = get_query_param(req->path.base + req->query_at + 1,
+		                                       req->path.len - req->query_at - 1,
+		                                       QUERIES_PARAMETER,
+		                                       sizeof(QUERIES_PARAMETER) - 1);
+
+		if (n)
+			num_query = atoi(n);
+	}
+
+	if (num_query < 1)
+		num_query = 1;
+	else if (num_query > MAX_QUERIES)
+		num_query = MAX_QUERIES;
+
+	const size_t sz = offsetof(multiple_query_ctx_t, res) + num_query * sizeof(query_result_t);
+	multiple_query_ctx_t * const query_ctx = h2o_mem_alloc_pool(&req->pool, sz);
+
+	if (!query_ctx || initialize_single_query_context(req,
+	                                                  on_multiple_query_result,
+	                                                  &query_ctx->single))
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	else {
+		// MAX_ID is a relatively small number, so allocate on the stack.
+		DEFINE_BITSET(bitset, MAX_ID);
+
+		memset(&query_ctx->single + 1,
+		       0,
+		       offsetof(multiple_query_ctx_t, res) - sizeof(query_ctx->single));
+		query_ctx->num_query = num_query;
+		query_ctx->update_state = update_state;
+
+		size_t max_rand = MAX_ID - query_ctx->num_query + 1;
+
+		for (size_t i = 0; i < query_ctx->num_query; i++) {
+			query_ctx->res[i].id = get_random_number(max_rand, &ctx->random_seed);
+
+			if (BITSET_ISSET(query_ctx->res[i].id, bitset))
+				query_ctx->res[i].id = max_rand - 1;
+
+			BITSET_SET(query_ctx->res[i].id++, bitset);
+			max_rand++;
+		}
+
+		query_ctx->single.id = htonl(query_ctx->res->id);
+
+		if (execute_query(ctx, &query_ctx->single.param)) {
+			yajl_gen_free(query_ctx->single.gen);
+			send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+
+	return 0;
+}
+
+static int initialize_single_query_context(h2o_req_t *req,
+                                           on_result_t on_result,
+                                           single_query_ctx_t *query_ctx)
+{
+	int ret = EXIT_FAILURE;
+
+	memset(query_ctx, 0, sizeof(*query_ctx));
+	query_ctx->gen = get_json_generator(&req->pool);
+
+	if (query_ctx->gen) {
+		query_ctx->h2o_generator.proceed = complete_request;
+		query_ctx->h2o_generator.stop = cleanup_request;
+		query_ctx->id_format = 1;
+		query_ctx->id_len = sizeof(query_ctx->id);
+		query_ctx->id_pointer = (const char *) &query_ctx->id;
+		query_ctx->param.command = WORLD_TABLE_NAME;
+		query_ctx->param.nParams = 1;
+		query_ctx->param.on_error = on_database_error;
+		query_ctx->param.on_result = on_result;
+		query_ctx->param.on_timeout = on_database_timeout;
+		query_ctx->param.paramFormats = &query_ctx->id_format;
+		query_ctx->param.paramLengths = &query_ctx->id_len;
+		query_ctx->param.paramValues = &query_ctx->id_pointer;
+		query_ctx->param.flags = IS_PREPARED;
+		query_ctx->param.resultFormat = 1;
+		query_ctx->req = req;
+		ret = EXIT_SUCCESS;
+	}
+
+	return ret;
+}
+
+static void on_database_error(db_query_param_t *param, const char *error_string)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	yajl_gen_free(query_ctx->gen);
+	send_error(BAD_GATEWAY, error_string, query_ctx->req);
+}
+
+static void on_database_timeout(db_query_param_t *param)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	yajl_gen_free(query_ctx->gen);
+	send_error(GATEWAY_TIMEOUT, DB_TIMEOUT_ERROR, query_ctx->req);
+}
+
+static result_return_t on_multiple_query_result(db_query_param_t *param, PGresult *result)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+
+	if (PQresultStatus(result) == PGRES_TUPLES_OK) {
+		thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+		                                                      event_loop.h2o_ctx,
+		                                                      query_ctx->single.req->conn->ctx);
+		uint32_t * const random_number = &query_ctx->res[query_ctx->num_result++].random_number;
+
+		assert(PQnfields(result) == 2);
+		assert(PQntuples(result) == 1);
+		assert(PQgetlength(result, 0, 1) == sizeof(*random_number));
+		// Use memcpy() in case the result is not aligned.
+		memcpy(random_number, PQgetvalue(result, 0, 1), sizeof(*random_number));
+		*random_number = ntohl(*random_number);
+		PQclear(result);
+
+		if (query_ctx->num_result < query_ctx->num_query) {
+			query_ctx->single.id = htonl(query_ctx->res[query_ctx->num_result].id);
+
+			if (!execute_query(ctx, &query_ctx->single.param))
+				return DONE;
+
+			send_service_unavailable_error(DB_REQ_ERROR, query_ctx->single.req);
+		}
+		else if (query_ctx->update_state == NO_UPDATE) {
+			serialize_items(query_ctx->res,
+			                query_ctx->num_result,
+			                &query_ctx->single.h2o_generator,
+			                query_ctx->single.gen,
+			                query_ctx->single.req);
+			return DONE;
+		}
+		else {
+			query_ctx->single.param.command = UPDATE_QUERY;
+			query_ctx->single.param.nParams = 0;
+			query_ctx->single.param.on_result = on_update_result;
+			query_ctx->single.param.on_write_ready = on_update_write_ready;
+			query_ctx->single.param.paramFormats = NULL;
+			query_ctx->single.param.paramLengths = NULL;
+			query_ctx->single.param.paramValues = NULL;
+			query_ctx->single.param.flags = 0;
+
+			if (!execute_query(ctx, &query_ctx->single.param))
+				return DONE;
+
+			send_service_unavailable_error(DB_REQ_ERROR, query_ctx->single.req);
+		}
+	}
+	else {
+		send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->single.req);
+		PQclear(result);
+	}
+
+	yajl_gen_free(query_ctx->single.gen);
+	return DONE;
+}
+
+static result_return_t on_single_query_result(db_query_param_t *param, PGresult *result)
+{
+	single_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(single_query_ctx_t,
+	                                                              param,
+	                                                              param);
+
+	if (PQresultStatus(result) == PGRES_TUPLES_OK) {
+		uint32_t random_number;
+
+		assert(PQnfields(result) == 2);
+		assert(PQntuples(result) == 1);
+		assert(PQgetlength(result, 0, 1) == sizeof(random_number));
+
+		// Use memcpy() in case the result is not aligned.
+		memcpy(&random_number, PQgetvalue(result, 0, 1), sizeof(random_number));
+		random_number = ntohl(random_number);
+		PQclear(result);
+
+		if (!serialize_item(ntohl(query_ctx->id), random_number, query_ctx->gen) &&
+		    !send_json_response(query_ctx->gen, &query_ctx->h2o_generator, query_ctx->req))
+			return DONE;
+
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, query_ctx->req);
+	}
+	else {
+		send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->req);
+		PQclear(result);
+	}
+
+	yajl_gen_free(query_ctx->gen);
+	return DONE;
+}
+
+static result_return_t on_update_result(db_query_param_t *param, PGresult *result)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+	result_return_t ret = SUCCESS;
+	const ExecStatusType status = PQresultStatus(result);
+
+	switch (query_ctx->update_state) {
+		case CREATE:
+		case COPY_2:
+			if (status != PGRES_COMMAND_OK)
+				goto error;
+
+			query_ctx->update_state++;
+			break;
+		case COPY_1:
+			if (status != PGRES_COPY_IN)
+				goto error;
+
+			ret = WANT_WRITE;
+			break;
+		case UPDATE:
+			if (status != PGRES_COMMAND_OK)
+				goto error;
+
+			serialize_items(query_ctx->res,
+			                query_ctx->num_result,
+			                &query_ctx->single.h2o_generator,
+			                query_ctx->single.gen,
+			                query_ctx->single.req);
+			ret = DONE;
+			break;
+		default:
+			goto error;
+	}
+
+	PQclear(result);
+	return ret;
+error:
+	send_error(BAD_GATEWAY, PQresultErrorMessage(result), query_ctx->single.req);
+	PQclear(result);
+	return DONE;
+}
+
+static int on_update_write_ready(db_query_param_t *param, PGconn *db_conn)
+{
+	multiple_query_ctx_t * const query_ctx = H2O_STRUCT_FROM_MEMBER(multiple_query_ctx_t,
+	                                                                single.param,
+	                                                                param);
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      query_ctx->single.req->conn->ctx);
+
+	if (query_ctx->num_result == query_ctx->num_query && query_ctx->update_state != COPY_2) {
+		const int rc = PQputCopyData(db_conn, H2O_STRLIT(COPY_HEADER));
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+
+		query_ctx->num_result = 0;
+		query_ctx->update_state = COPY_2;
+	}
+
+	if (query_ctx->num_result < query_ctx->num_query) {
+		assert(query_ctx->num_query - query_ctx->num_result <= MAX_QUERIES);
+
+		// There are at most MAX_QUERIES elements, so allocate on the stack.
+		struct __attribute__ ((__packed__)) {
+			uint16_t field_count;
+			uint32_t id_size;
+			uint32_t id;
+			uint32_t random_number_size;
+			uint32_t random_number;
+		} data[query_ctx->num_query - query_ctx->num_result];
+
+		memset(&data, 0, sizeof(data));
+
+		for (size_t i = 0; i < query_ctx->num_query; i++) {
+			query_ctx->res[i].random_number = get_random_number(MAX_ID, &ctx->random_seed) + 1;
+			data[i].field_count = htons(2);
+			data[i].id_size = htonl(sizeof(data->id));
+			data[i].id = htonl(query_ctx->res[i].id);
+			data[i].random_number_size = htonl(sizeof(data->random_number));
+			data[i].random_number = htonl(query_ctx->res[i].random_number);
+		}
+
+		const int rc = PQputCopyData(db_conn, (const char *) &data, sizeof(data));
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+
+		query_ctx->num_result = query_ctx->num_query;
+	}
+
+	if (query_ctx->num_result == query_ctx->num_query) {
+		const int rc = PQputCopyEnd(db_conn, NULL);
+
+		if (!rc)
+			return 1;
+		else if (rc < 0)
+			return rc;
+	}
+
+	return PQflush(db_conn);
+}
+
+static int serialize_item(uint32_t id, uint32_t random_number, yajl_gen gen)
+{
+	CHECK_YAJL_STATUS(yajl_gen_map_open, gen);
+	CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT(ID_KEY));
+	CHECK_YAJL_STATUS(yajl_gen_integer, gen, id);
+	CHECK_YAJL_STATUS(yajl_gen_string, gen, YAJL_STRLIT(RANDOM_NUM_KEY));
+	CHECK_YAJL_STATUS(yajl_gen_integer, gen, random_number);
+	CHECK_YAJL_STATUS(yajl_gen_map_close, gen);
+	return EXIT_SUCCESS;
+error_yajl:
+	return EXIT_FAILURE;
+}
+
+static void serialize_items(const query_result_t *res,
+                            size_t num_result,
+                            h2o_generator_t *h2o_generator,
+                            yajl_gen gen,
+                            h2o_req_t *req)
+{
+	CHECK_YAJL_STATUS(yajl_gen_array_open, gen);
+
+	for (size_t i = 0; i < num_result; i++)
+		if (serialize_item(res[i].id, res[i].random_number, gen))
+			goto error_yajl;
+
+	CHECK_YAJL_STATUS(yajl_gen_array_close, gen);
+
+	if (send_json_response(gen, h2o_generator, req))
+error_yajl:
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+}
+
+int multiple_queries(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	return do_multiple_queries(NO_UPDATE, req);
+}
+
+int single_query(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	thread_context_t * const ctx = H2O_STRUCT_FROM_MEMBER(thread_context_t,
+	                                                      event_loop.h2o_ctx,
+	                                                      req->conn->ctx);
+	single_query_ctx_t * const query_ctx = h2o_mem_alloc_pool(&req->pool, sizeof(*query_ctx));
+
+	if (!query_ctx || initialize_single_query_context(req,
+	                                                  on_single_query_result,
+	                                                  query_ctx))
+		send_error(INTERNAL_SERVER_ERROR, MEM_ALLOC_ERR_MSG, req);
+	else {
+		query_ctx->id = htonl(get_random_number(MAX_ID, &ctx->random_seed) + 1);
+
+		if (execute_query(ctx, &query_ctx->param)) {
+			yajl_gen_free(query_ctx->gen);
+			send_service_unavailable_error(DB_REQ_ERROR, req);
+		}
+	}
+
+	return 0;
+}
+
+int updates(struct st_h2o_handler_t *self, h2o_req_t *req)
+{
+	IGNORE_FUNCTION_PARAMETER(self);
+
+	return do_multiple_queries(CREATE, req);
+}

+ 30 - 0
frameworks/C/h2o/src/world.h

@@ -0,0 +1,30 @@
+/*
+ Copyright (c) 2016 Anton Valentinov Kirilov
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
+ associated documentation files (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or
+ substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
+ NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
+ OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#ifndef WORLD_H_
+
+#define WORLD_H_
+
+#include <h2o.h>
+
+int multiple_queries(struct st_h2o_handler_t *self, h2o_req_t *req);
+int single_query(struct st_h2o_handler_t *self, h2o_req_t *req);
+int updates(struct st_h2o_handler_t *self, h2o_req_t *req);
+
+#endif // WORLD_H_

+ 10 - 0
frameworks/C/h2o/template/fortunes.mustache

@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head><title>Fortunes</title></head>
+<body>
+<table>
+<tr><th>id</th><th>message</th></tr>
+{{#Fortune}}<tr><td>{{id}}</td><td>{{message}}</td></tr>{{/Fortune}}
+</table>
+</body>
+</html>

+ 23 - 0
toolset/setup/linux/systools/mustache-c.sh

@@ -0,0 +1,23 @@
+#!/bin/bash
+
+RETCODE=$(fw_exists "${IROOT}/mustache-c.installed")
+[ ! "$RETCODE" == 0 ] || { \
+  source "${IROOT}/mustache-c.installed"
+  return 0; }
+
+MUSTACHE_C_HOME="${IROOT}/mustache-c"
+BUILD_DIR="${MUSTACHE_C_HOME}_build"
+
+git clone 'https://github.com/x86-64/mustache-c.git' "$BUILD_DIR"
+pushd "$BUILD_DIR"
+git checkout c00262c9bb57b7871b1c68930e85c8f0e4c8c7d9
+./configure --prefix="$MUSTACHE_C_HOME"
+make -j "$(nproc)" install
+popd
+rm -rf "$BUILD_DIR"
+
+echo "export MUSTACHE_C_HOME=$MUSTACHE_C_HOME" > "${IROOT}/mustache-c.installed"
+echo -e "export LD_LIBRARY_PATH=\$MUSTACHE_C_HOME/lib:\$LD_LIBRARY_PATH" \
+	>> "${IROOT}/mustache-c.installed"
+
+source "${IROOT}/mustache-c.installed"

+ 27 - 0
toolset/setup/linux/systools/yajl.sh

@@ -0,0 +1,27 @@
+#!/bin/bash
+
+RETCODE=$(fw_exists "${IROOT}/yajl.installed")
+[ ! "$RETCODE" == 0 ] || { \
+  source "${IROOT}/yajl.installed"
+  return 0; }
+
+VERSION="2.1.0"
+ARCHIVE="${VERSION}.tar.gz"
+BUILD_DIR="yajl-${VERSION}"
+YAJL_HOME="${IROOT}/yajl"
+
+pushd "${IROOT}"
+fw_get -O "https://github.com/lloyd/yajl/archive/$ARCHIVE"
+fw_untar "$ARCHIVE"
+pushd "$BUILD_DIR"
+./configure -p "$YAJL_HOME"
+make -j "$(nproc)" install
+popd
+rm -rf "$BUILD_DIR"
+popd
+
+echo "export YAJL_HOME=$YAJL_HOME" > "${IROOT}/yajl.installed"
+echo -e "export LD_LIBRARY_PATH=\$YAJL_HOME/lib:\$LD_LIBRARY_PATH" \
+	>> "${IROOT}/yajl.installed"
+
+source "${IROOT}/yajl.installed"

+ 26 - 0
toolset/setup/linux/webservers/h2o.sh

@@ -0,0 +1,26 @@
+#!/bin/bash
+
+RETCODE=$(fw_exists "${IROOT}/h2o.installed")
+[ ! "$RETCODE" == 0 ] || { \
+  source "${IROOT}/h2o.installed"
+  return 0; }
+
+H2O_HOME="${IROOT}/h2o"
+VERSION="2.1.0-beta1"
+ARCHIVE="v${VERSION}.tar.gz"
+BUILD_DIR="h2o-${VERSION}"
+
+pushd "${IROOT}"
+fw_get -O "https://github.com/h2o/h2o/archive/$ARCHIVE"
+fw_untar "$ARCHIVE"
+pushd "$BUILD_DIR"
+cmake -DCMAKE_INSTALL_PREFIX="$H2O_HOME"
+make -j "$(nproc)" install
+popd
+rm -rf "$BUILD_DIR"
+popd
+
+echo "export H2O_HOME=$H2O_HOME" > "${IROOT}/h2o.installed"
+echo -e "export PATH=\$H2O_HOME/bin:\$PATH" >> "${IROOT}/h2o.installed"
+
+source "${IROOT}/h2o.installed"