Bladeren bron

Re-add Lwan to the suite (#5229)

Source code for the benchmark is now included in this repository. Since
there's no recent version of Lwan, however, a specific Git hash is
obtained from the repository so that the framework can be built.
Leandro A. F. Pereira 5 jaren geleden
bovenliggende
commit
54659ff0c0

+ 30 - 0
frameworks/C/lwan/Makefile

@@ -0,0 +1,30 @@
+.PHONY: all
+
+CFLAGS = -mtune=native -march=native -O3 -flto -ffat-lto-objects -DNDEBUG \
+	-include /lwan/build/lwan-build-config.h \
+	-I /lwan/src/lib \
+	`pkg-config mariadb --cflags` \
+	`pkg-config sqlite3 --cflags`
+
+LDFLAGS = -mtune=native -march=native -O3 -flto -ffat-lto-objects \
+	-Wl,-whole-archive /lwan/build/src/lib/liblwan.a -Wl,-no-whole-archive \
+	`pkg-config mariadb --libs` \
+	`pkg-config sqlite3 --libs` \
+	-lpthread \
+	-lz
+
+SRCS = src/techempower.c src/database.c src/json.c
+
+OBJS = $(SRCS:.c=.o)
+
+all: techempower
+
+.c.o: $<
+	$(CC) $(CFLAGS) -c $< -o $@
+
+techempower: $(OBJS)
+	$(CC) $(LDFLAGS) $(OBJS) -o techempower
+
+clean:
+	rm -f techempower $(OBJS)
+

+ 33 - 0
frameworks/C/lwan/README.md

@@ -0,0 +1,33 @@
+# Lwan Benchmarking Test
+
+This test is based on of the [Lwan](https://lwan.ws) web-server project,
+an experimental high-performance web server.
+
+### Test Type Implementation Source Code
+
+* [JSON](src/techempower.c)
+* [PLAINTEXT](src/techempower.c)
+* [DB](src/techempower.c)
+* [QUERY](src/techempower.c)
+* [FORTUNES](src/techempower.c)
+
+## Test URLs
+### JSON
+
+http://localhost:8080/json
+
+### PLAINTEXT
+
+http://localhost:8080/plaintext
+
+### DB
+
+http://localhost:8080/db
+
+### QUERY
+
+http://localhost:8080/query?queries=
+
+### FORTUNES
+
+http://localhost:8080/fortunes

+ 29 - 0
frameworks/C/lwan/benchmark_config.json

@@ -0,0 +1,29 @@
+{
+  "framework": "lwan",
+  "tests": [
+    {
+      "default": {
+        "json_url": "/json",
+        "db_url": "/db",
+        "queries_url": "/queries",
+        "plaintext_url": "/plaintext",
+        "fortune_url": "/fortunes",
+        "port": 8080,
+        "approach": "Realistic",
+        "classification": "Micro",
+        "database": "mysql",
+        "framework": "Lwan",
+        "language": "C",
+        "flavor": "None",
+        "orm": "Raw",
+        "platform": "Lwan",
+        "webserver": "Lwan",
+        "os": "Linux",
+        "database_os": "Linux",
+        "display_name": "Lwan",
+        "notes": "",
+        "versus": "None"
+      }
+    }
+  ]
+}

+ 24 - 0
frameworks/C/lwan/lwan.dockerfile

@@ -0,0 +1,24 @@
+FROM ubuntu:19.04
+
+RUN apt update -yqq
+RUN apt install -yqq \
+	git pkg-config build-essential cmake zlib1g-dev \
+	libsqlite3-dev libmariadbclient-dev wget
+
+ADD ./ /lwan
+WORKDIR /lwan
+
+RUN wget https://github.com/lpereira/lwan/archive/d7fc0d27fbea5c68d61444033517d0e962e822e6.tar.gz -O - | tar xz --strip-components=1 && \
+    mkdir build && cd build && \
+    cmake /lwan -DCMAKE_BUILD_TYPE=Release && \
+    make lwan-static
+
+RUN make clean && make
+
+ENV USE_MYSQL=1
+ENV MYSQL_USER=benchmarkdbuser
+ENV MYSQL_PASS=benchmarkdbpass
+ENV MYSQL_DB=hello_world
+ENV MYSQL_HOST=tfb-database
+
+CMD ["./techempower"]

+ 388 - 0
frameworks/C/lwan/src/database.c

@@ -0,0 +1,388 @@
+/*
+ * lwan - simple web server
+ * Copyright (c) 2014 Leandro A. F. Pereira <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <mysql.h>
+#include <sqlite3.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include "database.h"
+#include "lwan-status.h"
+
+struct db_stmt {
+    bool (*bind)(const struct db_stmt *stmt,
+                 struct db_row *rows,
+                 size_t n_rows);
+    bool (*step)(const struct db_stmt *stmt, struct db_row *row);
+    void (*finalize)(struct db_stmt *stmt);
+};
+
+struct db {
+    void (*disconnect)(struct db *db);
+    struct db_stmt *(*prepare)(const struct db *db,
+                               const char *sql,
+                               const size_t sql_len);
+};
+
+/* MySQL */
+
+struct db_mysql {
+    struct db base;
+    MYSQL *con;
+};
+
+struct db_stmt_mysql {
+    struct db_stmt base;
+    MYSQL_STMT *stmt;
+    MYSQL_BIND *param_bind;
+    MYSQL_BIND *result_bind;
+    bool must_execute_again;
+};
+
+static bool db_stmt_bind_mysql(const struct db_stmt *stmt,
+                               struct db_row *rows,
+                               size_t n_rows)
+{
+    struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
+
+    stmt_mysql->must_execute_again = true;
+
+    if (!stmt_mysql->param_bind) {
+        stmt_mysql->param_bind = calloc(n_rows, sizeof(MYSQL_BIND));
+        if (!stmt_mysql->param_bind) {
+            return false;
+        }
+    } else {
+        mysql_stmt_reset(stmt_mysql->stmt);
+    }
+
+    for (size_t row = 0; row < n_rows; row++) {
+        if (rows[row].kind == '\0')
+            break;
+
+        MYSQL_BIND *param = &stmt_mysql->param_bind[row];
+        if (rows[row].kind == 's') {
+            param->buffer_type = MYSQL_TYPE_STRING;
+            param->buffer = rows[row].u.s;
+        } else if (rows[row].kind == 'i') {
+            param->buffer_type = MYSQL_TYPE_LONG;
+            param->buffer = &rows[row].u.i;
+        }
+        param->is_null = false;
+        param->length = 0;
+    }
+
+    return !mysql_stmt_bind_param(stmt_mysql->stmt, stmt_mysql->param_bind);
+}
+
+static bool db_stmt_step_mysql(const struct db_stmt *stmt, struct db_row *row)
+{
+    struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
+
+    if (stmt_mysql->must_execute_again) {
+        stmt_mysql->must_execute_again = false;
+        if (mysql_stmt_execute(stmt_mysql->stmt))
+            return false;
+    }
+
+    if (!stmt_mysql->result_bind) {
+        size_t n_rows = 0;
+        for (struct db_row *r = row; r->kind != '\0'; r++)
+            n_rows++;
+
+        if (!n_rows)
+            return false;
+
+        stmt_mysql->result_bind =
+            calloc(n_rows, sizeof(*stmt_mysql->result_bind));
+        if (!stmt_mysql->result_bind)
+            return false;
+
+        stmt_mysql->param_bind =
+            calloc(n_rows, sizeof(*stmt_mysql->param_bind));
+        if (!stmt_mysql->param_bind) {
+            free(stmt_mysql->result_bind);
+            return false;
+        }
+
+        MYSQL_BIND *result = stmt_mysql->result_bind;
+        for (size_t r = 0; r < n_rows; r++) {
+            if (row[r].kind == 's') {
+                result[r].buffer_type = MYSQL_TYPE_STRING;
+                result[r].buffer = row[r].u.s;
+            } else if (row[r].kind == 'i') {
+                result[r].buffer_type = MYSQL_TYPE_LONG;
+                result[r].buffer = &row[r].u.i;
+            } else {
+                return false;
+            }
+
+            result[r].is_null = false;
+            result[r].buffer_length = row[r].buffer_length;
+        }
+
+        if (mysql_stmt_bind_result(stmt_mysql->stmt, result))
+            return false;
+    }
+
+    return mysql_stmt_fetch(stmt_mysql->stmt) == 0;
+}
+
+static void db_stmt_finalize_mysql(struct db_stmt *stmt)
+{
+    struct db_stmt_mysql *stmt_mysql = (struct db_stmt_mysql *)stmt;
+
+    mysql_stmt_close(stmt_mysql->stmt);
+    free(stmt_mysql->result_bind);
+    free(stmt_mysql->param_bind);
+    free(stmt_mysql);
+}
+
+static struct db_stmt *
+db_prepare_mysql(const struct db *db, const char *sql, const size_t sql_len)
+{
+    const struct db_mysql *db_mysql = (const struct db_mysql *)db;
+    struct db_stmt_mysql *stmt_mysql = malloc(sizeof(*stmt_mysql));
+
+    if (!stmt_mysql)
+        return NULL;
+
+    stmt_mysql->stmt = mysql_stmt_init(db_mysql->con);
+    if (!stmt_mysql->stmt)
+        goto out_free_stmt;
+
+    if (mysql_stmt_prepare(stmt_mysql->stmt, sql, sql_len))
+        goto out_close_stmt;
+
+    stmt_mysql->base.bind = db_stmt_bind_mysql;
+    stmt_mysql->base.step = db_stmt_step_mysql;
+    stmt_mysql->base.finalize = db_stmt_finalize_mysql;
+    stmt_mysql->result_bind = NULL;
+    stmt_mysql->param_bind = NULL;
+    stmt_mysql->must_execute_again = true;
+
+    return (struct db_stmt *)stmt_mysql;
+
+out_close_stmt:
+    mysql_stmt_close(stmt_mysql->stmt);
+out_free_stmt:
+    free(stmt_mysql);
+
+    return NULL;
+}
+
+static void db_disconnect_mysql(struct db *db)
+{
+    struct db_mysql *db_mysql = (struct db_mysql *)db;
+
+    mysql_close(db_mysql->con);
+    free(db);
+}
+
+struct db *db_connect_mysql(const char *host,
+                            const char *user,
+                            const char *pass,
+                            const char *database)
+{
+    struct db_mysql *db_mysql = malloc(sizeof(*db_mysql));
+
+    if (!db_mysql)
+        return NULL;
+
+    db_mysql->con = mysql_init(NULL);
+    if (!db_mysql->con) {
+        free(db_mysql);
+        return NULL;
+    }
+
+    if (!mysql_real_connect(db_mysql->con, host, user, pass, database, 0, NULL,
+                            0))
+        goto error;
+
+    if (mysql_set_character_set(db_mysql->con, "utf8"))
+        goto error;
+
+    db_mysql->base.disconnect = db_disconnect_mysql;
+    db_mysql->base.prepare = db_prepare_mysql;
+
+    return (struct db *)db_mysql;
+
+error:
+    mysql_close(db_mysql->con);
+    free(db_mysql);
+    return NULL;
+}
+
+/* SQLite */
+
+struct db_sqlite {
+    struct db base;
+    sqlite3 *sqlite;
+};
+
+struct db_stmt_sqlite {
+    struct db_stmt base;
+    sqlite3_stmt *sqlite;
+};
+
+static bool db_stmt_bind_sqlite(const struct db_stmt *stmt,
+                                struct db_row *rows,
+                                size_t n_rows)
+{
+    const struct db_stmt_sqlite *stmt_sqlite =
+        (const struct db_stmt_sqlite *)stmt;
+    const struct db_row *rows_1_based = rows - 1;
+    int ret;
+
+    sqlite3_reset(stmt_sqlite->sqlite);
+    sqlite3_clear_bindings(stmt_sqlite->sqlite);
+
+    for (size_t row = 1; row <= n_rows; row++) {
+        const struct db_row *r = &rows_1_based[row];
+        if (r->kind == '\0')
+            break;
+
+        if (r->kind == 's') {
+            ret = sqlite3_bind_text(stmt_sqlite->sqlite, (int)row, r->u.s, -1,
+                                    NULL);
+            if (ret != SQLITE_OK)
+                return false;
+        } else if (r->kind == 'i') {
+            ret = sqlite3_bind_int(stmt_sqlite->sqlite, (int)row, r->u.i);
+            if (ret != SQLITE_OK)
+                return false;
+        } else {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static bool db_stmt_step_sqlite(const struct db_stmt *stmt, struct db_row *row)
+{
+    const struct db_stmt_sqlite *stmt_sqlite =
+        (const struct db_stmt_sqlite *)stmt;
+
+    if (sqlite3_step(stmt_sqlite->sqlite) != SQLITE_ROW)
+        return false;
+
+    int column_id = 0;
+    for (struct db_row *r = row; r->kind != '\0'; r++, column_id++) {
+        if (r->kind == 'i') {
+            r->u.i = sqlite3_column_int(stmt_sqlite->sqlite, column_id);
+        } else if (r->kind == 's') {
+            r->u.s =
+                (char *)sqlite3_column_text(stmt_sqlite->sqlite, column_id);
+        } else {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+static void db_stmt_finalize_sqlite(struct db_stmt *stmt)
+{
+    struct db_stmt_sqlite *stmt_sqlite = (struct db_stmt_sqlite *)stmt;
+
+    sqlite3_finalize(stmt_sqlite->sqlite);
+    free(stmt_sqlite);
+}
+
+static struct db_stmt *
+db_prepare_sqlite(const struct db *db, const char *sql, const size_t sql_len)
+{
+    const struct db_sqlite *db_sqlite = (const struct db_sqlite *)db;
+    struct db_stmt_sqlite *stmt_sqlite = malloc(sizeof(*stmt_sqlite));
+
+    if (!stmt_sqlite)
+        return NULL;
+
+    int ret = sqlite3_prepare(db_sqlite->sqlite, sql, (int)sql_len,
+                              &stmt_sqlite->sqlite, NULL);
+    if (ret != SQLITE_OK) {
+        free(stmt_sqlite);
+        return NULL;
+    }
+
+    stmt_sqlite->base.bind = db_stmt_bind_sqlite;
+    stmt_sqlite->base.step = db_stmt_step_sqlite;
+    stmt_sqlite->base.finalize = db_stmt_finalize_sqlite;
+
+    return (struct db_stmt *)stmt_sqlite;
+}
+
+static void db_disconnect_sqlite(struct db *db)
+{
+    struct db_sqlite *db_sqlite = (struct db_sqlite *)db;
+
+    sqlite3_close(db_sqlite->sqlite);
+    free(db);
+}
+
+struct db *
+db_connect_sqlite(const char *path, bool read_only, const char *pragmas[])
+{
+    struct db_sqlite *db_sqlite = malloc(sizeof(*db_sqlite));
+
+    if (!db_sqlite)
+        return NULL;
+
+    int flags = read_only ? SQLITE_OPEN_READONLY : 0;
+    int ret = sqlite3_open_v2(path, &db_sqlite->sqlite, flags, NULL);
+    if (ret != SQLITE_OK) {
+        free(db_sqlite);
+        return NULL;
+    }
+
+    if (pragmas) {
+        for (size_t p = 0; pragmas[p]; p++)
+            sqlite3_exec(db_sqlite->sqlite, pragmas[p], NULL, NULL, NULL);
+    }
+
+    db_sqlite->base.disconnect = db_disconnect_sqlite;
+    db_sqlite->base.prepare = db_prepare_sqlite;
+
+    return (struct db *)db_sqlite;
+}
+
+/* Generic */
+
+inline bool
+db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows)
+{
+    return stmt->bind(stmt, rows, n_rows);
+}
+
+inline bool db_stmt_step(const struct db_stmt *stmt, struct db_row *row)
+{
+    return stmt->step(stmt, row);
+}
+
+inline void db_stmt_finalize(struct db_stmt *stmt) { stmt->finalize(stmt); }
+
+inline void db_disconnect(struct db *db) { db->disconnect(db); }
+
+inline struct db_stmt *
+db_prepare_stmt(const struct db *db, const char *sql, const size_t sql_len)
+{
+    return db->prepare(db, sql, sql_len);
+}

+ 45 - 0
frameworks/C/lwan/src/database.h

@@ -0,0 +1,45 @@
+/*
+ * lwan - simple web server
+ * Copyright (c) 2014 Leandro A. F. Pereira <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+#pragma once
+
+#include <stdbool.h>
+
+struct db;
+struct db_stmt;
+
+struct db_row {
+    union {
+        char *s;
+        int i;
+    } u;
+    char kind; /* 's' = string, 'i' = 'int', '\0' = last */
+    size_t buffer_length;
+};
+
+bool db_stmt_bind(const struct db_stmt *stmt, struct db_row *rows, size_t n_rows);
+bool db_stmt_step(const struct db_stmt *stmt, struct db_row *row);
+void db_stmt_finalize(struct db_stmt *stmt);
+void db_disconnect(struct db *db);
+struct db_stmt *db_prepare_stmt(const struct db *db, const char *sql,
+    const size_t sql_len);
+
+struct db *db_connect_sqlite(const char *path, bool read_only, const char *pragmas[]);
+struct db *db_connect_mysql(const char *host, const char *user, const char *pass, const char *database);
+

+ 933 - 0
frameworks/C/lwan/src/json.c

@@ -0,0 +1,933 @@
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "json.h"
+
+struct token {
+    enum json_tokens type;
+    char *start;
+    char *end;
+};
+
+struct lexer {
+    void *(*state)(struct lexer *lexer);
+    char *start;
+    char *pos;
+    char *end;
+    struct token token;
+};
+
+struct json_obj {
+    struct lexer lexer;
+};
+
+struct json_obj_key_value {
+    const char *key;
+    size_t key_len;
+    struct token value;
+};
+
+static bool lexer_consume(struct lexer *lexer,
+                          struct token *token,
+                          enum json_tokens empty_token)
+{
+    if (lexer->token.type == empty_token) {
+        return false;
+    }
+
+    *token = lexer->token;
+    lexer->token.type = empty_token;
+
+    return true;
+}
+
+static bool lexer_next(struct lexer *lexer, struct token *token)
+{
+    while (lexer->state) {
+        if (lexer_consume(lexer, token, JSON_TOK_NONE)) {
+            return true;
+        }
+
+        lexer->state = lexer->state(lexer);
+    }
+
+    return lexer_consume(lexer, token, JSON_TOK_EOF);
+}
+
+static void *lexer_json(struct lexer *lexer);
+
+static void emit(struct lexer *lexer, enum json_tokens token)
+{
+    lexer->token.type = token;
+    lexer->token.start = lexer->start;
+    lexer->token.end = lexer->pos;
+    lexer->start = lexer->pos;
+}
+
+static int next(struct lexer *lexer)
+{
+    if (lexer->pos >= lexer->end) {
+        lexer->pos = lexer->end + 1;
+
+        return '\0';
+    }
+
+    return *lexer->pos++;
+}
+
+static void ignore(struct lexer *lexer) { lexer->start = lexer->pos; }
+
+static void backup(struct lexer *lexer) { lexer->pos--; }
+
+static int peek(struct lexer *lexer)
+{
+    int chr = next(lexer);
+
+    backup(lexer);
+
+    return chr;
+}
+
+static void *lexer_string(struct lexer *lexer)
+{
+    ignore(lexer);
+
+    while (true) {
+        int chr = next(lexer);
+
+        if (chr == '\0') {
+            emit(lexer, JSON_TOK_ERROR);
+            return NULL;
+        }
+
+        if (chr == '\\') {
+            switch (next(lexer)) {
+            case '"':
+            case '\\':
+            case '/':
+            case 'b':
+            case 'f':
+            case 'n':
+            case 'r':
+            case 't':
+                continue;
+            case 'u':
+                if (!isxdigit(next(lexer))) {
+                    goto error;
+                }
+
+                if (!isxdigit(next(lexer))) {
+                    goto error;
+                }
+
+                if (!isxdigit(next(lexer))) {
+                    goto error;
+                }
+
+                if (!isxdigit(next(lexer))) {
+                    goto error;
+                }
+
+                break;
+            default:
+                goto error;
+            }
+        }
+
+        if (chr == '"') {
+            backup(lexer);
+            emit(lexer, JSON_TOK_STRING);
+
+            next(lexer);
+            ignore(lexer);
+
+            return lexer_json;
+        }
+    }
+
+error:
+    emit(lexer, JSON_TOK_ERROR);
+    return NULL;
+}
+
+static int accept_run(struct lexer *lexer, const char *run)
+{
+    for (; *run; run++) {
+        if (next(lexer) != *run) {
+            return -EINVAL;
+        }
+    }
+
+    return 0;
+}
+
+static void *lexer_boolean(struct lexer *lexer)
+{
+    backup(lexer);
+
+    switch (next(lexer)) {
+    case 't':
+        if (!accept_run(lexer, "rue")) {
+            emit(lexer, JSON_TOK_TRUE);
+            return lexer_json;
+        }
+        break;
+    case 'f':
+        if (!accept_run(lexer, "alse")) {
+            emit(lexer, JSON_TOK_FALSE);
+            return lexer_json;
+        }
+        break;
+    }
+
+    emit(lexer, JSON_TOK_ERROR);
+    return NULL;
+}
+
+static void *lexer_null(struct lexer *lexer)
+{
+    if (accept_run(lexer, "ull") < 0) {
+        emit(lexer, JSON_TOK_ERROR);
+        return NULL;
+    }
+
+    emit(lexer, JSON_TOK_NULL);
+    return lexer_json;
+}
+
+static void *lexer_number(struct lexer *lexer)
+{
+    while (true) {
+        int chr = next(lexer);
+
+        if (isdigit(chr) || chr == '.') {
+            continue;
+        }
+
+        backup(lexer);
+        emit(lexer, JSON_TOK_NUMBER);
+
+        return lexer_json;
+    }
+}
+
+static void *lexer_json(struct lexer *lexer)
+{
+    while (true) {
+        int chr = next(lexer);
+
+        switch (chr) {
+        case '\0':
+            emit(lexer, JSON_TOK_EOF);
+            return NULL;
+        case '}':
+        case '{':
+        case '[':
+        case ']':
+        case ',':
+        case ':':
+            emit(lexer, (enum json_tokens)chr);
+            return lexer_json;
+        case '"':
+            return lexer_string;
+        case 'n':
+            return lexer_null;
+        case 't':
+        case 'f':
+            return lexer_boolean;
+        case '-':
+            if (isdigit(peek(lexer))) {
+                return lexer_number;
+            }
+
+            /* fallthrough */
+        default:
+            if (isspace(chr)) {
+                ignore(lexer);
+                continue;
+            }
+
+            if (isdigit(chr)) {
+                return lexer_number;
+            }
+
+            emit(lexer, JSON_TOK_ERROR);
+            return NULL;
+        }
+    }
+}
+
+static void lexer_init(struct lexer *lexer, char *data, size_t len)
+{
+    lexer->state = lexer_json;
+    lexer->start = data;
+    lexer->pos = data;
+    lexer->end = data + len;
+    lexer->token.type = JSON_TOK_NONE;
+}
+
+static int obj_init(struct json_obj *json, char *data, size_t len)
+{
+    struct token token;
+
+    lexer_init(&json->lexer, data, len);
+
+    if (!lexer_next(&json->lexer, &token)) {
+        return -EINVAL;
+    }
+
+    if (token.type != JSON_TOK_OBJECT_START) {
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+static int element_token(enum json_tokens token)
+{
+    switch (token) {
+    case JSON_TOK_OBJECT_START:
+    case JSON_TOK_LIST_START:
+    case JSON_TOK_STRING:
+    case JSON_TOK_NUMBER:
+    case JSON_TOK_TRUE:
+    case JSON_TOK_FALSE:
+        return 0;
+    default:
+        return -EINVAL;
+    }
+}
+
+static int obj_next(struct json_obj *json, struct json_obj_key_value *kv)
+{
+    struct token token;
+
+    if (!lexer_next(&json->lexer, &token)) {
+        return -EINVAL;
+    }
+
+    /* Match end of object or next key */
+    switch (token.type) {
+    case JSON_TOK_OBJECT_END:
+        kv->key = NULL;
+        kv->key_len = 0;
+        kv->value = token;
+
+        return 0;
+    case JSON_TOK_COMMA:
+        if (!lexer_next(&json->lexer, &token)) {
+            return -EINVAL;
+        }
+
+        if (token.type != JSON_TOK_STRING) {
+            return -EINVAL;
+        }
+
+        /* fallthrough */
+    case JSON_TOK_STRING:
+        kv->key = token.start;
+        kv->key_len = (size_t)(token.end - token.start);
+        break;
+    default:
+        return -EINVAL;
+    }
+
+    /* Match : after key */
+    if (!lexer_next(&json->lexer, &token)) {
+        return -EINVAL;
+    }
+
+    if (token.type != JSON_TOK_COLON) {
+        return -EINVAL;
+    }
+
+    /* Match value */
+    if (!lexer_next(&json->lexer, &kv->value)) {
+        return -EINVAL;
+    }
+
+    return element_token(kv->value.type);
+}
+
+static int arr_next(struct json_obj *json, struct token *value)
+{
+    if (!lexer_next(&json->lexer, value)) {
+        return -EINVAL;
+    }
+
+    if (value->type == JSON_TOK_LIST_END) {
+        return 0;
+    }
+
+    if (value->type == JSON_TOK_COMMA) {
+        if (!lexer_next(&json->lexer, value)) {
+            return -EINVAL;
+        }
+    }
+
+    return element_token(value->type);
+}
+
+static int decode_num(const struct token *token, int32_t *num)
+{
+    /* FIXME: strtod() is not available in newlib/minimal libc,
+     * so using strtol() here.
+     */
+    char *endptr;
+    char prev_end;
+
+    prev_end = *token->end;
+    *token->end = '\0';
+
+    errno = 0;
+    *num = strtol(token->start, &endptr, 10);
+
+    *token->end = prev_end;
+
+    if (errno != 0) {
+        return -errno;
+    }
+
+    if (endptr != token->end) {
+        return -EINVAL;
+    }
+
+    return 0;
+}
+
+static bool equivalent_types(enum json_tokens type1, enum json_tokens type2)
+{
+    if (type1 == JSON_TOK_TRUE || type1 == JSON_TOK_FALSE) {
+        return type2 == JSON_TOK_TRUE || type2 == JSON_TOK_FALSE;
+    }
+
+    return type1 == type2;
+}
+
+static int obj_parse(struct json_obj *obj,
+                     const struct json_obj_descr *descr,
+                     size_t descr_len,
+                     void *val);
+static int arr_parse(struct json_obj *obj,
+                     const struct json_obj_descr *elem_descr,
+                     size_t max_elements,
+                     void *field,
+                     void *val);
+
+static int decode_value(struct json_obj *obj,
+                        const struct json_obj_descr *descr,
+                        struct token *value,
+                        void *field,
+                        void *val)
+{
+
+    if (!equivalent_types(value->type, descr->type)) {
+        return -EINVAL;
+    }
+
+    switch (descr->type) {
+    case JSON_TOK_OBJECT_START:
+        return obj_parse(obj, descr->object.sub_descr,
+                         descr->object.sub_descr_len, field);
+    case JSON_TOK_LIST_START:
+        return arr_parse(obj, descr->array.element_descr,
+                         descr->array.n_elements, field, val);
+    case JSON_TOK_FALSE:
+    case JSON_TOK_TRUE: {
+        bool *v = field;
+
+        *v = value->type == JSON_TOK_TRUE;
+
+        return 0;
+    }
+    case JSON_TOK_NUMBER: {
+        int32_t *num = field;
+
+        return decode_num(value, num);
+    }
+    case JSON_TOK_STRING: {
+        char **str = field;
+
+        *value->end = '\0';
+        *str = value->start;
+
+        return 0;
+    }
+    default:
+        return -EINVAL;
+    }
+}
+
+static ptrdiff_t get_elem_size(const struct json_obj_descr *descr)
+{
+    switch (descr->type) {
+    case JSON_TOK_NUMBER:
+        return sizeof(int32_t);
+    case JSON_TOK_STRING:
+        return sizeof(char *);
+    case JSON_TOK_TRUE:
+    case JSON_TOK_FALSE:
+        return sizeof(bool);
+    case JSON_TOK_LIST_START:
+        return descr->array.n_elements *
+               get_elem_size(descr->array.element_descr);
+    case JSON_TOK_OBJECT_START: {
+        ptrdiff_t total = 0;
+        size_t i;
+
+        for (i = 0; i < descr->object.sub_descr_len; i++) {
+            ptrdiff_t s = get_elem_size(&descr->object.sub_descr[i]);
+
+            total += ROUND_UP(s, 1 << descr->object.sub_descr[i].align_shift);
+        }
+
+        return total;
+    }
+    default:
+        return -EINVAL;
+    }
+}
+
+static int arr_parse(struct json_obj *obj,
+                     const struct json_obj_descr *elem_descr,
+                     size_t max_elements,
+                     void *field,
+                     void *val)
+{
+    ptrdiff_t elem_size = get_elem_size(elem_descr);
+    void *last_elem = (char *)field + elem_size * max_elements;
+    size_t *elements = (size_t *)((char *)val + elem_descr->offset);
+    struct token value;
+
+    assert(elem_size > 0);
+
+    *elements = 0;
+
+    while (!arr_next(obj, &value)) {
+        if (value.type == JSON_TOK_LIST_END) {
+            return 0;
+        }
+
+        if (field == last_elem) {
+            return -ENOSPC;
+        }
+
+        if (decode_value(obj, elem_descr, &value, field, val) < 0) {
+            return -EINVAL;
+        }
+
+        (*elements)++;
+        field = (char *)field + elem_size;
+    }
+
+    return -EINVAL;
+}
+
+static int obj_parse(struct json_obj *obj,
+                     const struct json_obj_descr *descr,
+                     size_t descr_len,
+                     void *val)
+{
+    struct json_obj_key_value kv;
+    int32_t decoded_fields = 0;
+    size_t i;
+    int ret;
+
+    while (!obj_next(obj, &kv)) {
+        if (kv.value.type == JSON_TOK_OBJECT_END) {
+            return decoded_fields;
+        }
+
+        for (i = 0; i < descr_len; i++) {
+            void *decode_field = (char *)val + descr[i].offset;
+
+            /* Field has been decoded already, skip */
+            if (decoded_fields & (1 << i)) {
+                continue;
+            }
+
+            /* Check if it's the i-th field */
+            if (kv.key_len != descr[i].field_name_len) {
+                continue;
+            }
+
+            if (memcmp(kv.key, descr[i].field_name, descr[i].field_name_len)) {
+                continue;
+            }
+
+            /* Store the decoded value */
+            ret = decode_value(obj, &descr[i], &kv.value, decode_field, val);
+            if (ret < 0) {
+                return ret;
+            }
+
+            decoded_fields |= 1 << i;
+            break;
+        }
+    }
+
+    return -EINVAL;
+}
+
+int json_obj_parse(char *payload,
+                   size_t len,
+                   const struct json_obj_descr *descr,
+                   size_t descr_len,
+                   void *val)
+{
+    struct json_obj obj;
+    int ret;
+
+    assert(descr_len < (sizeof(ret) * CHAR_BIT - 1));
+
+    ret = obj_init(&obj, payload, len);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return obj_parse(&obj, descr, descr_len, val);
+}
+
+static char escape_as(char chr)
+{
+    switch (chr) {
+    case '"':
+        return '"';
+    case '\\':
+        return '\\';
+    case '\b':
+        return 'b';
+    case '\f':
+        return 'f';
+    case '\n':
+        return 'n';
+    case '\r':
+        return 'r';
+    case '\t':
+        return 't';
+    }
+
+    return 0;
+}
+
+static int json_escape_internal(const char *str,
+                                json_append_bytes_t append_bytes,
+                                void *data)
+{
+    const char *cur;
+    int ret = 0;
+
+    for (cur = str; ret == 0 && *cur; cur++) {
+        char escaped = escape_as(*cur);
+
+        if (escaped) {
+            char bytes[2] = {'\\', escaped};
+
+            ret = append_bytes(bytes, 2, data);
+        } else {
+            ret = append_bytes(cur, 1, data);
+        }
+    }
+
+    return ret;
+}
+
+size_t json_calc_escaped_len(const char *str, size_t len)
+{
+    size_t escaped_len = len;
+    size_t pos;
+
+    for (pos = 0; pos < len; pos++) {
+        if (escape_as(str[pos])) {
+            escaped_len++;
+        }
+    }
+
+    return escaped_len;
+}
+
+ssize_t json_escape(char *str, size_t *len, size_t buf_size)
+{
+    char *next; /* Points after next character to escape. */
+    char *dest; /* Points after next place to write escaped character. */
+    size_t escaped_len = json_calc_escaped_len(str, *len);
+
+    if (escaped_len == *len) {
+        /*
+         * If no escape is necessary, there is nothing to do.
+         */
+        return 0;
+    }
+
+    if (escaped_len >= buf_size) {
+        return -ENOMEM;
+    }
+
+    /*
+     * By walking backwards in the buffer from the end positions
+     * of both the original and escaped strings, we avoid using
+     * extra space. Characters in the original string are
+     * overwritten only after they have already been escaped.
+     */
+    str[escaped_len] = '\0';
+    for (next = &str[*len], dest = &str[escaped_len]; next != str;) {
+        char next_c = *(--next);
+        char escape = escape_as(next_c);
+
+        if (escape) {
+            *(--dest) = escape;
+            *(--dest) = '\\';
+        } else {
+            *(--dest) = next_c;
+        }
+    }
+    *len = escaped_len;
+
+    return 0;
+}
+
+static int encode(const struct json_obj_descr *descr,
+                  const void *val,
+                  json_append_bytes_t append_bytes,
+                  void *data);
+
+static int arr_encode(const struct json_obj_descr *elem_descr,
+                      const void *field,
+                      const void *val,
+                      json_append_bytes_t append_bytes,
+                      void *data)
+{
+    ptrdiff_t elem_size = get_elem_size(elem_descr);
+    /*
+     * NOTE: Since an element descriptor's offset isn't meaningful
+     * (array elements occur at multiple offsets in `val'), we use
+     * its space in elem_descr to store the offset to the field
+     * containing the number of elements.
+     */
+    size_t n_elem = *(size_t *)((char *)val + elem_descr->offset);
+    size_t i;
+    int ret;
+
+    ret = append_bytes("[", 1, data);
+    if (ret < 0) {
+        return ret;
+    }
+
+    for (i = 0; i < n_elem; i++) {
+        /*
+         * Though "field" points at the next element in the
+         * array which we need to encode, the value in
+         * elem_descr->offset is actually the offset of the
+         * length field in the "parent" struct containing the
+         * array.
+         *
+         * To patch things up, we lie to encode() about where
+         * the field is by exactly the amount it will offset
+         * it. This is a size optimization for struct
+         * json_obj_descr: the alternative is to keep a
+         * separate field next to element_descr which is an
+         * offset to the length field in the parent struct,
+         * but that would add a size_t to every descriptor.
+         */
+        ret = encode(elem_descr, (char *)field - elem_descr->offset,
+                     append_bytes, data);
+        if (ret < 0) {
+            return ret;
+        }
+
+        if (i < n_elem - 1) {
+            ret = append_bytes(",", 1, data);
+            if (ret < 0) {
+                return ret;
+            }
+        }
+
+        field = (char *)field + elem_size;
+    }
+
+    return append_bytes("]", 1, data);
+}
+
+static int
+str_encode(const char **str, json_append_bytes_t append_bytes, void *data)
+{
+    int ret;
+
+    ret = append_bytes("\"", 1, data);
+    if (ret < 0) {
+        return ret;
+    }
+
+    ret = json_escape_internal(*str, append_bytes, data);
+    if (!ret) {
+        return append_bytes("\"", 1, data);
+    }
+
+    return ret;
+}
+
+static int
+num_encode(const int32_t *num, json_append_bytes_t append_bytes, void *data)
+{
+    char buf[3 * sizeof(int32_t)];
+    int ret;
+
+    ret = snprintf(buf, sizeof(buf), "%d", *num);
+    if (ret < 0) {
+        return ret;
+    }
+    if (ret >= (int)sizeof(buf)) {
+        return -ENOMEM;
+    }
+
+    return append_bytes(buf, (size_t)ret, data);
+}
+
+static int
+bool_encode(const bool *value, json_append_bytes_t append_bytes, void *data)
+{
+    if (*value) {
+        return append_bytes("true", 4, data);
+    }
+
+    return append_bytes("false", 5, data);
+}
+
+static int encode(const struct json_obj_descr *descr,
+                  const void *val,
+                  json_append_bytes_t append_bytes,
+                  void *data)
+{
+    void *ptr = (char *)val + descr->offset;
+
+    switch (descr->type) {
+    case JSON_TOK_FALSE:
+    case JSON_TOK_TRUE:
+        return bool_encode(ptr, append_bytes, data);
+    case JSON_TOK_STRING:
+        return str_encode(ptr, append_bytes, data);
+    case JSON_TOK_LIST_START:
+        return arr_encode(descr->array.element_descr, ptr, val, append_bytes, data);
+    case JSON_TOK_OBJECT_START:
+        return json_obj_encode(descr->object.sub_descr,
+                               descr->object.sub_descr_len, ptr, append_bytes,
+                               data);
+    case JSON_TOK_NUMBER:
+        return num_encode(ptr, append_bytes, data);
+    default:
+        return -EINVAL;
+    }
+}
+
+int json_obj_encode(const struct json_obj_descr *descr,
+                    size_t descr_len,
+                    const void *val,
+                    json_append_bytes_t append_bytes,
+                    void *data)
+{
+    size_t i;
+    int ret;
+
+    ret = append_bytes("{", 1, data);
+    if (ret < 0) {
+        return ret;
+    }
+
+    for (i = 0; i < descr_len; i++) {
+        ret =
+            str_encode((const char **)&descr[i].field_name, append_bytes, data);
+        if (ret < 0) {
+            return ret;
+        }
+
+        ret = append_bytes(":", 1, data);
+        if (ret < 0) {
+            return ret;
+        }
+
+        ret = encode(&descr[i], val, append_bytes, data);
+        if (ret < 0) {
+            return ret;
+        }
+
+        if (i < descr_len - 1) {
+            ret = append_bytes(",", 1, data);
+            if (ret < 0) {
+                return ret;
+            }
+        }
+    }
+
+    return append_bytes("}", 1, data);
+}
+
+struct appender {
+    char *buffer;
+    size_t used;
+    size_t size;
+};
+
+static int append_bytes_to_buf(const char *bytes, size_t len, void *data)
+{
+    struct appender *appender = data;
+
+    if (len > appender->size - appender->used) {
+        return -ENOMEM;
+    }
+
+    memcpy(appender->buffer + appender->used, bytes, len);
+    appender->used += len;
+    appender->buffer[appender->used] = '\0';
+
+    return 0;
+}
+
+int json_obj_encode_buf(const struct json_obj_descr *descr,
+                        size_t descr_len,
+                        const void *val,
+                        char *buffer,
+                        size_t buf_size)
+{
+    struct appender appender = {.buffer = buffer, .size = buf_size};
+
+    return json_obj_encode(descr, descr_len, val, append_bytes_to_buf,
+                           &appender);
+}
+
+static int
+measure_bytes(const char *bytes __attribute__((unused)), size_t len, void *data)
+{
+    ssize_t *total = data;
+
+    *total += (ssize_t)len;
+
+    return 0;
+}
+
+ssize_t json_calc_encoded_len(const struct json_obj_descr *descr,
+                              size_t descr_len,
+                              const void *val)
+{
+    ssize_t total = 0;
+    int ret;
+
+    ret = json_obj_encode(descr, descr_len, val, measure_bytes, &total);
+    if (ret < 0) {
+        return ret;
+    }
+
+    return total;
+}

+ 652 - 0
frameworks/C/lwan/src/json.h

@@ -0,0 +1,652 @@
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef ZEPHYR_INCLUDE_DATA_JSON_H_
+#define ZEPHYR_INCLUDE_DATA_JSON_H_
+
+#include <stddef.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define ROUND_UP(x, align)                                                     \
+    (((unsigned long)(x) + ((unsigned long)(align)-1)) &                       \
+     ~((unsigned long)(align)-1))
+
+/**
+ * @brief Structured Data
+ * @defgroup structured_data Structured Data
+ */
+
+/**
+ * @defgroup json JSON
+ * @ingroup structured_data
+ * @{
+ */
+
+enum json_tokens {
+    /* Before changing this enum, ensure that its maximum
+     * value is still within 7 bits. See comment next to the
+     * declaration of `type` in struct json_obj_descr.
+     */
+
+    JSON_TOK_NONE = '_',
+    JSON_TOK_OBJECT_START = '{',
+    JSON_TOK_OBJECT_END = '}',
+    JSON_TOK_LIST_START = '[',
+    JSON_TOK_LIST_END = ']',
+    JSON_TOK_STRING = '"',
+    JSON_TOK_COLON = ':',
+    JSON_TOK_COMMA = ',',
+    JSON_TOK_NUMBER = '0',
+    JSON_TOK_TRUE = 't',
+    JSON_TOK_FALSE = 'f',
+    JSON_TOK_NULL = 'n',
+    JSON_TOK_ERROR = '!',
+    JSON_TOK_EOF = '\0',
+};
+
+struct json_obj_descr {
+    const char *field_name;
+
+    /* Alignment can be 1, 2, 4, or 8.  The macros to create
+     * a struct json_obj_descr will store the alignment's
+     * power of 2 in order to keep this value in the 0-3 range
+     * and thus use only 2 bits.
+     */
+    uint32_t align_shift : 2;
+
+    /* 127 characters is more than enough for a field name. */
+    uint32_t field_name_len : 7;
+
+    /* Valid values here (enum json_tokens): JSON_TOK_STRING,
+     * JSON_TOK_NUMBER, JSON_TOK_TRUE, JSON_TOK_FALSE,
+     * JSON_TOK_OBJECT_START, JSON_TOK_LIST_START.  (All others
+     * ignored.) Maximum value is '}' (125), so this has to be 7 bits
+     * long.
+     */
+    uint32_t type : 7;
+
+    /* 65535 bytes is more than enough for many JSON payloads. */
+    uint32_t offset : 16;
+
+    union {
+        struct {
+            const struct json_obj_descr *sub_descr;
+            size_t sub_descr_len;
+        } object;
+        struct {
+            const struct json_obj_descr *element_descr;
+            size_t n_elements;
+        } array;
+    };
+};
+
+/**
+ * @brief Function pointer type to append bytes to a buffer while
+ * encoding JSON data.
+ *
+ * @param bytes Contents to write to the output
+ * @param len Number of bytes in @param bytes to append to output
+ * @param data User-provided pointer
+ *
+ * @return This callback function should return a negative number on
+ * error (which will be propagated to the return value of
+ * json_obj_encode()), or 0 on success.
+ */
+typedef int (*json_append_bytes_t)(const char *bytes, size_t len, void *data);
+
+#define Z_ALIGN_SHIFT(type)                                                    \
+    (__alignof__(type) == 1                                                    \
+         ? 0                                                                   \
+         : __alignof__(type) == 2 ? 1 : __alignof__(type) == 4 ? 2 : 3)
+
+/**
+ * @brief Helper macro to declare a descriptor for supported primitive
+ * values.
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param field_name_ Field name in the struct
+ *
+ * @param type_ Token type for JSON value corresponding to a primitive
+ * type. Must be one of: JSON_TOK_STRING for strings, JSON_TOK_NUMBER
+ * for numbers, JSON_TOK_TRUE (or JSON_TOK_FALSE) for booleans.
+ *
+ * Here's an example of use:
+ *
+ *     struct foo {
+ *         int some_int;
+ *     };
+ *
+ *     struct json_obj_descr foo[] = {
+ *         JSON_OBJ_DESCR_PRIM(struct foo, some_int, JSON_TOK_NUMBER),
+ *     };
+ */
+#define JSON_OBJ_DESCR_PRIM(struct_, field_name_, type_)                       \
+    {                                                                          \
+        .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_),   \
+        .field_name_len = sizeof(#field_name_) - 1, .type = type_,             \
+        .offset = offsetof(struct_, field_name_),                              \
+    }
+
+/**
+ * @brief Helper macro to declare a descriptor for an object value
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param field_name_ Field name in the struct
+ *
+ * @param sub_descr_ Array of json_obj_descr describing the subobject
+ *
+ * Here's an example of use:
+ *
+ *      struct nested {
+ *          int foo;
+ *          struct {
+ *             int baz;
+ *          } bar;
+ *      };
+ *
+ *      struct json_obj_descr nested_bar[] = {
+ *          { ... declare bar.baz descriptor ... },
+ *      };
+ *      struct json_obj_descr nested[] = {
+ *          { ... declare foo descriptor ... },
+ *          JSON_OBJ_DESCR_OBJECT(struct nested, bar, nested_bar),
+ *      };
+ */
+#define JSON_OBJ_DESCR_OBJECT(struct_, field_name_, sub_descr_)                \
+    {                                                                          \
+        .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_),   \
+        .field_name_len = (sizeof(#field_name_) - 1),                          \
+        .type = JSON_TOK_OBJECT_START,                                         \
+        .offset = offsetof(struct_, field_name_),                              \
+        .object = {                                                            \
+            .sub_descr = sub_descr_,                                           \
+            .sub_descr_len = ARRAY_SIZE(sub_descr_),                           \
+        },                                                                     \
+    }
+
+/**
+ * @brief Helper macro to declare a descriptor for an array of primitives
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param field_name_ Field name in the struct
+ *
+ * @param max_len_ Maximum number of elements in array
+ *
+ * @param len_field_ Field name in the struct for the number of elements
+ * in the array
+ *
+ * @param elem_type_ Element type, must be a primitive type
+ *
+ * Here's an example of use:
+ *
+ *      struct example {
+ *          int foo[10];
+ *          size_t foo_len;
+ *      };
+ *
+ *      struct json_obj_descr array[] = {
+ *           JSON_OBJ_DESCR_ARRAY(struct example, foo, 10, foo_len,
+ *                                JSON_TOK_NUMBER)
+ *      };
+ */
+#define JSON_OBJ_DESCR_ARRAY(struct_, field_name_, max_len_, len_field_,       \
+                             elem_type_)                                       \
+    {                                                                          \
+        .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_),   \
+        .field_name_len = sizeof(#field_name_) - 1,                            \
+        .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \
+        .array = {                                                             \
+            .element_descr =                                                   \
+                &(struct json_obj_descr){                                      \
+                    .align_shift = Z_ALIGN_SHIFT(struct_),                     \
+                    .type = elem_type_,                                        \
+                    .offset = offsetof(struct_, len_field_),                   \
+                },                                                             \
+            .n_elements = (max_len_),                                          \
+        },                                                                     \
+    }
+
+/**
+ * @brief Helper macro to declare a descriptor for an array of objects
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param field_name_ Field name in the struct containing the array
+ *
+ * @param max_len_ Maximum number of elements in the array
+ *
+ * @param len_field_ Field name in the struct for the number of elements
+ * in the array
+ *
+ * @param elem_descr_ Element descriptor, pointer to a descriptor array
+ *
+ * @param elem_descr_len_ Number of elements in elem_descr_
+ *
+ * Here's an example of use:
+ *
+ *      struct person_height {
+ *          const char *name;
+ *          int height;
+ *      };
+ *
+ *      struct people_heights {
+ *          struct person_height heights[10];
+ *          size_t heights_len;
+ *      };
+ *
+ *      struct json_obj_descr person_height_descr[] = {
+ *           JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING),
+ *           JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER),
+ *      };
+ *
+ *      struct json_obj_descr array[] = {
+ *           JSON_OBJ_DESCR_OBJ_ARRAY(struct people_heights, heights, 10,
+ *                                    heights_len, person_height_descr,
+ *                                    ARRAY_SIZE(person_height_descr)),
+ *      };
+ */
+#define JSON_OBJ_DESCR_OBJ_ARRAY(struct_, field_name_, max_len_, len_field_,   \
+                                 elem_descr_, elem_descr_len_)                 \
+    {                                                                          \
+        .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_),   \
+        .field_name_len = sizeof(#field_name_) - 1,                            \
+        .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \
+        .array = {                                                             \
+            .element_descr =                                                   \
+                &(struct json_obj_descr){                                      \
+                    .align_shift = Z_ALIGN_SHIFT(struct_),                     \
+                    .type = JSON_TOK_OBJECT_START,                             \
+                    .offset = offsetof(struct_, len_field_),                   \
+                    .object =                                                  \
+                        {                                                      \
+                            .sub_descr = elem_descr_,                          \
+                            .sub_descr_len = elem_descr_len_,                  \
+                        },                                                     \
+                },                                                             \
+            .n_elements = (max_len_),                                          \
+        },                                                                     \
+    }
+
+/**
+ * @brief Helper macro to declare a descriptor for an array of array
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param field_name_ Field name in the struct containing the array
+ *
+ * @param max_len_ Maximum number of elements in the array
+ *
+ * @param len_field_ Field name in the struct for the number of elements
+ * in the array
+ *
+ * @param elem_descr_ Element descriptor, pointer to a descriptor array
+ *
+ * @param elem_descr_len_ Number of elements in elem_descr_
+ *
+ * Here's an example of use:
+ *
+ *      struct person_height {
+ *          const char *name;
+ *          int height;
+ *      };
+ *
+ *      struct person_heights_array {
+ *          struct person_height heights;
+ *      }
+ *
+ *      struct people_heights {
+ *          struct person_height_array heights[10];
+ *          size_t heights_len;
+ *      };
+ *
+ *      struct json_obj_descr person_height_descr[] = {
+ *          JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING),
+ *          JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER),
+ *      };
+ *
+ *      struct json_obj_descr person_height_array_descr[] = {
+ *          JSON_OBJ_DESCR_OBJECT(struct person_heights_array,
+ *                                heights, person_heigth_descr),
+ *      };
+ *
+ *      struct json_obj_descr array_array[] = {
+ *           JSON_OBJ_DESCR_ARRAY_ARRAY(struct people_heights, heights, 10,
+ *                                      heights_len, person_height_array_descr,
+ *                                      ARRAY_SIZE(person_height_array_descr)),
+ *      };
+ */
+#define JSON_OBJ_DESCR_ARRAY_ARRAY(struct_, field_name_, max_len_, len_field_, \
+                                   elem_descr_, elem_descr_len_)               \
+    {                                                                          \
+        .field_name = (#field_name_), .align_shift = Z_ALIGN_SHIFT(struct_),   \
+        .field_name_len = sizeof(#field_name_) - 1,                            \
+        .type = JSON_TOK_LIST_START, .offset = offsetof(struct_, field_name_), \
+        .array = {                                                             \
+            .element_descr =                                                   \
+                &(struct json_obj_descr){                                      \
+                    .align_shift = Z_ALIGN_SHIFT(struct_),                     \
+                    .type = JSON_TOK_LIST_START,                               \
+                    .offset = offsetof(struct_, len_field_),                   \
+                    .object =                                                  \
+                        {                                                      \
+                            .sub_descr = elem_descr_,                          \
+                            .sub_descr_len = elem_descr_len_,                  \
+                        },                                                     \
+                },                                                             \
+            .n_elements = (max_len_),                                          \
+        },                                                                     \
+    }
+
+/**
+ * @brief Variant of JSON_OBJ_DESCR_PRIM that can be used when the
+ *        structure and JSON field names differ.
+ *
+ * This is useful when the JSON field is not a valid C identifier.
+ *
+ * @param struct_ Struct packing the values.
+ *
+ * @param json_field_name_ String, field name in JSON strings
+ *
+ * @param struct_field_name_ Field name in the struct
+ *
+ * @param type_ Token type for JSON value corresponding to a primitive
+ * type.
+ *
+ * @see JSON_OBJ_DESCR_PRIM
+ */
+#define JSON_OBJ_DESCR_PRIM_NAMED(struct_, json_field_name_,                   \
+                                  struct_field_name_, type_)                   \
+    {                                                                          \
+        .field_name = (json_field_name_),                                      \
+        .align_shift = Z_ALIGN_SHIFT(struct_),                                 \
+        .field_name_len = sizeof(json_field_name_) - 1, .type = type_,         \
+        .offset = offsetof(struct_, struct_field_name_),                       \
+    }
+
+/**
+ * @brief Variant of JSON_OBJ_DESCR_OBJECT that can be used when the
+ *        structure and JSON field names differ.
+ *
+ * This is useful when the JSON field is not a valid C identifier.
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param json_field_name_ String, field name in JSON strings
+ *
+ * @param struct_field_name_ Field name in the struct
+ *
+ * @param sub_descr_ Array of json_obj_descr describing the subobject
+ *
+ * @see JSON_OBJ_DESCR_OBJECT
+ */
+#define JSON_OBJ_DESCR_OBJECT_NAMED(struct_, json_field_name_,                 \
+                                    struct_field_name_, sub_descr_)            \
+    {                                                                          \
+        .field_name = (json_field_name_),                                      \
+        .align_shift = Z_ALIGN_SHIFT(struct_),                                 \
+        .field_name_len = (sizeof(json_field_name_) - 1),                      \
+        .type = JSON_TOK_OBJECT_START,                                         \
+        .offset = offsetof(struct_, struct_field_name_),                       \
+        .object = {                                                            \
+            .sub_descr = sub_descr_,                                           \
+            .sub_descr_len = ARRAY_SIZE(sub_descr_),                           \
+        },                                                                     \
+    }
+
+/**
+ * @brief Variant of JSON_OBJ_DESCR_ARRAY that can be used when the
+ *        structure and JSON field names differ.
+ *
+ * This is useful when the JSON field is not a valid C identifier.
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param json_field_name_ String, field name in JSON strings
+ *
+ * @param struct_field_name_ Field name in the struct
+ *
+ * @param max_len_ Maximum number of elements in array
+ *
+ * @param len_field_ Field name in the struct for the number of elements
+ * in the array
+ *
+ * @param elem_type_ Element type, must be a primitive type
+ *
+ * @see JSON_OBJ_DESCR_ARRAY
+ */
+#define JSON_OBJ_DESCR_ARRAY_NAMED(struct_, json_field_name_,                  \
+                                   struct_field_name_, max_len_, len_field_,   \
+                                   elem_type_)                                 \
+    {                                                                          \
+        .field_name = (json_field_name_),                                      \
+        .align_shift = Z_ALIGN_SHIFT(struct_),                                 \
+        .field_name_len = sizeof(json_field_name_) - 1,                        \
+        .type = JSON_TOK_LIST_START,                                           \
+        .offset = offsetof(struct_, struct_field_name_),                       \
+        .array = {                                                             \
+            .element_descr =                                                   \
+                &(struct json_obj_descr){                                      \
+                    .align_shift = Z_ALIGN_SHIFT(struct_),                     \
+                    .type = elem_type_,                                        \
+                    .offset = offsetof(struct_, len_field_),                   \
+                },                                                             \
+            .n_elements = (max_len_),                                          \
+        },                                                                     \
+    }
+
+/**
+ * @brief Variant of JSON_OBJ_DESCR_OBJ_ARRAY that can be used when
+ *        the structure and JSON field names differ.
+ *
+ * This is useful when the JSON field is not a valid C identifier.
+ *
+ * @param struct_ Struct packing the values
+ *
+ * @param json_field_name_ String, field name of the array in JSON strings
+ *
+ * @param struct_field_name_ Field name in the struct containing the array
+ *
+ * @param max_len_ Maximum number of elements in the array
+ *
+ * @param len_field_ Field name in the struct for the number of elements
+ * in the array
+ *
+ * @param elem_descr_ Element descriptor, pointer to a descriptor array
+ *
+ * @param elem_descr_len_ Number of elements in elem_descr_
+ *
+ * Here's an example of use:
+ *
+ *      struct person_height {
+ *          const char *name;
+ *          int height;
+ *      };
+ *
+ *      struct people_heights {
+ *          struct person_height heights[10];
+ *          size_t heights_len;
+ *      };
+ *
+ *      struct json_obj_descr person_height_descr[] = {
+ *           JSON_OBJ_DESCR_PRIM(struct person_height, name, JSON_TOK_STRING),
+ *           JSON_OBJ_DESCR_PRIM(struct person_height, height, JSON_TOK_NUMBER),
+ *      };
+ *
+ *      struct json_obj_descr array[] = {
+ *           JSON_OBJ_DESCR_OBJ_ARRAY_NAMED(struct people_heights,
+ *                                          "people-heights", heights,
+ *                                          10, heights_len,
+ *                                          person_height_descr,
+ *                                          ARRAY_SIZE(person_height_descr)),
+ *      };
+ */
+#define JSON_OBJ_DESCR_OBJ_ARRAY_NAMED(                                        \
+    struct_, json_field_name_, struct_field_name_, max_len_, len_field_,       \
+    elem_descr_, elem_descr_len_)                                              \
+    {                                                                          \
+        .field_name = json_field_name_, .align_shift = Z_ALIGN_SHIFT(struct_), \
+        .field_name_len = sizeof(json_field_name_) - 1,                        \
+        .type = JSON_TOK_LIST_START,                                           \
+        .offset = offsetof(struct_, struct_field_name_),                       \
+        .element_descr =                                                       \
+            &(struct json_obj_descr){                                          \
+                .align_shift = Z_ALIGN_SHIFT(struct_),                         \
+                .type = JSON_TOK_OBJECT_START,                                 \
+                .offset = offsetof(struct_, len_field_),                       \
+                .object =                                                      \
+                    {                                                          \
+                        .sub_descr = elem_descr_,                              \
+                        .sub_descr_len = elem_descr_len_,                      \
+                    },                                                         \
+            },                                                                 \
+        .n_elements = (max_len_),                                              \
+    }
+
+/**
+ * @brief Parses the JSON-encoded object pointer to by @a json, with
+ * size @a len, according to the descriptor pointed to by @a descr.
+ * Values are stored in a struct pointed to by @a val.  Set up the
+ * descriptor like this:
+ *
+ *    struct s { int foo; char *bar; }
+ *    struct json_obj_descr descr[] = {
+ *       JSON_OBJ_DESCR_PRIM(struct s, foo, JSON_TOK_NUMBER),
+ *       JSON_OBJ_DESCR_PRIM(struct s, bar, JSON_TOK_STRING),
+ *    };
+ *
+ * Since this parser is designed for machine-to-machine communications, some
+ * liberties were taken to simplify the design:
+ * (1) strings are not unescaped (but only valid escape sequences are
+ * accepted);
+ * (2) no UTF-8 validation is performed; and
+ * (3) only integer numbers are supported (no strtod() in the minimal libc).
+ *
+ * @param json Pointer to JSON-encoded value to be parsed
+ *
+ * @param len Length of JSON-encoded value
+ *
+ * @param descr Pointer to the descriptor array
+ *
+ * @param descr_len Number of elements in the descriptor array. Must be less
+ * than 31 due to implementation detail reasons (if more fields are
+ * necessary, use two descriptors)
+ *
+ * @param val Pointer to the struct to hold the decoded values
+ *
+ * @return < 0 if error, bitmap of decoded fields on success (bit 0
+ * is set if first field in the descriptor has been properly decoded, etc).
+ */
+int json_obj_parse(char *json,
+                   size_t len,
+                   const struct json_obj_descr *descr,
+                   size_t descr_len,
+                   void *val);
+
+/**
+ * @brief Escapes the string so it can be used to encode JSON objects
+ *
+ * @param str The string to escape; the escape string is stored the
+ * buffer pointed to by this parameter
+ *
+ * @param len Points to a size_t containing the size before and after
+ * the escaping process
+ *
+ * @param buf_size The size of buffer str points to
+ *
+ * @return 0 if string has been escaped properly, or -ENOMEM if there
+ * was not enough space to escape the buffer
+ */
+ssize_t json_escape(char *str, size_t *len, size_t buf_size);
+
+/**
+ * @brief Calculates the JSON-escaped string length
+ *
+ * @param str The string to analyze
+ *
+ * @param len String size
+ *
+ * @return The length str would have if it were escaped
+ */
+size_t json_calc_escaped_len(const char *str, size_t len);
+
+/**
+ * @brief Calculates the string length to fully encode an object
+ *
+ * @param descr Pointer to the descriptor array
+ *
+ * @param descr_len Number of elements in the descriptor array
+ *
+ * @param val Struct holding the values
+ *
+ * @return Number of bytes necessary to encode the values if >0,
+ * an error code is returned.
+ */
+ssize_t json_calc_encoded_len(const struct json_obj_descr *descr,
+                              size_t descr_len,
+                              const void *val);
+
+/**
+ * @brief Encodes an object in a contiguous memory location
+ *
+ * @param descr Pointer to the descriptor array
+ *
+ * @param descr_len Number of elements in the descriptor array
+ *
+ * @param val Struct holding the values
+ *
+ * @param buffer Buffer to store the JSON data
+ *
+ * @param buf_size Size of buffer, in bytes, with space for the terminating
+ * NUL character
+ *
+ * @return 0 if object has been successfully encoded. A negative value
+ * indicates an error (as defined on errno.h).
+ */
+int json_obj_encode_buf(const struct json_obj_descr *descr,
+                        size_t descr_len,
+                        const void *val,
+                        char *buffer,
+                        size_t buf_size);
+
+/**
+ * @brief Encodes an object using an arbitrary writer function
+ *
+ * @param descr Pointer to the descriptor array
+ *
+ * @param descr_len Number of elements in the descriptor array
+ *
+ * @param val Struct holding the values
+ *
+ * @param append_bytes Function to append bytes to the output
+ *
+ * @param data Data pointer to be passed to the append_bytes callback
+ * function.
+ *
+ * @return 0 if object has been successfully encoded. A negative value
+ * indicates an error.
+ */
+int json_obj_encode(const struct json_obj_descr *descr,
+                    size_t descr_len,
+                    const void *val,
+                    json_append_bytes_t append_bytes,
+                    void *data);
+
+#ifdef __cplusplus
+}
+#endif
+
+/**
+ * @}
+ */
+#endif /* ZEPHYR_INCLUDE_DATA_JSON_H_ */

+ 420 - 0
frameworks/C/lwan/src/techempower.c

@@ -0,0 +1,420 @@
+/*
+ * lwan - simple web server
+ * Copyright (c) 2014 Leandro A. F. Pereira <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
+ * USA.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "lwan.h"
+#include "lwan-config.h"
+#include "lwan-template.h"
+
+#include "database.h"
+#include "json.h"
+
+enum db_connect_type { DB_CONN_MYSQL, DB_CONN_SQLITE };
+
+struct db_connection_params {
+    enum db_connect_type type;
+    union {
+        struct {
+            const char *user;
+            const char *password;
+            const char *database;
+            const char *hostname;
+        } mysql;
+        struct {
+            const char *path;
+            const char **pragmas;
+        } sqlite;
+    };
+};
+
+static struct db_connection_params db_connection_params;
+
+static struct db *get_db(void)
+{
+    static __thread struct db *database;
+
+    if (!database) {
+        switch (db_connection_params.type) {
+        case DB_CONN_MYSQL:
+            database = db_connect_mysql(db_connection_params.mysql.hostname,
+                                        db_connection_params.mysql.user,
+                                        db_connection_params.mysql.password,
+                                        db_connection_params.mysql.database);
+            break;
+        case DB_CONN_SQLITE:
+            database = db_connect_sqlite(db_connection_params.sqlite.path, true,
+                                         db_connection_params.sqlite.pragmas);
+            break;
+        }
+        if (!database)
+            lwan_status_critical("Could not connect to the database");
+    }
+
+    return database;
+}
+
+static const char hello_world[] = "Hello, World!";
+static const char random_number_query[] =
+    "SELECT randomNumber FROM world WHERE id=?";
+
+struct Fortune {
+    struct {
+        coro_function_t generator;
+
+        int id;
+        char *message;
+    } item;
+};
+
+DEFINE_ARRAY_TYPE_INLINEFIRST(fortune_array, struct Fortune)
+
+static const char fortunes_template_str[] =
+    "<!DOCTYPE html>"
+    "<html>"
+    "<head><title>Fortunes</title></head>"
+    "<body>"
+    "<table>"
+    "<tr><th>id</th><th>message</th></tr>"
+    "{{#item}}"
+    "<tr><td>{{item.id}}</td><td>{{item.message}}</td></tr>"
+    "{{/item}}"
+    "</table>"
+    "</body>"
+    "</html>";
+
+static int fortune_list_generator(struct coro *coro, void *data);
+
+#undef TPL_STRUCT
+#define TPL_STRUCT struct Fortune
+static const struct lwan_var_descriptor fortune_item_desc[] = {
+    TPL_VAR_INT(item.id),
+    TPL_VAR_STR_ESCAPE(item.message),
+    TPL_VAR_SENTINEL,
+};
+
+static const struct lwan_var_descriptor fortune_desc[] = {
+    TPL_VAR_SEQUENCE(item, fortune_list_generator, fortune_item_desc),
+    TPL_VAR_SENTINEL,
+};
+
+static struct lwan_tpl *fortune_tpl;
+
+struct hello_world_json {
+    const char *message;
+};
+static const struct json_obj_descr hello_world_json_desc[] = {
+    JSON_OBJ_DESCR_PRIM(struct hello_world_json, message, JSON_TOK_STRING),
+};
+
+struct db_json {
+    int id;
+    int randomNumber;
+};
+static const struct json_obj_descr db_json_desc[] = {
+    JSON_OBJ_DESCR_PRIM(struct db_json, id, JSON_TOK_NUMBER),
+    JSON_OBJ_DESCR_PRIM(struct db_json, randomNumber, JSON_TOK_NUMBER),
+};
+
+struct queries_json {
+    struct db_json queries[500];
+    size_t queries_len;
+};
+static const struct json_obj_descr queries_json_desc[] = {
+    JSON_OBJ_DESCR_OBJ_ARRAY(struct queries_json,
+                             queries,
+                             500,
+                             queries_len,
+                             db_json_desc,
+                             N_ELEMENTS(db_json_desc)),
+};
+
+static int append_to_strbuf(const char *bytes, size_t len, void *data)
+{
+    struct lwan_strbuf *strbuf = data;
+
+    return lwan_strbuf_append_str(strbuf, bytes, len) ? 0 : -EINVAL;
+}
+
+static enum lwan_http_status json_response(struct lwan_response *response,
+                                           const struct json_obj_descr *descr,
+                                           size_t descr_len,
+                                           const void *data)
+{
+    if (json_obj_encode(descr, descr_len, data, append_to_strbuf,
+                        response->buffer) < 0)
+        return HTTP_INTERNAL_ERROR;
+
+    response->mime_type = "application/json";
+    return HTTP_OK;
+}
+
+LWAN_HANDLER(json)
+{
+    struct hello_world_json j = {.message = hello_world};
+
+    return json_response(response, hello_world_json_desc,
+                         N_ELEMENTS(hello_world_json_desc), &j);
+}
+
+static bool db_query(struct db_stmt *stmt,
+                     struct db_row rows[],
+                     struct db_row results[],
+                     struct db_json *out)
+{
+    int id = rand() % 10000;
+
+    rows[0].u.i = id;
+
+    if (UNLIKELY(!db_stmt_bind(stmt, rows, 1)))
+        return false;
+
+    if (UNLIKELY(!db_stmt_step(stmt, results)))
+        return false;
+
+    out->id = id;
+    out->randomNumber = results[0].u.i;
+
+    return true;
+}
+
+LWAN_HANDLER(db)
+{
+    struct db_row rows[1] = {{.kind = 'i'}};
+    struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}};
+    struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
+                                           sizeof(random_number_query) - 1);
+    struct db_json db_json;
+
+    if (UNLIKELY(!stmt)) {
+        lwan_status_debug("preparing stmt failed");
+        return HTTP_INTERNAL_ERROR;
+    }
+
+    bool queried = db_query(stmt, rows, results, &db_json);
+
+    db_stmt_finalize(stmt);
+
+    if (!queried)
+        return HTTP_INTERNAL_ERROR;
+
+    return json_response(response, db_json_desc, N_ELEMENTS(db_json_desc),
+                         &db_json);
+}
+
+LWAN_HANDLER(queries)
+{
+    enum lwan_http_status ret = HTTP_INTERNAL_ERROR;
+    const char *queries_str = lwan_request_get_query_param(request, "queries");
+    long queries;
+
+    if (LIKELY(queries_str)) {
+        queries = parse_long(queries_str, -1);
+        if (UNLIKELY(queries <= 0))
+            queries = 1;
+        else if (UNLIKELY(queries > 500))
+            queries = 500;
+    } else {
+        queries = 1;
+    }
+
+    struct db_stmt *stmt = db_prepare_stmt(get_db(), random_number_query,
+                                           sizeof(random_number_query) - 1);
+    if (UNLIKELY(!stmt))
+        return HTTP_INTERNAL_ERROR;
+
+    struct queries_json qj = {.queries_len = queries};
+    struct db_row rows[1] = {{.kind = 'i'}};
+    struct db_row results[] = {{.kind = 'i'}, {.kind = '\0'}};
+    for (long i = 0; i < queries; i++) {
+        if (!db_query(stmt, rows, results, &qj.queries[i]))
+            goto out;
+    }
+
+    ret = json_response(response, queries_json_desc,
+                        N_ELEMENTS(queries_json_desc), &qj);
+
+out:
+    db_stmt_finalize(stmt);
+
+    return ret;
+}
+
+LWAN_HANDLER(plaintext)
+{
+    lwan_strbuf_set_static(response->buffer, hello_world,
+                           sizeof(hello_world) - 1);
+
+    response->mime_type = "text/plain";
+    return HTTP_OK;
+}
+
+static int fortune_compare(const void *a, const void *b)
+{
+    const struct Fortune *fortune_a = (const struct Fortune *)a;
+    const struct Fortune *fortune_b = (const struct Fortune *)b;
+    size_t a_len = strlen(fortune_a->item.message);
+    size_t b_len = strlen(fortune_b->item.message);
+
+    if (!a_len || !b_len)
+        return a_len > b_len;
+
+    size_t min_len = a_len < b_len ? a_len : b_len;
+
+    int cmp = memcmp(fortune_a->item.message, fortune_b->item.message, min_len);
+    return cmp == 0 ? -(ssize_t)min_len : cmp;
+}
+
+static bool append_fortune(struct coro *coro,
+                           struct fortune_array *fortunes,
+                           int id,
+                           const char *message)
+{
+    struct Fortune *fortune;
+    char *message_copy;
+
+    message_copy = coro_strdup(coro, message);
+    if (UNLIKELY(!message_copy))
+        return false;
+
+    fortune = fortune_array_append(fortunes);
+    if (UNLIKELY(!fortune))
+        return false;
+
+    fortune->item.id = id;
+    fortune->item.message = message_copy;
+
+    return true;
+}
+
+static int fortune_list_generator(struct coro *coro, void *data)
+{
+    static const char fortune_query[] = "SELECT * FROM Fortune";
+    char fortune_buffer[256];
+    struct Fortune *fortune = data;
+    struct fortune_array fortunes;
+    struct db_stmt *stmt;
+
+    stmt = db_prepare_stmt(get_db(), fortune_query, sizeof(fortune_query) - 1);
+    if (UNLIKELY(!stmt))
+        return 0;
+
+    fortune_array_init(&fortunes);
+
+    struct db_row results[] = {{.kind = 'i'},
+                               {.kind = 's',
+                                .u.s = fortune_buffer,
+                                .buffer_length = sizeof(fortune_buffer)},
+                               {.kind = '\0'}};
+    while (db_stmt_step(stmt, results)) {
+        if (!append_fortune(coro, &fortunes, results[0].u.i, results[1].u.s))
+            goto out;
+    }
+
+    if (!append_fortune(coro, &fortunes, 0,
+                        "Additional fortune added at request time."))
+        goto out;
+
+    fortune_array_sort(&fortunes, fortune_compare);
+
+    struct Fortune *iter;
+    LWAN_ARRAY_FOREACH (&fortunes, iter) {
+        fortune->item.id = iter->item.id;
+        fortune->item.message = iter->item.message;
+        coro_yield(coro, 1);
+    }
+
+out:
+    fortune_array_reset(&fortunes);
+    db_stmt_finalize(stmt);
+    return 0;
+}
+
+LWAN_HANDLER(fortunes)
+{
+    struct Fortune fortune;
+
+    if (UNLIKELY(!lwan_tpl_apply_with_buffer(fortune_tpl, response->buffer,
+                                             &fortune)))
+        return HTTP_INTERNAL_ERROR;
+
+    response->mime_type = "text/html; charset=UTF-8";
+    return HTTP_OK;
+}
+
+int main(void)
+{
+    static const struct lwan_url_map url_map[] = {
+        {.prefix = "/json", .handler = LWAN_HANDLER_REF(json)},
+        {.prefix = "/db", .handler = LWAN_HANDLER_REF(db)},
+        {.prefix = "/queries", .handler = LWAN_HANDLER_REF(queries)},
+        {.prefix = "/plaintext", .handler = LWAN_HANDLER_REF(plaintext)},
+        {.prefix = "/fortunes", .handler = LWAN_HANDLER_REF(fortunes)},
+        {.prefix = NULL},
+    };
+    struct lwan l;
+
+    lwan_init(&l);
+
+    srand((unsigned int)time(NULL));
+
+    if (getenv("USE_MYSQL")) {
+        db_connection_params = (struct db_connection_params) {
+            .type = DB_CONN_MYSQL,
+            .mysql.user = getenv("MYSQL_USER"),
+            .mysql.password = getenv("MYSQL_PASS"),
+            .mysql.hostname = getenv("MYSQL_HOST"),
+            .mysql.database = getenv("MYSQL_DB")
+        };
+
+        if (!db_connection_params.mysql.user)
+            lwan_status_critical("No MySQL user provided");
+        if (!db_connection_params.mysql.password)
+            lwan_status_critical("No MySQL password provided");
+        if (!db_connection_params.mysql.hostname)
+            lwan_status_critical("No MySQL hostname provided");
+        if (!db_connection_params.mysql.database)
+            lwan_status_critical("No MySQL database provided");
+    } else {
+        const char *pragmas[] = {"PRAGMA mmap_size=44040192",
+                                 "PRAGMA journal_mode=OFF",
+                                 "PRAGMA locking_mode=EXCLUSIVE", NULL};
+        db_connection_params = (struct db_connection_params) {
+            .type = DB_CONN_SQLITE,
+            .sqlite.path = "techempower.db",
+            .sqlite.pragmas = pragmas,
+        };
+    }
+
+    fortune_tpl = lwan_tpl_compile_string_full(
+        fortunes_template_str, fortune_desc, LWAN_TPL_FLAG_CONST_TEMPLATE);
+    if (!fortune_tpl)
+        lwan_status_critical("Could not compile fortune templates");
+
+    lwan_set_url_map(&l, url_map);
+    lwan_main_loop(&l);
+
+    lwan_tpl_free(fortune_tpl);
+    lwan_shutdown(&l);
+
+    return 0;
+}

+ 2 - 0
frameworks/C/lwan/techempower.conf

@@ -0,0 +1,2 @@
+listener *:8080 {
+}