diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7ea0944a9a5f301f7fa4b803fecc58fef0070d58..acbf1ce499e0febd56c8ef4a547fb27e32901b54 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -89,7 +89,7 @@ option(LWS_MBED3 "Platform is MBED3" OFF)
 option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF)
 option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF)
 option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF)
-
+option(LWS_WITH_LWSWS "Libwebsockets Webserver" ON)
 
 if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING)
 
@@ -110,6 +110,11 @@ if (WIN32)
 set(LWS_MAX_SMP 1)
 endif()
 
+
+if (LWS_WITHOUT_SERVER)
+set(LWS_WITH_LWSWS OFF)
+endif()
+
 if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER))
 	message(FATAL_ERROR "You have to enable both client and server for http proxy")
 endif()
@@ -1065,6 +1070,54 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 	endif()
 endif(NOT LWS_WITHOUT_TESTAPPS)
 
+if (LWS_WITH_LWSWS)
+		list(APPEND LWSWS_SRCS
+			"lwsws/main.c"
+			"lwsws/lejp.c"
+			"lwsws/conf.c"
+			"lwsws/http.c"
+		)
+
+		if (WIN32)
+			list(APPEND LWSWS_SRCS
+				${WIN32_HELPERS_PATH}/getopt.c
+				${WIN32_HELPERS_PATH}/getopt_long.c
+				${WIN32_HELPERS_PATH}/gettimeofday.c
+			)
+
+			list(APPEND LWSWS_HDR
+				${WIN32_HELPERS_PATH}/getopt.h
+				${WIN32_HELPERS_PATH}/gettimeofday.h
+			)
+		endif(WIN32)
+
+		source_group("Headers Private"   FILES ${LWSWS_HDR})
+		source_group("Sources"   FILES ${LWSWS_SRCS})
+		add_executable("lwsws" ${LWSWS_SRCS} ${LWSWS_HDR})
+
+		if (LWS_LINK_TESTAPPS_DYNAMIC)
+			if (NOT LWS_WITH_SHARED)
+				message(FATAL_ERROR "Build of the shared library is disabled. LWS_LINK_TESTAPPS_DYNAMIC must be combined with LWS_WITH_SHARED.")
+			endif()
+			target_link_libraries("lwsws" websockets_shared)
+			add_dependencies("lwsws" websockets_shared)
+		else()
+			if (NOT LWS_WITH_STATIC)
+				message(FATAL_ERROR "Build of the static library is disabled. Disabled LWS_LINK_TESTAPPS_DYNAMIC must be combined with LWS_WITH_STATIC.")
+			endif()
+			target_link_libraries("lwsws" websockets)
+			add_dependencies("lwsws" websockets)
+		endif()
+
+		# Set test app specific defines.
+		set_property(TARGET "lwsws"
+			     PROPERTY COMPILE_DEFINITIONS
+			     INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/share"
+		)
+	
+		
+endif (LWS_WITH_LWSWS)
+
 if (UNIX)
 	# Generate documentation.
 	# TODO: Fix this on Windows.
@@ -1177,6 +1230,12 @@ if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_CLIENT)
 	set(CPACK_COMPONENT_EXAMPLES_DISPLAY_NAME "Example files")
 endif()
 
+# lwsws
+if (LWS_WITH_LWSWS)
+	install(TARGETS lwsws
+		RUNTIME DESTINATION "${LWS_INSTALL_BIN_DIR}" COMPONENT lwsws )
+endif()
+
 # Programs shared files used by the test-server.
 if (NOT LWS_WITHOUT_TESTAPPS AND NOT LWS_WITHOUT_SERVER)
 	install(FILES ${TEST_SERVER_DATA}
diff --git a/LICENSE b/LICENSE
index da87198c6c47693767a8530ca2e0a868cdc6e479..ef462df47f029252bd199c32eb6eb3b2c978ea57 100644
--- a/LICENSE
+++ b/LICENSE
@@ -40,6 +40,16 @@ Public Domain (CC-zero) to simplify reuse
 
   - test-server/*.c
   - test-server/*.h
+  
+4) lwsws (Libwebsocket web server) is a bundled application that is not
+part of the libwebsockets library, it's a separate application that uses
+the library.  The related sources are in a separate directory.  If you don't
+distribute lwsws, you do not need to observe its license.
+
+  - lwsws/lejp.c        - LGPL2.1
+  - lwsws/lejp.h        - LGPL2.1
+  - lwsws/[all else]    -  GPL2.1
+
 
 
                   GNU LESSER GENERAL PUBLIC LICENSE
diff --git a/README.lwsws.md b/README.lwsws.md
new file mode 100644
index 0000000000000000000000000000000000000000..460e0e56512acfc8bc5edc5c02ef694f79e449dd
--- /dev/null
+++ b/README.lwsws.md
@@ -0,0 +1,110 @@
+Libwebsockets Web Server
+------------------------
+
+lwsws is an implementation of a very lightweight, ws-capable generic web
+server, which uses libwebsockets to implement everything underneath.
+
+Configuration
+-------------
+
+lwsws uses JSON config files, there is a single file intended for global
+settings
+
+/etc/lwsws/conf
+
+```
+# these are the server global settings
+# stuff related to vhosts should go in one
+# file per vhost in ../conf.d/
+
+{
+  "global": {
+   "uid": "99",
+   "gid": "99",
+   "interface": "eth0",
+   "count-threads": "1",
+   "init-ssl": "yes"
+ }
+}
+```
+
+and a config directory intended to take one file per vhost
+
+/etc/lwsws/conf.d/warmcat.com
+
+```
+{
+	"vhosts": [{
+		"name": "warmcat.com",
+		"port": "443",
+		"host-ssl-key": "/etc/pki/tls/private/warmcat.com.key",
+		"host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt",
+		"host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer",
+		"mounts": [{
+			"mountpoint": "/",
+			"origin": "file:///var/www/warmcat.com",
+			"default": "index.html"
+		}]
+	}]
+}
+```
+
+Vhosts
+------
+
+One server can run many vhosts, where SSL is in use SNI is used to match
+the connection to a vhost and its vhost-specific SSL keys during SSL
+negotiation.
+
+Listing multiple vhosts looks something like this
+
+```
+{
+        "vhosts": [{
+                "name": "warmcat.com",
+                "port": "443",
+                "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key",
+                "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt",
+                "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer",
+                "mounts": [{
+                        "mountpoint": "/",
+                        "origin": "file:///var/www/warmcat.com",
+                        "default": "index.html"
+                }]
+        }, {
+                "name": "warmcat2.com",
+                "port": "443",
+                "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key",
+                "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt",
+                "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer",
+                "mounts": [{
+                        "mountpoint": "/",
+                        "origin": "file:///var/www/warmcat2.com",
+                        "default": "index.html"
+                }]
+        }
+]
+}
+```
+
+Vhost name and port
+-------------------
+
+The vhost name field is used to match on incoming SNI or Host: header, so it
+must always be the host name used to reach the vhost externally.
+
+Vhosts may have the same name and different ports, these will each create a
+listening socket on the appropriate port, and they may have the same port and
+different name: these will be treated as true vhosts on one listening socket
+and the active vhost decided at SSL negotiation time (via SNI) or if no SSL,
+then after the Host: header from the client has been parsed.
+
+
+Mounts
+------
+
+Where mounts are given in the vhost definition, then directory contents may
+be auto-served if it matches the mountpoint.
+
+Currently only file:// mount protocol and a fixed set of mimetypes are
+supported.
\ No newline at end of file
diff --git a/changelog b/changelog
index 9c598a190a34564e80500486eef26a94a915424e..3142d559be8c4a01875e7cf896377765d34aa081 100644
--- a/changelog
+++ b/changelog
@@ -185,6 +185,49 @@ LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS.  If you give this, non-ssl
 connections to the server listen port are accepted and receive a 301
 redirect to / on the same host and port using https://
 
+New application lwsws
+---------------------
+
+A libwebsockets-based general webserver is built by default now, lwsws.
+
+It's configured by JSON, by default in
+
+  /etc/lwsws/conf
+
+which contains global lws context settings like this
+
+{
+  "global": {
+   "uid": "99",
+   "gid": "99",
+   "interface": "eth0",
+   "count-threads": "1"
+ }
+}
+
+  /etc/lwsws/conf.d/*
+
+which contains zero or more files describing vhosts, like this
+
+{
+ "vhosts": [
+  { "name": "warmcat.com",
+    "port": "443",
+    "host-ssl-key": "/etc/pki/tls/private/warmcat.com.key",
+    "host-ssl-cert": "/etc/pki/tls/certs/warmcat.com.crt",
+    "host-ssl-ca": "/etc/pki/tls/certs/warmcat.com.cer",
+    "mounts": [
+      { "/": [
+       { "home": "file:///var/www/warmcat.com" },
+       { "default": "index.html" }
+      ]
+     }
+    ]
+   }
+ ]
+}
+
+
 
 v1.7.0
 ======
diff --git a/lwsws/conf.c b/lwsws/conf.c
new file mode 100644
index 0000000000000000000000000000000000000000..4c94c5df448dcfc7204efa08804e6d2e062ba43e
--- /dev/null
+++ b/lwsws/conf.c
@@ -0,0 +1,382 @@
+/*
+ * libwebsockets web server application
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * 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:
+ * version 2.1 of the License.
+ *
+ * This library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301  USA
+ */
+
+#include "lwsws.h"
+
+static const char * const paths_global[] = {
+	"global.uid",
+	"global.gid",
+	"global.interface",
+	"global.count-threads",
+	"global.init-ssl",
+};
+
+enum lejp_global_paths {
+	LEJPGP_UID,
+	LEJPGP_GID,
+	LEJPGP_INTERFACE,
+	LEJPGP_COUNT_THREADS,
+	LWJPGP_INIT_SSL,
+};
+
+static const char * const paths_vhosts[] = {
+	"vhosts[]",
+	"vhosts[].mounts[]",
+	"vhosts[].name",
+	"vhosts[].port",
+	"vhosts[].host-ssl-key",
+	"vhosts[].host-ssl-cert",
+	"vhosts[].host-ssl-ca",
+	"vhosts[].mounts[].mountpoint",
+	"vhosts[].mounts[].origin",
+	"vhosts[].mounts[].default"
+};
+
+enum lejp_vhost_paths {
+	LEJPVP,
+	LEJPVP_MOUNTS,
+	LEJPVP_NAME,
+	LEJPVP_PORT,
+	LEJPVP_HOST_SSL_KEY,
+	LEJPVP_HOST_SSL_CERT,
+	LEJPVP_HOST_SSL_CA,
+	LEJPVP_MOUNTPOINT,
+	LEJPVP_ORIGIN,
+	LEJPVP_DEFAULT,
+};
+
+struct jpargs {
+	struct lws_context_creation_info *info;
+	struct lws_context *context;
+	const struct lws_protocols *protocols;
+	const struct lws_extension *extensions;
+	char *p, *end, valid;
+	struct lws_http_mount *head, *last;
+	char *mountpoint, *origin, *def;
+};
+
+static int arg_to_bool(const char *s)
+{
+	static const char * const on[] = { "on", "yes", "true" };
+	int n = atoi(s);
+
+	if (n)
+		return 1;
+
+	for (n = 0; n < ARRAY_SIZE(on); n++)
+		if (!strcasecmp(s, on[n]))
+			return 1;
+
+	return 0;
+}
+
+static char
+lejp_globals_cb(struct lejp_ctx *ctx, char reason)
+{
+	struct jpargs *a = (struct jpargs *)ctx->user;
+
+	/* we only match on the prepared path strings */
+	if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+		return 0;
+
+	switch (ctx->path_match - 1) {
+	case LEJPGP_UID:
+		a->info->uid = atoi(ctx->buf);
+		return 0;
+	case LEJPGP_GID:
+		a->info->gid = atoi(ctx->buf);
+		return 0;
+	case LEJPGP_INTERFACE:
+		a->info->iface = a->p;
+		break;
+	case LEJPGP_COUNT_THREADS:
+		a->info->count_threads = atoi(ctx->buf);
+		return 0;
+	case LWJPGP_INIT_SSL:
+		if (arg_to_bool(ctx->buf))
+			a->info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
+		return 0;
+
+	default:
+		return 0;
+	}
+
+	a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf);
+
+	return 0;
+}
+
+static char
+lejp_vhosts_cb(struct lejp_ctx *ctx, char reason)
+{
+	struct jpargs *a = (struct jpargs *)ctx->user;
+	struct lws_http_mount *m;
+	int n;
+
+	if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) {
+		a->valid = 1;
+		a->head = NULL;
+		a->last = NULL;
+		a->info->port = 0;
+		a->info->iface = NULL;
+		a->info->protocols = a->protocols;
+		a->info->extensions = a->extensions;
+		a->info->ssl_cert_filepath = NULL;
+		a->info->ssl_private_key_filepath = NULL;
+		a->info->ssl_ca_filepath = NULL;
+		a->info->timeout_secs = 5;
+		a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
+				       "ECDHE-RSA-AES256-GCM-SHA384:"
+				       "DHE-RSA-AES256-GCM-SHA384:"
+				       "ECDHE-RSA-AES256-SHA384:"
+				       "HIGH:!aNULL:!eNULL:!EXPORT:"
+				       "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
+				       "!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
+				       "!DHE-RSA-AES128-SHA256:"
+				       "!AES128-GCM-SHA256:"
+				       "!AES128-SHA256:"
+				       "!DHE-RSA-AES256-SHA256:"
+				       "!AES256-GCM-SHA384:"
+				       "!AES256-SHA256";
+	}
+
+	if (reason == LEJPCB_OBJECT_START &&
+	    ctx->path_match == LEJPVP_MOUNTS + 1) {
+		a->mountpoint = NULL;
+		a->origin = NULL;
+		a->def = NULL;
+	}
+
+	if (reason == LEJPCB_OBJECT_END &&
+	    (ctx->path_match == LEJPVP + 1 || !ctx->path[0]) &&
+	    a->valid) {
+
+		//lwsl_notice("%s\n", ctx->path);
+		if (!a->info->port) {
+			lwsl_err("Port required (eg, 443)");
+			return 1;
+		}
+		a->valid = 0;
+
+		if (!lws_create_vhost(a->context, a->info, a->head)) {
+			lwsl_err("Failed to create vhost %s\n",
+				 a->info->vhost_name);
+			return 1;
+		}
+
+		return 0;
+	}
+
+	if (reason == LEJPCB_OBJECT_END &&
+	    ctx->path_match == LEJPVP_MOUNTS + 1) {
+		if (!a->mountpoint || !a->origin) {
+			lwsl_err("mountpoint and origin required\n");
+			return 1;
+		}
+
+		n = lws_write_http_mount(a->last, &m, a->p, a->mountpoint,
+					 a->origin, a->def);
+		if (!n)
+			return 1;
+		a->p += n;
+		if (!a->head)
+			a->head = m;
+
+		a->last = m;
+	}
+
+	/* we only match on the prepared path strings */
+	if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match)
+		return 0;
+
+	switch (ctx->path_match - 1) {
+	case LEJPVP_NAME:
+		a->info->vhost_name = a->p;
+		break;
+	case LEJPVP_PORT:
+		a->info->port = atoi(ctx->buf);
+		return 0;
+	case LEJPVP_HOST_SSL_KEY:
+		a->info->ssl_private_key_filepath = a->p;
+		break;
+	case LEJPVP_HOST_SSL_CERT:
+		a->info->ssl_cert_filepath = a->p;
+		break;
+	case LEJPVP_HOST_SSL_CA:
+		a->info->ssl_ca_filepath = a->p;
+		break;
+	case LEJPVP_MOUNTPOINT:
+		a->mountpoint = a->p;
+		break;
+	case LEJPVP_ORIGIN:
+		a->origin = a->p;
+		break;
+	case LEJPVP_DEFAULT:
+		a->def = a->p;
+		break;
+	default:
+		return 0;
+	}
+
+	a->p += snprintf(a->p, a->end - a->p, "%s", ctx->buf);
+	*(a->p)++ = '\0';
+
+	return 0;
+}
+
+/*
+ * returns 0 = OK, 1 = can't open, 2 = parsing error
+ */
+
+static int
+lwsws_get_config(void *user, const char *f, const char * const *paths,
+		 int count_paths, lejp_callback cb)
+{
+	unsigned char buf[128];
+	struct lejp_ctx ctx;
+	int n, m, fd;
+
+	fd = open(f, O_RDONLY);
+	if (fd < 0) {
+		lwsl_err("Cannot open %s\n", f);
+		return 1;
+	}
+	lwsl_info("%s: %s\n", __func__, f);
+	lejp_construct(&ctx, cb, user, paths, count_paths);
+
+	do {
+		n = read(fd, buf, sizeof(buf));
+		if (!n)
+			break;
+
+		m = (int)(char)lejp_parse(&ctx, buf, n);
+	} while (m == LEJP_CONTINUE);
+
+	close(fd);
+	n = ctx.line;
+	lejp_destruct(&ctx);
+
+	if (m < 0) {
+		lwsl_err("%s(%u): parsing error %d\n", f, n, m);
+		return 2;
+	}
+
+	return 0;
+}
+
+#ifndef _WIN32
+static int filter(const struct dirent *ent)
+{
+	if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+		return 0;
+
+	return 1;
+}
+#endif
+
+static int
+lwsws_get_config_d(void *user, const char *d, const char * const *paths,
+		   int count_paths, lejp_callback cb)
+{
+#ifndef _WIN32
+	struct dirent **namelist;
+	char path[256];
+	int n, i, ret = 0;
+
+	n = scandir(d, &namelist, filter, alphasort);
+	if (n < 0) {
+		lwsl_err("Scandir on %d failed\n", d);
+	}
+
+	for (i = 0; i < n; i++) {
+		snprintf(path, sizeof(path) - 1, "%s/%s", d,
+			 namelist[i]->d_name);
+		ret = lwsws_get_config(user, path, paths, count_paths, cb);
+		if (ret) {
+			while (i++ < n)
+				free(namelist[i]);
+			goto bail;
+		}
+		free(namelist[i]);
+	}
+
+bail:
+	free(namelist);
+
+	return ret;
+#else
+	return 0;
+#endif
+}
+
+int
+lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
+			 char **cs, int *len)
+{
+	struct jpargs a;
+
+	a.info = info;
+	a.p = *cs;
+	a.end = a.p + *len;
+	a.valid = 0;
+
+	if (lwsws_get_config(&a, "/etc/lwsws/conf", paths_global,
+			     ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
+		return 1;
+	if (lwsws_get_config_d(&a, d, paths_global,
+			       ARRAY_SIZE(paths_global), lejp_globals_cb) > 1)
+		return 1;
+
+	*cs = a.p;
+	*len = a.end - a.p;
+
+	return 0;
+}
+
+int
+lwsws_get_config_vhosts(struct lws_context *context,
+			struct lws_context_creation_info *info, const char *d,
+			char **cs, int *len)
+{
+	struct jpargs a;
+
+	a.info = info;
+	a.p = *cs;
+	a.end = a.p + *len;
+	a.valid = 0;
+	a.context = context;
+	a.protocols = info->protocols;
+	a.extensions = info->extensions;
+
+	if (lwsws_get_config(&a, "/etc/lwsws/conf", paths_vhosts,
+			     ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
+		return 1;
+	if (lwsws_get_config_d(&a, d, paths_vhosts,
+			       ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1)
+		return 1;
+
+	*cs = a.p;
+	*len = a.end - a.p;
+
+	lws_finalize_startup(context);
+
+	return 0;
+}
diff --git a/lwsws/http.c b/lwsws/http.c
new file mode 100644
index 0000000000000000000000000000000000000000..1b27a7d213b2f220177214264ca574a75983ba84
--- /dev/null
+++ b/lwsws/http.c
@@ -0,0 +1,663 @@
+/*
+ * libwebsockets web server application
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * 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:
+ * version 2.1 of the License.
+ *
+ * This library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301  USA
+ */
+#include "lwsws.h"
+
+/* http server gets files from this path */
+#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
+char *resource_path = LOCAL_RESOURCE_PATH;
+
+
+
+/*
+ * We take a strict whitelist approach to stop ../ attacks
+ */
+struct serveable {
+	const char *urlpath;
+	const char *mimetype;
+};
+
+const char * get_mimetype(const char *file)
+{
+	int n = strlen(file);
+
+	if (n < 5)
+		return NULL;
+
+	if (!strcmp(&file[n - 4], ".ico"))
+		return "image/x-icon";
+
+	if (!strcmp(&file[n - 4], ".png"))
+		return "image/png";
+
+	if (!strcmp(&file[n - 5], ".html"))
+		return "text/html";
+
+	if (!strcmp(&file[n - 4], ".css"))
+		return "text/css";
+
+	return NULL;
+}
+
+/* this protocol server (always the first one) handles HTTP,
+ *
+ * Some misc callbacks that aren't associated with a protocol also turn up only
+ * here on the first protocol server.
+ */
+
+int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
+		  void *in, size_t len)
+{
+	struct per_session_data__http *pss =
+			(struct per_session_data__http *)user;
+	unsigned char buffer[4096 + LWS_PRE];
+	unsigned long amount, file_len, sent;
+	char leaf_path[1024];
+	const char *mimetype;
+	char *other_headers;
+	unsigned char *end;
+	struct timeval tv;
+	unsigned char *p;
+#ifndef LWS_NO_CLIENT
+	struct per_session_data__http *pss1;
+	struct lws *wsi1;
+#endif
+	char buf[256];
+	char b64[64];
+	int n, m;
+#ifdef EXTERNAL_POLL
+	struct lws_pollargs *pa = (struct lws_pollargs *)in;
+#endif
+
+//	lwsl_err("%s: reason %d\n", __func__, reason);
+
+	switch (reason) {
+	case LWS_CALLBACK_HTTP:
+
+		{
+			char name[100], rip[50];
+			lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name,
+					       sizeof(name), rip, sizeof(rip));
+			sprintf(buf, "%s (%s)", name, rip);
+			lwsl_notice("HTTP connect from %s\n", buf);
+		}
+
+		if (len < 1) {
+			lws_return_http_status(wsi,
+						HTTP_STATUS_BAD_REQUEST, NULL);
+			goto try_to_reuse;
+		}
+
+#ifndef LWS_NO_CLIENT
+		if (!strncmp(in, "/proxytest", 10)) {
+			struct lws_client_connect_info i;
+			char *rootpath = "/";
+			const char *p = (const char *)in;
+
+			if (lws_get_child(wsi))
+				break;
+
+			pss->client_finished = 0;
+			memset(&i,0, sizeof(i));
+			i.context = lws_get_context(wsi);
+			i.address = "git.libwebsockets.org";
+			i.port = 80;
+			i.ssl_connection = 0;
+			if (p[10])
+				i.path = (char *)in + 10;
+			else
+				i.path = rootpath;
+			i.host = "git.libwebsockets.org";
+			i.origin = NULL;
+			i.method = "GET";
+			i.parent_wsi = wsi;
+			i.uri_replace_from = "git.libwebsockets.org/";
+			i.uri_replace_to = "/proxytest/";
+			if (!lws_client_connect_via_info(&i)) {
+				lwsl_err("proxy connect fail\n");
+				break;
+			}
+
+
+
+			break;
+		}
+#endif
+
+#if 1
+		/* this example server has no concept of directories */
+		if (strchr((const char *)in + 1, '/')) {
+			lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+			goto try_to_reuse;
+		}
+#endif
+
+#ifdef LWS_WITH_CGI
+		if (!strncmp(in, "/cgitest", 8)) {
+			static char *cmd[] = {
+				"/bin/sh",
+				"-c",
+				INSTALL_DATADIR"/libwebsockets-test-server/lws-cgi-test.sh",
+//				"/var/www/cgi-bin/cgit",
+				NULL
+			};
+
+			lwsl_notice("%s: cgitest\n", __func__);
+			n = lws_cgi(wsi, cmd, 8, 5);
+			if (n) {
+				lwsl_err("%s: cgi failed\n");
+				return -1;
+			}
+			p = buffer + LWS_PRE;
+			end = p + sizeof(buffer) - LWS_PRE;
+
+			if (lws_add_http_header_status(wsi, 200, &p, end))
+				return 1;
+			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION,
+					(unsigned char *)"close", 5, &p, end))
+				return 1;
+			n = lws_write(wsi, buffer + LWS_PRE,
+				      p - (buffer + LWS_PRE),
+				      LWS_WRITE_HTTP_HEADERS);
+
+			/* the cgi starts by outputting headers, we can't
+			 *  finalize the headers until we see the end of that
+			 */
+
+			break;
+		}
+#endif
+
+		/* if a legal POST URL, let it continue and accept data */
+		if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
+			return 0;
+
+		/* check for the "send a big file by hand" example case */
+
+		if (!strcmp((const char *)in, "/leaf.jpg")) {
+			if (strlen(resource_path) > sizeof(leaf_path) - 10)
+				return -1;
+			sprintf(leaf_path, "%s/leaf.jpg", resource_path);
+
+			/* well, let's demonstrate how to send the hard way */
+
+			p = buffer + LWS_PRE;
+			end = p + sizeof(buffer) - LWS_PRE;
+
+			pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len,
+						     LWS_O_RDONLY);
+
+			if (pss->fd == LWS_INVALID_FILE) {
+				lwsl_err("faild to open file %s\n", leaf_path);
+				return -1;
+			}
+
+			/*
+			 * we will send a big jpeg file, but it could be
+			 * anything.  Set the Content-Type: appropriately
+			 * so the browser knows what to do with it.
+			 *
+			 * Notice we use the APIs to build the header, which
+			 * will do the right thing for HTTP 1/1.1 and HTTP2
+			 * depending on what connection it happens to be working
+			 * on
+			 */
+			if (lws_add_http_header_status(wsi, 200, &p, end))
+				return 1;
+			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER,
+				    	(unsigned char *)"libwebsockets",
+					13, &p, end))
+				return 1;
+			if (lws_add_http_header_by_token(wsi,
+					WSI_TOKEN_HTTP_CONTENT_TYPE,
+				    	(unsigned char *)"image/jpeg",
+					10, &p, end))
+				return 1;
+			if (lws_add_http_header_content_length(wsi,
+							       file_len, &p,
+							       end))
+				return 1;
+			if (lws_finalize_http_header(wsi, &p, end))
+				return 1;
+
+			/*
+			 * send the http headers...
+			 * this won't block since it's the first payload sent
+			 * on the connection since it was established
+			 * (too small for partial)
+			 *
+			 * Notice they are sent using LWS_WRITE_HTTP_HEADERS
+			 * which also means you can't send body too in one step,
+			 * this is mandated by changes in HTTP2
+			 */
+
+			*p = '\0';
+			lwsl_info("%s\n", buffer + LWS_PRE);
+
+			n = lws_write(wsi, buffer + LWS_PRE,
+				      p - (buffer + LWS_PRE),
+				      LWS_WRITE_HTTP_HEADERS);
+			if (n < 0) {
+				lws_plat_file_close(wsi, pss->fd);
+				return -1;
+			}
+			/*
+			 * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
+			 */
+			lws_callback_on_writable(wsi);
+			break;
+		}
+
+		/* if not, send a file the easy way */
+		if (!strncmp(in, "/cgit-data/", 11)) {
+			in = (char *)in + 11;
+			strcpy(buf, "/usr/share/cgit");
+		} else
+			strcpy(buf, resource_path);
+
+		if (strcmp(in, "/")) {
+			if (*((const char *)in) != '/')
+				strcat(buf, "/");
+			strncat(buf, in, sizeof(buf) - strlen(buf) - 1);
+		} else /* default file to serve */
+			strcat(buf, "/test.html");
+		buf[sizeof(buf) - 1] = '\0';
+
+		/* refuse to serve files we don't understand */
+		mimetype = get_mimetype(buf);
+		if (!mimetype) {
+			lwsl_err("Unknown mimetype for %s\n", buf);
+			lws_return_http_status(wsi,
+				      HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL);
+			return -1;
+		}
+
+		/* demonstrates how to set a cookie on / */
+
+		other_headers = leaf_path;
+		p = (unsigned char *)leaf_path;
+		if (!strcmp((const char *)in, "/") &&
+			   !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
+			/* this isn't very unguessable but it'll do for us */
+			gettimeofday(&tv, NULL);
+			n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
+				(unsigned int)tv.tv_sec,
+				(unsigned int)tv.tv_usec);
+
+			if (lws_add_http_header_by_name(wsi,
+				(unsigned char *)"set-cookie:",
+				(unsigned char *)b64, n, &p,
+				(unsigned char *)leaf_path + sizeof(leaf_path)))
+				return 1;
+		}
+		if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi,
+						(unsigned char *)
+						"Strict-Transport-Security:",
+						(unsigned char *)
+						"max-age=15768000 ; "
+						"includeSubDomains", 36, &p,
+						(unsigned char *)leaf_path +
+							sizeof(leaf_path)))
+			return 1;
+		n = (char *)p - leaf_path;
+
+		n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n);
+		if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
+			return -1; /* error or can't reuse connection: close the socket */
+
+		/*
+		 * notice that the sending of the file completes asynchronously,
+		 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
+		 * it's done
+		 */
+		break;
+
+	case LWS_CALLBACK_HTTP_BODY:
+		strncpy(buf, in, 20);
+		buf[20] = '\0';
+		if (len < 20)
+			buf[len] = '\0';
+
+		lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n",
+				(const char *)buf, (int)len);
+
+		break;
+
+	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
+		lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n");
+		/* the whole of the sent body arrived, close or reuse the connection */
+		lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
+		goto try_to_reuse;
+
+	case LWS_CALLBACK_HTTP_FILE_COMPLETION:
+		goto try_to_reuse;
+
+	case LWS_CALLBACK_HTTP_WRITEABLE:
+		lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n");
+
+		if (pss->client_finished)
+			return -1;
+
+		if (pss->fd == LWS_INVALID_FILE)
+			goto try_to_reuse;
+#ifdef LWS_WITH_CGI
+		if (pss->reason_bf & 1) {
+			if (lws_cgi_write_split_stdout_headers(wsi) < 0)
+				goto bail;
+
+			pss->reason_bf &= ~1;
+			break;
+		}
+#endif
+#ifndef LWS_NO_CLIENT
+		if (pss->reason_bf & 2) {
+			char *px = buf + LWS_PRE;
+			int lenx = sizeof(buf) - LWS_PRE;
+			/*
+			 * our sink is writeable and our source has something
+			 * to read.  So read a lump of source material of
+			 * suitable size to send or what's available, whichever
+			 * is the smaller.
+			 */
+			pss->reason_bf &= ~2;
+			wsi1 = lws_get_child(wsi);
+			if (!wsi1)
+				break;
+			if (lws_http_client_read(wsi1, &px, &lenx) < 0)
+				goto bail;
+
+			if (pss->client_finished)
+				return -1;
+			break;
+		}
+#endif
+		/*
+		 * we can send more of whatever it is we were sending
+		 */
+		sent = 0;
+		do {
+			/* we'd like the send this much */
+			n = sizeof(buffer) - LWS_PRE;
+
+			/* but if the peer told us he wants less, we can adapt */
+			m = lws_get_peer_write_allowance(wsi);
+
+			/* -1 means not using a protocol that has this info */
+			if (m == 0)
+				/* right now, peer can't handle anything */
+				goto later;
+
+			if (m != -1 && m < n)
+				/* he couldn't handle that much */
+				n = m;
+
+			n = lws_plat_file_read(wsi, pss->fd,
+					       &amount, buffer + LWS_PRE, n);
+			/* problem reading, close conn */
+			if (n < 0) {
+				lwsl_err("problem reading file\n");
+				goto bail;
+			}
+			n = (int)amount;
+			/* sent it all, close conn */
+			if (n == 0)
+				goto penultimate;
+			/*
+			 * To support HTTP2, must take care about preamble space
+			 *
+			 * identification of when we send the last payload frame
+			 * is handled by the library itself if you sent a
+			 * content-length header
+			 */
+			m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP);
+			if (m < 0) {
+				lwsl_err("write failed\n");
+				/* write failed, close conn */
+				goto bail;
+			}
+			if (m) /* while still active, extend timeout */
+				lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5);
+			sent += m;
+
+		} while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024));
+later:
+		lws_callback_on_writable(wsi);
+		break;
+penultimate:
+		lws_plat_file_close(wsi, pss->fd);
+		pss->fd = LWS_INVALID_FILE;
+		goto try_to_reuse;
+
+bail:
+		lws_plat_file_close(wsi, pss->fd);
+
+		return -1;
+
+	/*
+	 * callback for confirming to continue with client IP appear in
+	 * protocol 0 callback since no websocket protocol has been agreed
+	 * yet.  You can just ignore this if you won't filter on client IP
+	 * since the default unhandled callback return is 0 meaning let the
+	 * connection continue.
+	 */
+	case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
+		/* if we returned non-zero from here, we kill the connection */
+		break;
+
+#ifndef LWS_NO_CLIENT
+	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
+		char ctype[64], ctlen = 0;
+		lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
+		p = buffer + LWS_PRE;
+		end = p + sizeof(buffer) - LWS_PRE;
+		if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end))
+			return 1;
+		if (lws_add_http_header_by_token(lws_get_parent(wsi),
+				WSI_TOKEN_HTTP_SERVER,
+			    	(unsigned char *)"libwebsockets",
+				13, &p, end))
+			return 1;
+
+		ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
+		if (ctlen > 0) {
+			if (lws_add_http_header_by_token(lws_get_parent(wsi),
+				WSI_TOKEN_HTTP_CONTENT_TYPE,
+				(unsigned char *)ctype, ctlen, &p, end))
+				return 1;
+		}
+#if 0
+		if (lws_add_http_header_content_length(lws_get_parent(wsi),
+						       file_len, &p, end))
+			return 1;
+#endif
+		if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
+			return 1;
+
+		*p = '\0';
+		lwsl_info("%s\n", buffer + LWS_PRE);
+
+		n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE,
+			      p - (buffer + LWS_PRE),
+			      LWS_WRITE_HTTP_HEADERS);
+		if (n < 0)
+			return -1;
+
+		break; }
+	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+		//lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
+		return -1;
+		break;
+	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
+		//lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
+		assert(lws_get_parent(wsi));
+		if (!lws_get_parent(wsi))
+			break;
+		// lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d,  len %d\n",
+		//		wsi, lws_get_socket_fd(wsi),
+		//		lws_get_parent(wsi),
+		//		lws_get_socket_fd(lws_get_parent(wsi)), len);
+		pss1 = lws_wsi_user(lws_get_parent(wsi));
+		pss1->reason_bf |= 2;
+		lws_callback_on_writable(lws_get_parent(wsi));
+		break;
+	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+		//lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len);
+		assert(lws_get_parent(wsi));
+		m = lws_write(lws_get_parent(wsi), (unsigned char *)in,
+				len, LWS_WRITE_HTTP);
+		if (m < 0)
+			return -1;
+		break;
+	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+		//lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+		assert(lws_get_parent(wsi));
+		if (!lws_get_parent(wsi))
+			break;
+		pss1 = lws_wsi_user(lws_get_parent(wsi));
+		pss1->client_finished = 1;
+		break;
+#endif
+
+#ifdef LWS_WITH_CGI
+	/* CGI IO events (POLLIN/OUT) appear here our demo user code policy is
+	 *
+	 *  - POST data goes on subprocess stdin
+	 *  - subprocess stdout goes on http via writeable callback
+	 *  - subprocess stderr goes to the logs
+	 */
+	case LWS_CALLBACK_CGI:
+		pss->args = *((struct lws_cgi_args *)in);
+		//lwsl_notice("LWS_CALLBACK_CGI: ch %d\n", pss->args.ch);
+		switch (pss->args.ch) { /* which of stdin/out/err ? */
+		case LWS_STDIN:
+			/* TBD stdin rx flow control */
+			break;
+		case LWS_STDOUT:
+			pss->reason_bf |= 1;
+			/* when writing to MASTER would not block */
+			lws_callback_on_writable(wsi);
+			break;
+		case LWS_STDERR:
+			n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDERR]),
+					buf, 127);
+			//lwsl_notice("stderr reads %d\n", n);
+			if (n > 0) {
+				if (buf[n - 1] != '\n')
+					buf[n++] = '\n';
+				buf[n] = '\0';
+				lwsl_notice("CGI-stderr: %s\n", buf);
+			}
+			break;
+		}
+		break;
+
+	case LWS_CALLBACK_CGI_TERMINATED:
+		//lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n");
+		/* because we sent on openended http, close the connection */
+		return -1;
+
+	case LWS_CALLBACK_CGI_STDIN_DATA:  /* POST body for stdin */
+		//lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n");
+		pss->args = *((struct lws_cgi_args *)in);
+		n = write(lws_get_socket_fd(pss->args.stdwsi[LWS_STDIN]),
+			  pss->args.data, pss->args.len);
+		//lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n);
+		if (n < pss->args.len)
+			lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went",
+					n, pss->args.len);
+		return n;
+#endif
+
+	/*
+	 * callbacks for managing the external poll() array appear in
+	 * protocol 0 callback
+	 */
+
+	case LWS_CALLBACK_LOCK_POLL:
+		/*
+		 * lock mutex to protect pollfd state
+		 * called before any other POLL related callback
+		 * if protecting wsi lifecycle change, len == 1
+		 */
+		test_server_lock(len);
+		break;
+
+	case LWS_CALLBACK_UNLOCK_POLL:
+		/*
+		 * unlock mutex to protect pollfd state when
+		 * called after any other POLL related callback
+		 * if protecting wsi lifecycle change, len == 1
+		 */
+		test_server_unlock(len);
+		break;
+
+#ifdef EXTERNAL_POLL
+	case LWS_CALLBACK_ADD_POLL_FD:
+
+		if (count_pollfds >= max_poll_elements) {
+			lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
+			return 1;
+		}
+
+		fd_lookup[pa->fd] = count_pollfds;
+		pollfds[count_pollfds].fd = pa->fd;
+		pollfds[count_pollfds].events = pa->events;
+		pollfds[count_pollfds++].revents = 0;
+		break;
+
+	case LWS_CALLBACK_DEL_POLL_FD:
+		if (!--count_pollfds)
+			break;
+		m = fd_lookup[pa->fd];
+		/* have the last guy take up the vacant slot */
+		pollfds[m] = pollfds[count_pollfds];
+		fd_lookup[pollfds[count_pollfds].fd] = m;
+		break;
+
+	case LWS_CALLBACK_CHANGE_MODE_POLL_FD:
+	        pollfds[fd_lookup[pa->fd]].events = pa->events;
+		break;
+#endif
+
+	case LWS_CALLBACK_GET_THREAD_ID:
+		/*
+		 * if you will call "lws_callback_on_writable"
+		 * from a different thread, return the caller thread ID
+		 * here so lws can use this information to work out if it
+		 * should signal the poll() loop to exit and restart early
+		 */
+
+		/* return pthread_getthreadid_np(); */
+
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+
+	/* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */
+try_to_reuse:
+	if (lws_http_transaction_completed(wsi))
+		return -1;
+
+	return 0;
+}
diff --git a/lwsws/lejp.c b/lwsws/lejp.c
new file mode 100644
index 0000000000000000000000000000000000000000..9eff615296c3a6fc199e58affcec962cc9116d5f
--- /dev/null
+++ b/lwsws/lejp.c
@@ -0,0 +1,621 @@
+/*
+ * Lightweight Embedded JSON Parser
+ *
+ * Copyright (C) 2013 Andy Green <andy@warmcat.com>
+ * This code is licensed under LGPL 2.1
+ * http://www.gnu.org/licenses/lgpl-2.1.html
+ */
+
+#include <string.h>
+#include "lejp.h"
+
+#include <stdio.h>
+
+/**
+ * lejp_construct - prepare a struct lejp_ctx for use
+ *
+ * @ctx:	pointer to your struct lejp_ctx
+ * @callback:	your user callback which will received parsed tokens
+ * @user:	optional user data pointer untouched by lejp
+ * @paths:	your array of name elements you are interested in
+ * @count_paths:	ARRAY_SIZE() of @paths
+ *
+ * Prepares your context struct for use with lejp
+ */
+
+void
+lejp_construct(struct lejp_ctx *ctx,
+	char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
+			const char * const *paths, unsigned char count_paths)
+{
+	ctx->st[0].s = 0;
+	ctx->st[0].p = 0;
+	ctx->st[0].i = 0;
+	ctx->st[0].b = 0;
+	ctx->sp = 0;
+	ctx->ipos = 0;
+	ctx->ppos = 0;
+	ctx->path_match = 0;
+	ctx->path[0] = '\0';
+	ctx->callback = callback;
+	ctx->user = user;
+	ctx->paths = paths;
+	ctx->count_paths = count_paths;
+	ctx->line = 1;
+	ctx->callback(ctx, LEJPCB_CONSTRUCTED);
+}
+
+/**
+ * lejp_destruct - retire a previously constructed struct lejp_ctx
+ *
+ * @ctx:	pointer to your struct lejp_ctx
+ *
+ * lejp does not perform any allocations, but since your user code might, this
+ * provides a one-time LEJPCB_DESTRUCTED callback at destruction time where
+ * you can clean up in your callback.
+ */
+
+void
+lejp_destruct(struct lejp_ctx *ctx)
+{
+	/* no allocations... just let callback know what it happening */
+	ctx->callback(ctx, LEJPCB_DESTRUCTED);
+}
+
+/**
+ * lejp_change_callback - switch to a different callback from now on
+ *
+ * @ctx:	pointer to your struct lejp_ctx
+ * @callback:	your user callback which will received parsed tokens
+ *
+ * This tells the old callback it was destroyed, in case you want to take any
+ * action because that callback "lost focus", then changes to the new
+ * callback and tells it first that it was constructed, and then started.
+ *
+ * Changing callback is a cheap and powerful trick to split out handlers
+ * according to information earlier in the parse.  For example you may have
+ * a JSON pair "schema" whose value defines what can be expected for the rest
+ * of the JSON.  Rather than having one huge callback for all cases, you can
+ * have an initial one looking for "schema" which then calls
+ * lejp_change_callback() to a handler specific for the schema.
+ *
+ * Notice that afterwards, you need to construct the context again anyway to
+ * parse another JSON object, and the callback is reset then to the main,
+ * schema-interpreting one.  The construction action is very lightweight.
+ */
+
+void
+lejp_change_callback(struct lejp_ctx *ctx,
+		       char (*callback)(struct lejp_ctx *ctx, char reason))
+{
+	ctx->callback(ctx, LEJPCB_DESTRUCTED);
+	ctx->callback = callback;
+	ctx->callback(ctx, LEJPCB_CONSTRUCTED);
+	ctx->callback(ctx, LEJPCB_START);
+}
+
+static void
+lejp_check_path_match(struct lejp_ctx *ctx)
+{
+	int n;
+
+	/* we only need to check if a match is not active */
+	for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) {
+		if (strcmp(ctx->path, ctx->paths[n]))
+			continue;
+		ctx->path_match = n + 1;
+		ctx->path_match_len = ctx->ppos;
+		return;
+	}
+}
+
+/**
+ * lejp_parse - interpret some more incoming data incrementally
+ *
+ * @ctx:	previously constructed parsing context
+ * @json:	char buffer with the new data to interpret
+ * @len:	amount of data in the buffer
+ *
+ * Because lejp is a stream parser, it incrementally parses as new data
+ * becomes available, maintaining all state in the context struct.  So an
+ * incomplete JSON is a normal situation, getting you a LEJP_CONTINUE
+ * return, signalling there's no error but to call again with more data when
+ * it comes to complete the parsing.  Successful parsing completes with a
+ * 0 or positive integer indicating how much of the last input buffer was
+ * unused.
+ */
+
+int
+lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len)
+{
+	unsigned char c, n, s, ret = LEJP_REJECT_UNKNOWN;
+	static const char esc_char[] = "\"\\/bfnrt";
+	static const char esc_tran[] = "\"\\/\b\f\n\r\t";
+	static const char tokens[] = "rue alse ull ";
+
+	if (!ctx->sp && !ctx->ppos)
+		ctx->callback(ctx, LEJPCB_START);
+
+	while (len--) {
+		c = *json++;
+
+		s = ctx->st[ctx->sp].s;
+
+		/* skip whitespace unless we should care */
+		if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#') {
+			if (c == '\n') {
+				ctx->line++;
+				ctx->st[ctx->sp].s &= ~LEJP_FLAG_WS_COMMENTLINE;
+			}
+			if (!(s & LEJP_FLAG_WS_KEEP)) {
+				if (c == '#')
+					ctx->st[ctx->sp].s |=
+						LEJP_FLAG_WS_COMMENTLINE;
+				continue;
+			}
+		}
+
+		if (ctx->st[ctx->sp].s & LEJP_FLAG_WS_COMMENTLINE)
+			continue;
+
+		switch (s) {
+		case LEJP_IDLE:
+			if (c != '{') {
+				ret = LEJP_REJECT_IDLE_NO_BRACE;
+				goto reject;
+			}
+			ctx->callback(ctx, LEJPCB_OBJECT_START);
+			ctx->st[ctx->sp].s = LEJP_MEMBERS;
+			break;
+		case LEJP_MEMBERS:
+			if (c == '}') {
+				ctx->st[ctx->sp].s = LEJP_IDLE;
+				ret = LEJP_REJECT_MEMBERS_NO_CLOSE;
+				goto reject;
+			}
+			ctx->st[ctx->sp].s = LEJP_M_P;
+			goto redo_character;
+		case LEJP_M_P:
+			if (c != '\"') {
+				ret = LEJP_REJECT_MP_NO_OPEN_QUOTE;
+				goto reject;
+			}
+			/* push */
+			ctx->st[ctx->sp].s = LEJP_MP_DELIM;
+			c = LEJP_MP_STRING;
+			goto add_stack_level;
+
+		case LEJP_MP_STRING:
+			if (c == '\"') {
+				if (!ctx->sp) {
+					ret = LEJP_REJECT_MP_STRING_UNDERRUN;
+					goto reject;
+				}
+				if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
+					ctx->buf[ctx->npos] = '\0';
+					if (ctx->callback(ctx,
+						      LEJPCB_VAL_STR_END) < 0) {
+						ret = LEJP_REJECT_CALLBACK;
+						goto reject;
+					}
+				}
+				/* pop */
+				ctx->sp--;
+				break;
+			}
+			if (c == '\\') {
+				ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC;
+				break;
+			}
+			if (c < ' ') {/* "control characters" not allowed */
+				ret = LEJP_REJECT_MP_ILLEGAL_CTRL;
+				goto reject;
+			}
+			goto emit_string_char;
+
+		case LEJP_MP_STRING_ESC:
+			if (c == 'u') {
+				ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC_U1;
+				ctx->uni = 0;
+				break;
+			}
+			for (n = 0; n < sizeof(esc_char); n++) {
+				if (c != esc_char[n])
+					continue;
+				/* found it */
+				c = esc_tran[n];
+				ctx->st[ctx->sp].s = LEJP_MP_STRING;
+				goto emit_string_char;
+			}
+			ret = LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC;
+			/* illegal escape char */
+			goto reject;
+
+		case LEJP_MP_STRING_ESC_U1:
+		case LEJP_MP_STRING_ESC_U2:
+		case LEJP_MP_STRING_ESC_U3:
+		case LEJP_MP_STRING_ESC_U4:
+			ctx->uni <<= 4;
+			if (c >= '0' && c <= '9')
+				ctx->uni |= c - '0';
+			else
+				if (c >= 'a' && c <= 'f')
+					ctx->uni = c - 'a' + 10;
+				else
+					if (c >= 'A' && c <= 'F')
+						ctx->uni = c - 'A' + 10;
+					else {
+						ret = LEJP_REJECT_ILLEGAL_HEX;
+						goto reject;
+					}
+			ctx->st[ctx->sp].s++;
+			switch (s) {
+			case LEJP_MP_STRING_ESC_U2:
+				if (ctx->uni < 0x08)
+					break;
+				/*
+				 * 0x08-0xff (0x0800 - 0xffff)
+				 * emit 3-byte UTF-8
+				 */
+				c = 0xe0 | ((ctx->uni >> 4) & 0xf);
+				goto emit_string_char;
+
+			case LEJP_MP_STRING_ESC_U3:
+				if (ctx->uni >= 0x080) {
+					/*
+					 * 0x080 - 0xfff (0x0800 - 0xffff)
+					 * middle 3-byte seq
+					 * send ....XXXXXX..
+					 */
+					c = 0x80 | ((ctx->uni >> 2) & 0x3f);
+					goto emit_string_char;
+				}
+				if (ctx->uni < 0x008)
+					break;
+				/*
+				 * 0x008 - 0x7f (0x0080 - 0x07ff)
+				 * start 2-byte seq
+				 */
+				c = 0xc0 | (ctx->uni >> 2);
+				goto emit_string_char;
+
+			case LEJP_MP_STRING_ESC_U4:
+				if (ctx->uni >= 0x0080)
+					/* end of 2 or 3-byte seq */
+					c = 0x80 | (ctx->uni & 0x3f);
+				else
+					/* literal */
+					c = (unsigned char)ctx->uni;
+
+				ctx->st[ctx->sp].s = LEJP_MP_STRING;
+				goto emit_string_char;
+			default:
+				break;
+			}
+			break;
+
+		case LEJP_MP_DELIM:
+			if (c != ':') {
+				ret = LEJP_REJECT_MP_DELIM_MISSING_COLON;
+				goto reject;
+			}
+			ctx->st[ctx->sp].s = LEJP_MP_VALUE;
+			ctx->path[ctx->ppos] = '\0';
+
+			lejp_check_path_match(ctx);
+			ctx->callback(ctx, LEJPCB_PAIR_NAME);
+			break;
+
+		case LEJP_MP_VALUE:
+			if (c >= '0' && c <= '9') {
+				ctx->npos = 0;
+				ctx->dcount = 0;
+				ctx->f = 0;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
+				goto redo_character;
+			}
+			switch (c) {
+			case'\"':
+				/* push */
+				ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
+				c = LEJP_MP_STRING;
+				ctx->npos = 0;
+				ctx->buf[0] = '\0';
+				ctx->callback(ctx, LEJPCB_VAL_STR_START);
+				goto add_stack_level;
+
+			case '{':
+				/* push */
+				ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
+				c = LEJP_MEMBERS;
+				lejp_check_path_match(ctx);
+				ctx->callback(ctx, LEJPCB_OBJECT_START);
+				ctx->path_match = 0;
+				goto add_stack_level;
+
+			case '[':
+				/* push */
+				ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END;
+				c = LEJP_MP_VALUE;
+				ctx->path[ctx->ppos++] = '[';
+				ctx->path[ctx->ppos++] = ']';
+				ctx->path[ctx->ppos] = '\0';
+				ctx->callback(ctx, LEJPCB_ARRAY_START);
+				ctx->i[ctx->ipos++] = 0;
+				if (ctx->ipos > ARRAY_SIZE(ctx->i)) {
+					ret = LEJP_REJECT_MP_DELIM_ISTACK;
+					goto reject;
+				}
+				goto add_stack_level;
+
+			case 't': /* true */
+				ctx->uni = 0;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
+				break;
+
+			case 'f':
+				ctx->uni = 4;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
+				break;
+
+			case 'n':
+				ctx->uni = 4 + 5;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK;
+				break;
+			default:
+				ret = LEJP_REJECT_MP_DELIM_BAD_VALUE_START;
+				goto reject;
+			}
+			break;
+
+		case LEJP_MP_VALUE_NUM_INT:
+			if (!ctx->npos && c == '-') {
+				ctx->f |= LEJP_SEEN_MINUS;
+				goto append_npos;
+			}
+
+			if (ctx->dcount < 10 && c >= '0' && c <= '9') {
+				if (ctx->f & LEJP_SEEN_POINT)
+					ctx->f |= LEJP_SEEN_POST_POINT;
+				ctx->dcount++;
+				goto append_npos;
+			}
+			if (c == '.') {
+				if (ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) {
+					ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
+					goto reject;
+				}
+				ctx->f |= LEJP_SEEN_POINT;
+				goto append_npos;
+			}
+			/*
+			 * before exponent, if we had . we must have had at
+			 * least one more digit
+			 */
+			if ((ctx->f &
+				(LEJP_SEEN_POINT | LEJP_SEEN_POST_POINT)) ==
+							      LEJP_SEEN_POINT) {
+				ret = LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC;
+				goto reject;
+			}
+			if (c == 'e' || c == 'E') {
+				if (ctx->f & LEJP_SEEN_EXP) {
+					ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
+					goto reject;
+				}
+				ctx->f |= LEJP_SEEN_EXP;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_EXP;
+				goto append_npos;
+			}
+			/* if none of the above, did we even have a number? */
+			if (!ctx->dcount) {
+				ret = LEJP_REJECT_MP_VAL_NUM_FORMAT;
+				goto reject;
+			}
+
+			ctx->buf[ctx->npos] = '\0';
+			if (ctx->f & LEJP_SEEN_POINT)
+				ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT);
+			else
+				ctx->callback(ctx, LEJPCB_VAL_NUM_INT);
+
+			/* then this is the post-number character, loop */
+			ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
+			goto redo_character;
+
+		case LEJP_MP_VALUE_NUM_EXP:
+			ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT;
+			if (c >= '0' && c <= '9')
+				goto redo_character;
+			if (c == '+' || c == '-')
+				goto append_npos;
+			ret = LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP;
+			goto reject;
+
+		case LEJP_MP_VALUE_TOK: /* true, false, null */
+			if (c != tokens[ctx->uni]) {
+				ret = LEJP_REJECT_MP_VAL_TOK_UNKNOWN;
+				goto reject;
+			}
+			ctx->uni++;
+			if (tokens[ctx->uni] != ' ')
+				break;
+			switch (ctx->uni) {
+			case 3:
+				ctx->buf[0] = '1';
+				ctx->buf[1] = '\0';
+				ctx->callback(ctx, LEJPCB_VAL_TRUE);
+				break;
+			case 8:
+				ctx->buf[0] = '0';
+				ctx->buf[1] = '\0';
+				ctx->callback(ctx, LEJPCB_VAL_FALSE);
+				break;
+			case 12:
+				ctx->buf[0] = '\0';
+				ctx->callback(ctx, LEJPCB_VAL_NULL);
+				break;
+			}
+			ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
+			break;
+
+		case LEJP_MP_COMMA_OR_END:
+			ctx->path[ctx->ppos] = '\0';
+			if (c == ',') {
+				/* increment this stack level's index */
+				ctx->st[ctx->sp].s = LEJP_M_P;
+				if (!ctx->sp) {
+					ctx->ppos = 0;
+					/*
+					 * since we came back to root level,
+					 * no path can still match
+					 */
+					ctx->path_match = 0;
+					break;
+				}
+				ctx->ppos = ctx->st[ctx->sp - 1].p;
+				ctx->path[ctx->ppos] = '\0';
+				if (ctx->path_match &&
+					       ctx->ppos <= ctx->path_match_len)
+					/*
+					 * we shrank the path to be
+					 * smaller than the matching point
+					 */
+					ctx->path_match = 0;
+
+				if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END)
+					break;
+				/* top level is definitely an array... */
+				if (ctx->ipos)
+					ctx->i[ctx->ipos - 1]++;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE;
+				break;
+			}
+			if (c == ']') {
+				if (!ctx->sp) {
+					ret = LEJP_REJECT_MP_C_OR_E_UNDERF;
+					goto reject;
+				}
+				/* pop */
+				ctx->sp--;
+				if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) {
+					ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY;
+					goto reject;
+				}
+				/* drop the path [n] bit */
+				ctx->ppos = ctx->st[ctx->sp - 1].p;
+				ctx->ipos = ctx->st[ctx->sp - 1].i;
+				ctx->path[ctx->ppos] = '\0';
+				if (ctx->path_match &&
+					       ctx->ppos <= ctx->path_match_len)
+					/*
+					 * we shrank the path to be
+					 * smaller than the matching point
+					 */
+					ctx->path_match = 0;
+
+				/* do LEJP_MP_ARRAY_END processing */
+				goto redo_character;
+			}
+			if (c == '}') {
+				if (ctx->sp == 0) {
+					lejp_check_path_match(ctx);
+					ctx->callback(ctx, LEJPCB_OBJECT_END);
+					ctx->callback(ctx, LEJPCB_COMPLETE);
+					/* done, return unused amount */
+					return len;
+				}
+				/* pop */
+				ctx->sp--;
+				ctx->ppos = ctx->st[ctx->sp - 1].p;
+				ctx->ipos = ctx->st[ctx->sp - 1].i;
+				ctx->path[ctx->ppos] = '\0';
+				if (ctx->path_match &&
+					       ctx->ppos <= ctx->path_match_len)
+					/*
+					 * we shrank the path to be
+					 * smaller than the matching point
+					 */
+					ctx->path_match = 0;
+				lejp_check_path_match(ctx);
+				ctx->callback(ctx, LEJPCB_OBJECT_END);
+				break;
+			}
+
+			ret = LEJP_REJECT_MP_C_OR_E_NEITHER;
+			goto reject;
+
+		case LEJP_MP_ARRAY_END:
+			ctx->path[ctx->ppos] = '\0';
+			if (c == ',') {
+				/* increment this stack level's index */
+				if (ctx->ipos)
+					ctx->i[ctx->ipos - 1]++;
+				ctx->st[ctx->sp].s = LEJP_MP_VALUE;
+				if (ctx->sp)
+					ctx->ppos = ctx->st[ctx->sp - 1].p;
+				ctx->path[ctx->ppos] = '\0';
+				break;
+			}
+			if (c != ']') {
+				ret = LEJP_REJECT_MP_ARRAY_END_MISSING;
+				goto reject;
+			}
+
+			ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END;
+			ctx->callback(ctx, LEJPCB_ARRAY_END);
+			break;
+		}
+
+		continue;
+
+emit_string_char:
+		if (!ctx->sp || ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) {
+			/* assemble the string value into chunks */
+			ctx->buf[ctx->npos++] = c;
+			if (ctx->npos == sizeof(ctx->buf) - 1) {
+				ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK);
+				ctx->npos = 0;
+			}
+			continue;
+		}
+		/* name part of name:value pair */
+		ctx->path[ctx->ppos++] = c;
+		continue;
+
+add_stack_level:
+		/* push on to the object stack */
+		if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END &&
+				ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END)
+			ctx->path[ctx->ppos++] = '.';
+
+		ctx->st[ctx->sp].p = ctx->ppos;
+		ctx->st[ctx->sp].i = ctx->ipos;
+		if (++ctx->sp == ARRAY_SIZE(ctx->st)) {
+			ret = LEJP_REJECT_STACK_OVERFLOW;
+			goto reject;
+		}
+		ctx->path[ctx->ppos] = '\0';
+		ctx->st[ctx->sp].s = c;
+		ctx->st[ctx->sp].b = 0;
+		continue;
+
+append_npos:
+		if (ctx->npos >= sizeof(ctx->buf)) {
+			ret = LEJP_REJECT_NUM_TOO_LONG;
+			goto reject;
+		}
+		ctx->buf[ctx->npos++] = c;
+		continue;
+
+redo_character:
+		json--;
+		len++;
+	}
+
+	return LEJP_CONTINUE;
+
+reject:
+	ctx->callback(ctx, LEJPCB_FAILED);
+	return ret;
+}
diff --git a/lwsws/lejp.h b/lwsws/lejp.h
new file mode 100644
index 0000000000000000000000000000000000000000..455c203e067f121e1f83176d2444f45fd8cbc1a8
--- /dev/null
+++ b/lwsws/lejp.h
@@ -0,0 +1,227 @@
+
+struct lejp_ctx;
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0]))
+#endif
+#define LEJP_FLAG_WS_KEEP 64
+#define LEJP_FLAG_WS_COMMENTLINE 32
+
+enum lejp_states {
+	LEJP_IDLE = 0,
+	LEJP_MEMBERS = 1,
+	LEJP_M_P = 2,
+	LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3,
+	LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4,
+	LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5,
+	LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6,
+	LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7,
+	LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8,
+	LEJP_MP_DELIM = 9,
+	LEJP_MP_VALUE = 10,
+	LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11,
+	LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12,
+	LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13,
+	LEJP_MP_COMMA_OR_END = 14,
+	LEJP_MP_ARRAY_END = 15,
+};
+
+enum lejp_reasons {
+	LEJP_CONTINUE = -1,
+	LEJP_REJECT_IDLE_NO_BRACE = -2,
+	LEJP_REJECT_MEMBERS_NO_CLOSE = -3,
+	LEJP_REJECT_MP_NO_OPEN_QUOTE = -4,
+	LEJP_REJECT_MP_STRING_UNDERRUN = -5,
+	LEJP_REJECT_MP_ILLEGAL_CTRL = -6,
+	LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7,
+	LEJP_REJECT_ILLEGAL_HEX = -8,
+	LEJP_REJECT_MP_DELIM_MISSING_COLON = -9,
+	LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10,
+	LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11,
+	LEJP_REJECT_MP_VAL_NUM_FORMAT = -12,
+	LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13,
+	LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14,
+	LEJP_REJECT_MP_C_OR_E_UNDERF = -15,
+	LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16,
+	LEJP_REJECT_MP_ARRAY_END_MISSING = -17,
+	LEJP_REJECT_STACK_OVERFLOW = -18,
+	LEJP_REJECT_MP_DELIM_ISTACK = -19,
+	LEJP_REJECT_NUM_TOO_LONG = -20,
+	LEJP_REJECT_MP_C_OR_E_NEITHER = -21,
+	LEJP_REJECT_UNKNOWN = -22,
+	LEJP_REJECT_CALLBACK = -23
+};
+
+#define LEJP_FLAG_CB_IS_VALUE 64
+
+enum lejp_callbacks {
+	LEJPCB_CONSTRUCTED	= 0,
+	LEJPCB_DESTRUCTED	= 1,
+
+	LEJPCB_START		= 2,
+	LEJPCB_COMPLETE		= 3,
+	LEJPCB_FAILED		= 4,
+
+	LEJPCB_PAIR_NAME	= 5,
+
+	LEJPCB_VAL_TRUE		= LEJP_FLAG_CB_IS_VALUE | 6,
+	LEJPCB_VAL_FALSE	= LEJP_FLAG_CB_IS_VALUE | 7,
+	LEJPCB_VAL_NULL		= LEJP_FLAG_CB_IS_VALUE | 8,
+	LEJPCB_VAL_NUM_INT	= LEJP_FLAG_CB_IS_VALUE | 9,
+	LEJPCB_VAL_NUM_FLOAT	= LEJP_FLAG_CB_IS_VALUE | 10,
+	LEJPCB_VAL_STR_START	= 11, /* notice handle separately */
+	LEJPCB_VAL_STR_CHUNK	= LEJP_FLAG_CB_IS_VALUE | 12,
+	LEJPCB_VAL_STR_END	= LEJP_FLAG_CB_IS_VALUE | 13,
+
+	LEJPCB_ARRAY_START	= 14,
+	LEJPCB_ARRAY_END	= 15,
+
+	LEJPCB_OBJECT_START	= 16,
+	LEJPCB_OBJECT_END	= 17
+};
+
+/**
+ * _lejp_callback() - User parser actions
+ * @ctx:	LEJP context
+ * @reason:	Callback reason
+ *
+ *	Your user callback is associated with the context at construction time,
+ *	and receives calls as the parsing progresses.
+ *
+ *	All of the callbacks may be ignored and just return 0.
+ *
+ *	The reasons it might get called, found in @reason, are:
+ *
+ *  LEJPCB_CONSTRUCTED:  The context was just constructed... you might want to
+ *		perform one-time allocation for the life of the context.
+ *
+ *  LEJPCB_DESTRUCTED:	The context is being destructed... if you made any
+ *		allocations at construction-time, you can free them now
+ *
+ *  LEJPCB_START:	Parsing is beginning at the first byte of input
+ *
+ *  LEJPCB_COMPLETE:	Parsing has completed successfully.  You'll get a 0 or
+ *			positive return code from lejp_parse indicating the
+ *			amount of unused bytes left in the input buffer
+ *
+ *  LEJPCB_FAILED:	Parsing failed.  You'll get a negative error code
+ *  			returned from lejp_parse
+ *
+ *  LEJPCB_PAIR_NAME:	When a "name":"value" pair has had the name parsed,
+ *			this callback occurs.  You can find the new name at
+ *			the end of ctx->path[]
+ *
+ *  LEJPCB_VAL_TRUE:	The "true" value appeared
+ *
+ *  LEJPCB_VAL_FALSE:	The "false" value appeared
+ *
+ *  LEJPCB_VAL_NULL:	The "null" value appeared
+ *
+ *  LEJPCB_VAL_NUM_INT:	A string representing an integer is in ctx->buf
+ *
+ *  LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf
+ *
+ *  LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet
+ *
+ *  LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in
+ *			ctx->buf, which is as much as we can buffer, so we are
+ *			spilling it.  If all your strings are less than
+ *			LEJP_STRING_CHUNK - 1 bytes, you will never see this
+ *			callback.
+ *
+ *  LEJPCB_VAL_STR_END:	String parsing has completed, the last chunk of the
+ *			string is in ctx->buf.
+ *
+ *  LEJPCB_ARRAY_START:	An array started
+ *
+ *  LEJPCB_ARRAY_END:	An array ended
+ *
+ *  LEJPCB_OBJECT_START: An object started
+ *
+ *  LEJPCB_OBJECT_END:	An object ended
+ */
+extern char _lejp_callback(struct lejp_ctx *ctx, char reason);
+
+typedef char (*lejp_callback)(struct lejp_ctx *ctx, char reason);
+
+#ifndef LEJP_MAX_DEPTH
+#define LEJP_MAX_DEPTH 12
+#endif
+#ifndef LEJP_MAX_INDEX_DEPTH
+#define LEJP_MAX_INDEX_DEPTH 5
+#endif
+#ifndef LEJP_MAX_PATH
+#define LEJP_MAX_PATH 128
+#endif
+#ifndef LEJP_STRING_CHUNK
+/* must be >= 30 to assemble floats */
+#define LEJP_STRING_CHUNK 64
+#endif
+
+enum num_flags {
+	LEJP_SEEN_MINUS = (1 << 0),
+	LEJP_SEEN_POINT = (1 << 1),
+	LEJP_SEEN_POST_POINT = (1 << 2),
+	LEJP_SEEN_EXP = (1 << 3)
+};
+
+struct _lejp_stack {
+	char s; /* lejp_state stack*/
+	char p;	/* path length */
+	char i; /* index array length */
+	char b; /* user bitfield */
+};
+
+struct lejp_ctx {
+
+	/* sorted by type for most compact alignment
+	 *
+	 * pointers
+	 */
+
+	char (*callback)(struct lejp_ctx *ctx, char reason);
+	void *user;
+	const char * const *paths;
+
+	/* arrays */
+
+	struct _lejp_stack st[LEJP_MAX_DEPTH];
+	unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */
+	char path[LEJP_MAX_PATH];
+	char buf[LEJP_STRING_CHUNK];
+
+	/* int */
+
+	unsigned int line;
+
+	/* short */
+
+	unsigned short uni;
+
+	/* char */
+
+	unsigned char npos;
+	unsigned char dcount;
+	unsigned char f;
+	unsigned char sp; /* stack head */
+	unsigned char ipos; /* index stack depth */
+	unsigned char ppos;
+	unsigned char count_paths;
+	unsigned char path_match;
+	unsigned char path_match_len;
+};
+
+extern void
+lejp_construct(struct lejp_ctx *ctx,
+	       char (*callback)(struct lejp_ctx *ctx, char reason), void *user,
+	       const char * const *paths, unsigned char paths_count);
+
+extern void
+lejp_destruct(struct lejp_ctx *ctx);
+
+extern int
+lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len);
+
+extern void
+lejp_change_callback(struct lejp_ctx *ctx,
+		       char (*callback)(struct lejp_ctx *ctx, char reason));
diff --git a/lwsws/lwsws.h b/lwsws/lwsws.h
new file mode 100644
index 0000000000000000000000000000000000000000..a1936c92a6241a88b05de94a4e1510c8fca5f643
--- /dev/null
+++ b/lwsws/lwsws.h
@@ -0,0 +1,62 @@
+#if defined(_WIN32) && defined(EXTERNAL_POLL)
+#define WINVER 0x0600
+#define _WIN32_WINNT 0x0600
+#define poll(fdArray, fds, timeout)  WSAPoll((LPWSAPOLLFD)(fdArray), (ULONG)(fds), (INT)(timeout))
+#endif
+
+#include "lws_config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <getopt.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <assert.h>
+#ifndef _WIN32
+#include <dirent.h>
+#endif
+
+#include "../lib/libwebsockets.h"
+#include "lejp.h"
+
+#ifdef _WIN32
+#include <io.h>
+#include "gettimeofday.h"
+#else
+#include <syslog.h>
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+
+extern void test_server_lock(int care);
+extern void test_server_unlock(int care);
+
+#ifndef __func__
+#define __func__ __FUNCTION__
+#endif
+
+struct per_session_data__http {
+	lws_filefd_type fd;
+#ifdef LWS_WITH_CGI
+	struct lws_cgi_args args;
+#endif
+#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT)
+	int reason_bf;
+#endif
+	unsigned int client_finished:1;
+};
+
+extern int
+lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d,
+		char **config_strings, int *len);
+
+extern int
+lwsws_get_config_vhosts(struct lws_context *context,
+			struct lws_context_creation_info *info, const char *d,
+			char **config_strings, int *len);
+
+extern int
+callback_http(struct lws *wsi, enum lws_callback_reasons reason,
+	      void *user, void *in, size_t len);
diff --git a/lwsws/main.c b/lwsws/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..741fe7a319efdd89361545a787969bf0a23d0f0a
--- /dev/null
+++ b/lwsws/main.c
@@ -0,0 +1,238 @@
+/*
+ * libwebsockets web server application
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * 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:
+ * version 2.1 of the License.
+ *
+ * This library 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301  USA
+ */
+
+#include "lwsws.h"
+
+int debug_level = 7;
+
+volatile int force_exit = 0;
+struct lws_context *context;
+
+static char *config_dir = "/etc/lwsws/conf.d";
+
+/*
+ * strings and objects from the config file parsing are created here
+ */
+#define LWSWS_CONFIG_STRING_SIZE (32 * 1024)
+char config_strings[LWSWS_CONFIG_STRING_SIZE];
+
+/* singlethreaded version --> no locks */
+
+void test_server_lock(int care)
+{
+}
+void test_server_unlock(int care)
+{
+}
+
+
+enum demo_protocols {
+	/* always first */
+	PROTOCOL_HTTP = 0,
+
+	/* always last */
+	DEMO_PROTOCOL_COUNT
+};
+
+/* list of supported protocols and callbacks */
+
+static struct lws_protocols protocols[] = {
+	/* first protocol must always be HTTP handler */
+	{
+		"http-only",		/* name */
+		callback_http,		/* callback */
+		sizeof (struct per_session_data__http),	/* per_session_data_size */
+		0,			/* max frame size / rx buffer */
+	},
+};
+
+void sighandler(int sig)
+{
+	force_exit = 1;
+	lws_cancel_service(context);
+}
+
+static const struct lws_extension exts[] = {
+	{
+		"permessage-deflate",
+		lws_extension_callback_pm_deflate,
+		"permessage-deflate"
+	},
+	{ NULL, NULL, NULL /* terminator */ }
+};
+
+static struct option options[] = {
+	{ "help",	no_argument,		NULL, 'h' },
+	{ "debug",	required_argument,	NULL, 'd' },
+	{ "configdir",  required_argument,	NULL, 'c' },
+#ifndef LWS_NO_DAEMONIZE
+	{ "daemonize", 	no_argument,		NULL, 'D' },
+#endif
+	{ NULL, 0, 0, 0 }
+};
+
+#ifdef LWS_USE_LIBUV
+void signal_cb(uv_signal_t *watcher, int signum)
+{
+	lwsl_err("Signal %d caught, exiting...\n", watcher->signum);
+	switch (watcher->signum) {
+	case SIGTERM:
+	case SIGINT:
+		break;
+	default:
+		signal(SIGABRT, SIG_DFL);
+		abort();
+		break;
+	}
+	lws_libuv_stop(context);
+}
+#endif
+
+int main(int argc, char **argv)
+{
+	struct lws_context_creation_info info;
+	char *cs = config_strings;
+	int opts = 0, cs_len = sizeof(config_strings) - 1;
+	int n = 0;
+#ifndef _WIN32
+	int syslog_options = LOG_PID | LOG_PERROR;
+#endif
+#ifndef LWS_NO_DAEMONIZE
+ 	int daemonize = 0;
+#endif
+
+	memset(&info, 0, sizeof info);
+
+	while (n >= 0) {
+		n = getopt_long(argc, argv, "hd:c:D", options, NULL);
+		if (n < 0)
+			continue;
+		switch (n) {
+#ifndef LWS_NO_DAEMONIZE
+		case 'D':
+			daemonize = 1;
+			#ifndef _WIN32
+			syslog_options &= ~LOG_PERROR;
+			#endif
+			break;
+#endif
+		case 'd':
+			debug_level = atoi(optarg);
+			break;
+		case 'c':
+			strncpy(config_dir, optarg, sizeof(config_dir) - 1);
+			config_dir[sizeof(config_dir) - 1] = '\0';
+			break;
+		case 'h':
+			fprintf(stderr, "Usage: lwsws [-c <config dir>] "
+					"[-d <log bitfield>] [-D] [--help]\n");
+			exit(1);
+		}
+	}
+
+#if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
+	/*
+	 * normally lock path would be /var/lock/lwsts or similar, to
+	 * simplify getting started without having to take care about
+	 * permissions or running as root, set to /tmp/.lwsts-lock
+	 */
+	if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
+		fprintf(stderr, "Failed to daemonize\n");
+		return 10;
+	}
+#endif
+
+	signal(SIGINT, sighandler);
+
+#ifndef _WIN32
+	/* we will only try to log things according to our debug_level */
+	setlogmask(LOG_UPTO (LOG_DEBUG));
+	openlog("lwsws", syslog_options, LOG_DAEMON);
+#endif
+
+	lws_set_log_level(debug_level, lwsl_emit_syslog);
+
+	lwsl_notice("lwsws libwebsockets web server - license GPL2.1\n");
+	lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
+
+	memset(&info, 0, sizeof(info));
+
+	info.max_http_header_pool = 16;
+	info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 |
+		LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
+#ifdef LWS_USE_LIBUV
+	info.options |= LWS_SERVER_OPTION_LIBUV;
+#endif
+
+	lwsl_notice("Using config dir: \"%s\"\n", config_dir);
+
+	/*
+	 *  first go through the config for creating the outer context
+	 */
+
+	if (lwsws_get_config_globals(&info, config_dir, &cs, &cs_len))
+		goto bail;
+
+	context = lws_create_context(&info);
+	if (context == NULL) {
+		lwsl_err("libwebsocket init failed\n");
+		return -1;
+	}
+
+	/*
+	 * then create the vhosts...
+	 *
+	 * protocols and extensions are the global list of possible
+	 * protocols and extensions offered serverwide.  The vhosts
+	 * in the config files enable the ones they want to offer
+	 * per vhost.
+	 *
+	 * The first protocol is always included for http support.
+	 */
+
+	info.protocols = protocols;
+	info.extensions = exts;
+
+	if (lwsws_get_config_vhosts(context, &info, config_dir, &cs, &cs_len))
+		goto bail;
+
+#ifdef LWS_USE_LIBUV
+	lws_uv_sigint_cfg(context, 1, signal_cb);
+	lws_uv_initloop(context, NULL, 0);
+	lws_libuv_run(context, 0);
+#else
+
+	n = 0;
+	while (n >= 0 && !force_exit) {
+		n = lws_service(context, 50);
+	}
+#endif
+
+bail:
+	lws_context_destroy(context);
+	lwsl_notice("lwsws exited cleanly\n");
+
+#ifndef _WIN32
+	closelog();
+#endif
+
+	return 0;
+}