Quiz4: HTML + JS + JSON
This commit is contained in:
parent
2e0aaf6688
commit
7b3ddf5cc9
4
cmd.txt
4
cmd.txt
@ -8,3 +8,7 @@ doas mkdir /var/www/tmp
|
||||
doas chmod 1777 /var/www/tmp
|
||||
cc -static -g -W -Wall -o build/example4 examples/example4.c -I/usr/local/include -L/usr/local/lib -lkcgi -lkcgihtml -lz -lsqlite3
|
||||
doas install -o www -g www -m 0500 build/example4 /var/www/cgi-bin/
|
||||
|
||||
cc -static -g -W -Wall -o build/quiz4 quiz4.c -I/usr/local/include -L/usr/local/lib -lkcgi -lkcgihtml -lkcgijson -lz -lsqlite3
|
||||
doas install -o www -g www -m 0500 build/quiz4 /var/www/cgi-bin/
|
||||
doas install -o www -g www -m 0500 quiz4.js /var/www/htdocs/
|
||||
|
368
quiz4.c
Normal file
368
quiz4.c
Normal file
@ -0,0 +1,368 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Kristaps Dzonsons <kristaps@bsd.lv>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <kcgi.h>
|
||||
#include <kcgihtml.h>
|
||||
#include <kcgijson.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
enum page {
|
||||
PAGE_INDEX, /* /index or just / */
|
||||
PAGE_JSON, /* /quiz4.json */
|
||||
PAGE__MAX
|
||||
};
|
||||
|
||||
static const char *JSON_COUNT = "count";
|
||||
|
||||
static const char *const pages[PAGE__MAX] = {
|
||||
"index", /* PAGE_INDEX */
|
||||
"quiz4", /* PAGE_JSON */
|
||||
};
|
||||
|
||||
typedef void (*disp)(struct kreq *);
|
||||
|
||||
static void sendjson(struct kreq *req);
|
||||
static void sendindex(struct kreq *req);
|
||||
|
||||
static const disp disps[PAGE__MAX] = {
|
||||
sendindex, /* PAGE_INDEX */
|
||||
sendjson, /* PAGE_JSON */
|
||||
};
|
||||
|
||||
/*
|
||||
* Generic routine to prime the HTTP document.
|
||||
*/
|
||||
static void
|
||||
resp_open(struct kreq *req, enum khttp http)
|
||||
{
|
||||
|
||||
khttp_head(req, kresps[KRESP_STATUS],
|
||||
"%s", khttps[http]);
|
||||
khttp_head(req, kresps[KRESP_CONTENT_TYPE],
|
||||
"%s", kmimetypes[req->mime]);
|
||||
khttp_body(req);
|
||||
}
|
||||
|
||||
/*
|
||||
* When the database is busy or locked, sleep for a random amount of
|
||||
* time before continuing.
|
||||
* We could put all sorts of heuristics in here to back off, but we do
|
||||
* something really simple.
|
||||
*/
|
||||
static void
|
||||
db_sleep(size_t attempt)
|
||||
{
|
||||
|
||||
if (attempt < 10)
|
||||
usleep(arc4random_uniform(100000));
|
||||
else
|
||||
usleep(arc4random_uniform(500000));
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep trying to open the database forever.
|
||||
*/
|
||||
static sqlite3 *
|
||||
db_open(void)
|
||||
{
|
||||
size_t attempt;
|
||||
sqlite3 *db;
|
||||
int rc;
|
||||
|
||||
attempt = 0;
|
||||
again:
|
||||
rc = sqlite3_open("/tmp/example4.db", &db);
|
||||
if (SQLITE_BUSY == rc) {
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_LOCKED == rc) {
|
||||
fprintf(stderr, "sqlite3_open: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_PROTOCOL == rc) {
|
||||
fprintf(stderr, "sqlite3_open: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_OK == rc) {
|
||||
sqlite3_busy_timeout(db, 500);
|
||||
return(db);
|
||||
}
|
||||
|
||||
fprintf(stderr, "sqlite3_open: %s\n", sqlite3_errmsg(db));
|
||||
return(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the database.
|
||||
*/
|
||||
static void
|
||||
db_close(sqlite3 *db)
|
||||
{
|
||||
|
||||
if (SQLITE_OK == sqlite3_close(db))
|
||||
return;
|
||||
fprintf(stderr, "sqlite3_close: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
|
||||
/*
|
||||
* Step through the result set of a database query.
|
||||
*/
|
||||
static int
|
||||
db_step(sqlite3 *db, sqlite3_stmt *stmt)
|
||||
{
|
||||
int rc;
|
||||
size_t attempt = 0;
|
||||
|
||||
again:
|
||||
rc = sqlite3_step(stmt);
|
||||
if (SQLITE_BUSY == rc) {
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_LOCKED == rc) {
|
||||
fprintf(stderr, "sqlite3_step: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_PROTOCOL == rc) {
|
||||
fprintf(stderr, "sqlite3_step: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
}
|
||||
|
||||
if (SQLITE_DONE == rc || SQLITE_ROW == rc)
|
||||
return(rc);
|
||||
|
||||
fprintf(stderr, "sqlite3_step: %s\n", sqlite3_errmsg(db));
|
||||
return(rc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Execute a database query.
|
||||
*/
|
||||
static int
|
||||
db_exec(sqlite3 *db, const char *sql)
|
||||
{
|
||||
size_t attempt = 0;
|
||||
int rc;
|
||||
|
||||
again:
|
||||
rc = sqlite3_exec(db, sql, NULL, NULL, NULL);
|
||||
|
||||
if (SQLITE_BUSY == rc) {
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_LOCKED == rc) {
|
||||
fprintf(stderr, "sqlite3_exec: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_PROTOCOL == rc) {
|
||||
fprintf(stderr, "sqlite3_exec: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_OK == rc)
|
||||
return(1);
|
||||
|
||||
fprintf(stderr, "sqlite3_exec: %s (%s)\n",
|
||||
sqlite3_errmsg(db), sql);
|
||||
return(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Prepare a database statement.
|
||||
* If we fail, try to populate the database: it might not have been
|
||||
* initialised yet.
|
||||
*/
|
||||
static sqlite3_stmt *
|
||||
db_stmt(sqlite3 *db, const char *sql, int recurse)
|
||||
{
|
||||
sqlite3_stmt *stmt;
|
||||
const char *esql;
|
||||
size_t attempt = 0;
|
||||
int rc, bailed;
|
||||
|
||||
bailed = 0;
|
||||
again:
|
||||
rc = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
|
||||
if (SQLITE_BUSY == rc) {
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_LOCKED == rc) {
|
||||
fprintf(stderr, "sqlite3_prepare_v2: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_PROTOCOL == rc) {
|
||||
fprintf(stderr, "sqlite3_prepare_v2: %s\n",
|
||||
sqlite3_errmsg(db));
|
||||
db_sleep(attempt++);
|
||||
goto again;
|
||||
} else if (SQLITE_OK == rc)
|
||||
return(stmt);
|
||||
|
||||
fprintf(stderr, "sqlite3_prepare_v2: %s (%s) (%d)\n",
|
||||
sqlite3_errmsg(db), sql, sqlite3_errcode(db));
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
/*
|
||||
* Only attempt to do this if (1) we have a certain error, (2)
|
||||
* we haven't already bailed out, and (3) we haven't recursed
|
||||
* into the db_stmt() function.
|
||||
*/
|
||||
if (SQLITE_ERROR == rc && 0 == bailed && recurse) {
|
||||
esql = "CREATE TABLE record "
|
||||
"(count INTEGER NOT NULL DEFAULT(0))";
|
||||
if (db_exec(db, esql)) {
|
||||
esql = "INSERT INTO record DEFAULT VALUES";
|
||||
if (NULL != (stmt = db_stmt(db, esql, 0))) {
|
||||
db_step(db, stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
bailed = 1;
|
||||
goto again;
|
||||
}
|
||||
|
||||
return(NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
sendjson(struct kreq *req)
|
||||
{
|
||||
struct kjsonreq r;
|
||||
int rc;
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *stmt;
|
||||
|
||||
resp_open(req, KHTTP_200);
|
||||
kjson_open(&r, req);
|
||||
kjson_obj_open(&r);
|
||||
|
||||
if (NULL != (db = db_open())) {
|
||||
stmt = db_stmt(db, "SELECT count FROM record", 1);
|
||||
if (NULL != stmt) {
|
||||
rc = db_step(db, stmt);
|
||||
if (SQLITE_ROW == rc)
|
||||
kjson_putintp(&r, JSON_COUNT,
|
||||
sqlite3_column_int64(stmt, 0));
|
||||
else if (SQLITE_DONE == rc)
|
||||
kjson_putnullp(&r, JSON_COUNT);
|
||||
// TODO err khtml_puts(&r, "No value!?");
|
||||
else
|
||||
kjson_putnullp(&r, JSON_COUNT);
|
||||
// TODO err khtml_puts(&r, "Error getting value.");
|
||||
sqlite3_finalize(stmt);
|
||||
} else
|
||||
kjson_putnullp(&r, JSON_COUNT);
|
||||
// TODO err khtml_puts(&r, "Error getting statement.");
|
||||
|
||||
stmt = db_stmt(db, "UPDATE record SET count=count+1", 0);
|
||||
(void)db_step(db, stmt);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
db_close(db);
|
||||
} else
|
||||
kjson_putnullp(&r, JSON_COUNT);
|
||||
// TODO err khtml_puts(&r, "Error opening database.");
|
||||
|
||||
kjson_close(&r);
|
||||
}
|
||||
|
||||
static void
|
||||
sendindex(struct kreq *req)
|
||||
{
|
||||
struct khtmlreq r;
|
||||
|
||||
resp_open(req, KHTTP_200);
|
||||
khtml_open(&r, req);
|
||||
khtml_elem(&r, KELEM_DOCTYPE);
|
||||
khtml_elem(&r, KELEM_HTML);
|
||||
khtml_elem(&r, KELEM_HEAD);
|
||||
khtml_elem(&r, KELEM_TITLE);
|
||||
khtml_puts(&r, "Test JSON");
|
||||
khtml_closeelem(&r, 1);
|
||||
khtml_attr(&r, KELEM_SCRIPT,
|
||||
KATTR_SRC, "/quiz4.js",
|
||||
KATTR__MAX);
|
||||
khtml_closeelem(&r, 2);
|
||||
khtml_elem(&r, KELEM_BODY);
|
||||
khtml_attr(&r, KELEM_DIV,
|
||||
KATTR_ID, "count",
|
||||
KATTR__MAX);
|
||||
khtml_puts(&r, "No data.");
|
||||
|
||||
khtml_close(&r);
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
struct kreq r;
|
||||
|
||||
/*
|
||||
* Set up our main HTTP context.
|
||||
*/
|
||||
if (KCGI_OK != khttp_parse
|
||||
(&r, NULL, 0, pages, PAGE__MAX, PAGE_INDEX))
|
||||
return(EXIT_FAILURE);
|
||||
|
||||
if (KMETHOD_OPTIONS == r.method) {
|
||||
/*
|
||||
* Indicate that we accept GET and POST methods.
|
||||
* This isn't really needed.
|
||||
*/
|
||||
khttp_head(&r, kresps[KRESP_STATUS],
|
||||
"%s", khttps[KHTTP_200]);
|
||||
khttp_head(&r, kresps[KRESP_ALLOW],
|
||||
"OPTIONS GET POST");
|
||||
khttp_body(&r);
|
||||
} else if (KMETHOD_GET != r.method) {
|
||||
/*
|
||||
* Don't accept non-GET and non-POST methods.
|
||||
*/
|
||||
resp_open(&r, KHTTP_405);
|
||||
} else if (PAGE__MAX == r.page ||
|
||||
( KMIME_APP_JSON != r.mime &&
|
||||
KMIME_TEXT_HTML != r.mime) ) {
|
||||
/*
|
||||
* We've been asked for an unknown page or something
|
||||
* with an unknown extension.
|
||||
*/
|
||||
resp_open(&r, KHTTP_404);
|
||||
khttp_puts(&r, "Page not found.");
|
||||
} else
|
||||
/*
|
||||
* Route to page handler.
|
||||
*/
|
||||
(*disps[r.page])(&r);
|
||||
|
||||
/*
|
||||
* Clean up our context.
|
||||
*/
|
||||
khttp_free(&r);
|
||||
return(EXIT_SUCCESS);
|
||||
}
|
||||
|
20
quiz4.js
Normal file
20
quiz4.js
Normal file
@ -0,0 +1,20 @@
|
||||
function loadJSON(path, success, error)
|
||||
{
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === XMLHttpRequest.DONE) {
|
||||
if (xhr.status === 200) {
|
||||
success(JSON.parse(xhr.responseText));
|
||||
} else {
|
||||
error(xhr);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.open("GET", path, true);
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
loadJSON('/cgi-bin/quiz4/quiz4.json',
|
||||
function(data) { document.getElementById("count").textContent = data.count; },
|
||||
function(xhr) { console.error(xhr); }
|
||||
);
|
Loading…
Reference in New Issue
Block a user