diff --git a/.travis.yml b/.travis.yml
index b61a7650f8b0e32d63a0f0520de9049d81a4623a..56dd54fd1404ebeaabf4b50b0c86f59930b844b2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ env:
   global:
     - secure: "KhAdQ9ja+LBObWNQTYO7Df5J4DyOih6S+eerDMu8UPSO+CoWV2pWoQzbOfocjyOscGOwC+2PrrHDNZyGfqkCLDXg1BxynXPCFerHC1yc2IajvKpGXmAAygNIvp4KACDfGv/dkXrViqIzr/CdcNaU4vIMHSVb5xkeLi0W1dPnQOI="
   matrix:
+    - LWS_METHOD=lwsws CMAKE_ARGS="-DLWS_WITH_LWSWS=ON"
     - LWS_METHOD=default
     - LWS_METHOD=noserver CMAKE_ARGS="-DLWS_WITHOUT_SERVER=ON"
     - LWS_METHOD=noclient CMAKE_ARGS="-DLWS_WITHOUT_CLIENT=ON"
@@ -18,16 +19,18 @@ env:
 os:
   - linux
   - osx
-language: c
+language: generic
 install:
   - ./travis_install.sh
 script:
   - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then mkdir build && cd build && cmake $CMAKE_ARGS .. && cmake --build .; fi
+sudo: required
+dist: trusty
 addons:
   coverity_scan:
     project:
       name: "warmcat/libwebsockets"
-    notification_email: andy.green@linaro.org
+    notification_email: andy@warmcat.com
     build_command_prepend: "mkdir build && cd build && cmake .."
     build_command:   "cmake --build ."
     branch_pattern: coverity_scan
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f45a70e72cd26aa54495e0361a6885aef2d0efbe..d12af4d78604e8a98a613b29edbc688b8feca6f3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -91,7 +91,14 @@ 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)
+option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF)
+option(LWS_WITH_PLUGINS "Support plugins for protocols and extensions" OFF)
+
+if (LWS_WITH_LWSWS)
+ message(STATUS "LWS_WITH_LWSWS --> Enabling LWS_WITH_PLUGINS and LWS_WITH_LIBUV")
+ set(LWS_WITH_PLUGINS 1)
+ set(LWS_WITH_LIBUV 1)
+endif()
 
 if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING)
 
@@ -101,6 +108,7 @@ set(LWS_WITH_ZLIB OFF)
 set(LWS_WITHOUT_CLIENT ON)
 set(LWS_WITHOUT_TESTAPPS ON)
 set(LWS_WITHOUT_EXTENSIONS ON)
+set(LWS_WITH_PLUGINS OFF)
 set(LWS_MBED3 ON)
 # this implies no pthreads in the lib
 set(LWS_MAX_SMP 1)
@@ -110,6 +118,9 @@ endif()
 if (WIN32)
 # this implies no pthreads in the lib
 set(LWS_MAX_SMP 1)
+
+# plugin stuff not implemented in win32 plat
+set (LWS_WITH_PLUGINS OFF)
 endif()
 
 
@@ -926,6 +937,10 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		message("OpenSSL executable: ${OPENSSL_EXECUTABLE}")
 	endif()
 
+	if (UNIX AND LWS_WITH_PLUGINS)
+		set(CMAKE_C_FLAGS "-fPIC ${CMAKE_C_FLAGS}")
+		target_link_libraries(websockets dl)
+	endif()
 
 	if (NOT LWS_WITHOUT_SERVER)
 		#
@@ -1115,6 +1130,54 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 		endif()
 
 	endif(NOT LWS_WITHOUT_CLIENT)
+	
+	
+	if (LWS_WITH_PLUGINS AND LWS_WITH_SHARED)
+		macro(create_plugin PLUGIN_NAME MAIN_SRC)
+
+		set(PLUGIN_SRCS ${MAIN_SRC})
+
+		if (WIN32)
+			list(APPEND PLUGIN_SRCSset_property
+				${WIN32_HELPERS_PATH}/getopt.c
+				${WIN32_HELPERS_PATH}/getopt_long.c
+				${WIN32_HELPERS_PATH}/gettimeofday.c
+			)
+
+			list(APPEND PLUGIN_HDR
+				${WIN32_HELPERS_PATH}/getopt.h
+				${WIN32_HELPERS_PATH}/gettimeofday.h
+			)
+		endif(WIN32)
+
+		source_group("Headers Private"   FILES ${PLUGIN_HDR})
+		source_group("Sources"   FILES ${PLUGIN_SRCS})
+		add_library(${PLUGIN_NAME} SHARED ${PLUGIN_SRCS} ${PLUGIN_HDR})
+		
+		target_link_libraries(${PLUGIN_NAME} websockets)
+		add_dependencies(${PLUGIN_NAME} websockets)
+
+		# Set test app specific defines.
+		set_property(TARGET ${PLUGIN_NAME}
+			     PROPERTY COMPILE_DEFINITIONS
+			     INSTALL_DATADIR="${CMAKE_INSTALL_PREFIX}/plugins"
+		)
+
+#		set_target_properties(${PLUGIN_NAME}
+#			PROPERTIES
+#			OUTPUT_NAME ${PLUGIN_NAME})
+
+		list(APPEND PLUGINS_LIST ${PLUGIN_NAME})
+		endmacro()
+		
+
+		create_plugin(protocol_dumb_increment
+			      "plugins/protocol_dumb_increment.c")
+		create_plugin(protocol_lws_mirror
+			      "plugins/protocol_lws_mirror.c")
+		create_plugin(protocol_lws_status
+			      "plugins/protocol_lws_status.c")
+	endif(LWS_WITH_PLUGINS AND LWS_WITH_SHARED)
 
 	#
 	# Copy OpenSSL dlls to the output directory on Windows.
@@ -1318,6 +1381,15 @@ if (LWS_WITH_CGI)
 	endif()
 endif()
 
+# plugins
+
+if (LWS_WITH_PLUGINS)
+	install(TARGETS ${PLUGINS_LIST}
+		PERMISSIONS  OWNER_EXECUTE GROUP_EXECUTE WORLD_EXECUTE OWNER_READ GROUP_READ WORLD_READ
+		DESTINATION share/libwebsockets-test-server/plugins
+		COMPONENT plugins)
+endif()
+
 # Install the LibwebsocketsConfig.cmake and LibwebsocketsConfigVersion.cmake
 install(FILES
                "${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/LibwebsocketsConfig.cmake"
@@ -1379,6 +1451,7 @@ message(" LWS_WITH_CGI = ${LWS_WITH_CGI}")
 message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}")
 message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}")
 message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}")
+message(" PLUGINS = ${PLUGINS_LIST}")
 message("---------------------------------------------------------------------")
 
 # These will be available to parent projects including libwebsockets using add_subdirectory()
diff --git a/README.lwsws.md b/README.lwsws.md
index 460e0e56512acfc8bc5edc5c02ef694f79e449dd..ff559b9e049ee3a2873ee283be0125af6266e9e3 100644
--- a/README.lwsws.md
+++ b/README.lwsws.md
@@ -4,6 +4,14 @@ Libwebsockets Web Server
 lwsws is an implementation of a very lightweight, ws-capable generic web
 server, which uses libwebsockets to implement everything underneath.
 
+Build
+-----
+
+Just enable -DLWS_WITH_LWSWS=1 at cmake-time.
+
+It enables libuv and plugin support automatically.
+
+
 Configuration
 -------------
 
@@ -94,10 +102,12 @@ 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.
+listening socket on the appropriate port.
+
+They may also 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
@@ -107,4 +117,34 @@ 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
+supported.
+
+
+Plugins
+-------
+
+Protcols and extensions may also be provided from "plugins", these are
+lightweight dynamic libraries.  They are scanned for at init time, and
+any protocols and extensions found are added to the list given at context
+creation time.
+
+Protocols receive init (LWS_CALLBACK_PROTOCOL_INIT) and destruction
+(LWS_CALLBACK_PROTOCOL_DESTROY) callbacks per-vhost, and there are arrangements
+they can make per-vhost allocations and get hold of the correct pointer from
+the wsi at the callback.
+
+This allows a protocol to choose to strictly segregate data on a per-vhost
+basis, and also allows the plugin to handle its own initialization and
+context storage.
+
+To help that happen conveniently, there are some new apis
+
+ - lws_vhost_get(wsi)
+ - lws_protocol_get(wsi)
+ - lws_callback_on_writable_all_protocol_vhost(vhost, protocol)
+ - lws_protocol_vh_priv_zalloc(vhost, protocol, size)
+ - lws_protocol_vh_priv_get(vhost, protocol)
+ 
+dumb increment, mirror and status protocol plugins are provided as examples.
+
+
diff --git a/lib/context.c b/lib/context.c
index b8d031b08ca4e8daf89d32046efbb97daeb75fb3..46ff505009aa0b9620baac25c355e826bfdc8a8a 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -86,6 +86,86 @@ lws_write_http_mount(struct lws_http_mount *next, struct lws_http_mount **res,
 	return ((char *)store + sizeof(*m)) - (char *)orig;
 }
 
+LWS_VISIBLE void *
+lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot,
+			    int size)
+{
+	int n = 0;
+
+	/* allocate the vh priv array only on demand */
+	if (!vhost->protocol_vh_privs) {
+		vhost->protocol_vh_privs = (void **)lws_zalloc(
+				vhost->count_protocols * sizeof(void *));
+		if (!vhost->protocol_vh_privs)
+			return NULL;
+	}
+
+	while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
+		n++;
+
+	if (n == vhost->count_protocols)
+		return NULL;
+
+	vhost->protocol_vh_privs[n] = lws_zalloc(size);
+	return vhost->protocol_vh_privs[n];
+}
+
+LWS_VISIBLE void *
+lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot)
+{
+	int n = 0;
+
+	if (!vhost->protocol_vh_privs)
+		return NULL;
+
+	while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
+		n++;
+
+	if (n == vhost->count_protocols) {
+		lwsl_err("%s: unknown protocol %p\n", __func__, prot);
+		return NULL;
+	}
+
+	return vhost->protocol_vh_privs[n];
+}
+
+int
+lws_protocol_init(struct lws_context *context)
+{
+	struct lws_vhost *vh = context->vhost_list;
+	struct lws wsi;
+	int n;
+
+	memset(&wsi, 0, sizeof(wsi));
+	wsi.context = context;
+
+	while (vh) {
+		wsi.vhost = vh;
+
+		/* initialize supported protocols on this vhost */
+
+		for (n = 0; n < vh->count_protocols; n++) {
+			wsi.protocol = &vh->protocols[n];
+
+			/*
+			 * inform all the protocols that they are doing their one-time
+			 * initialization if they want to.
+			 *
+			 * NOTE the wsi is all zeros except for the context, vh and
+			 * protocol ptrs so lws_get_context(wsi) etc can work
+			 */
+			vh->protocols[n].callback(&wsi,
+				LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0);
+		}
+
+		vh = vh->vhost_next;
+	}
+
+	context->protocol_init_done = 1;
+
+	return 0;
+}
+
 LWS_VISIBLE struct lws_vhost *
 lws_create_vhost(struct lws_context *context,
 		 struct lws_context_creation_info *info,
@@ -93,9 +173,12 @@ lws_create_vhost(struct lws_context *context,
 {
 	struct lws_vhost *vh = lws_zalloc(sizeof(*vh)),
 			 **vh1 = &context->vhost_list;
-	struct lws wsi;
+#ifdef LWS_WITH_PLUGINS
+	struct lws_plugin *plugin = context->plugin_list;
+	struct lws_protocols *lwsp;
+	int m;
+#endif
 	char *p;
-	int n;
 
 	if (!vh)
 		return NULL;
@@ -107,11 +190,38 @@ lws_create_vhost(struct lws_context *context,
 		vh->name = info->vhost_name;
 
 	vh->iface = info->iface;
-	vh->protocols = info->protocols;
 	for (vh->count_protocols = 0;
 	     info->protocols[vh->count_protocols].callback;
 	     vh->count_protocols++)
 		;
+#ifdef LWS_WITH_PLUGINS
+	if (plugin) {
+		/*
+		 * give the vhost a unified list of protocols including the
+		 * ones that came from plugins
+		 */
+		lwsp = lws_zalloc(sizeof(struct lws_protocols) *
+					   (vh->count_protocols +
+					   context->plugin_protocol_count + 1));
+		if (!lwsp)
+			return NULL;
+
+		m = vh->count_protocols;
+		memcpy(lwsp, info->protocols,
+		       sizeof(struct lws_protocols) * m);
+		while (plugin) {
+			memcpy(&lwsp[m], plugin->caps.protocols,
+			       sizeof(struct lws_protocols) *
+			       plugin->caps.count_protocols);
+			m += plugin->caps.count_protocols;
+			vh->count_protocols += plugin->caps.count_protocols;
+			plugin = plugin->list;
+		}
+		vh->protocols = lwsp;
+	} else
+#endif
+		vh->protocols = info->protocols;
+
 
 	vh->mount_list = mounts;
 
@@ -126,7 +236,35 @@ lws_create_vhost(struct lws_context *context,
 	}
 
 #ifndef LWS_NO_EXTENSIONS
-	vh->extensions = info->extensions;
+#ifdef LWS_WITH_PLUGINS
+	if (context->plugin_extension_count) {
+
+		m = 0;
+		while (info->extensions && info->extensions[m].callback)
+			m++;
+
+		/*
+		 * give the vhost a unified list of extensions including the
+		 * ones that came from plugins
+		 */
+		vh->extensions = lws_zalloc(sizeof(struct lws_extension) *
+					   (m +
+					   context->plugin_extension_count + 1));
+		if (!vh->extensions)
+			return NULL;
+
+		memcpy((struct lws_extension *)vh->extensions, info->extensions,
+		       sizeof(struct lws_extension) * m);
+		while (plugin) {
+			memcpy((struct lws_extension *)&vh->extensions[m], plugin->caps.extensions,
+			       sizeof(struct lws_extension) *
+			       plugin->caps.count_extensions);
+			m += plugin->caps.count_extensions;
+			plugin = plugin->list;
+		}
+	} else
+#endif
+		vh->extensions = info->extensions;
 #endif
 
 	vh->listen_port = info->port;
@@ -148,23 +286,6 @@ lws_create_vhost(struct lws_context *context,
 #endif
 	}
 
-	memset(&wsi, 0, sizeof(wsi));
-	wsi.context = context;
-	wsi.vhost = vh;
-
-	/* initialize supported protocols */
-
-	for (n = 0; n < vh->count_protocols; n++)
-		/*
-		 * inform all the protocols that they are doing their one-time
-		 * initialization if they want to.
-		 *
-		 * NOTE the wsi is all zeros except for the context & vh ptrs
-		 * so lws_get_context(wsi) can work in the callback.
-		 */
-		info->protocols[n].callback(&wsi,
-				LWS_CALLBACK_PROTOCOL_INIT, NULL, NULL, 0);
-
 	vh->ka_time = info->ka_time;
 	vh->ka_interval = info->ka_interval;
 	vh->ka_probes = info->ka_probes;
@@ -467,7 +588,7 @@ lws_context_destroy(struct lws_context *context)
 {
 	const struct lws_protocols *protocol = NULL;
 	struct lws_context_per_thread *pt;
-	struct lws_vhost *vh;
+	struct lws_vhost *vh, *vh1;
 	struct lws wsi;
 	int n, m;
 
@@ -514,18 +635,25 @@ lws_context_destroy(struct lws_context *context)
 
 	/*
 	 * inform all the protocols that they are done and will have no more
-	 * callbacks
+	 * callbacks.
+	 *
+	 * We can't free things until after the event loop shuts down.
 	 */
 	vh = context->vhost_list;
 	while (vh) {
+		wsi.vhost = vh;
 		protocol = vh->protocols;
-		if (protocol)
-			while (protocol->callback) {
+		if (protocol) {
+			n = 0;
+			while (n < vh->count_protocols) {
+				wsi.protocol = protocol;
 				protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
 						   NULL, NULL, 0);
 				protocol++;
+				n++;
 			}
-		lws_ssl_SSL_CTX_destroy(vh);
+		}
+
 		vh = vh->vhost_next;
 	}
 
@@ -547,6 +675,39 @@ lws_context_destroy(struct lws_context *context)
 	if (context->pt[0].fds)
 		lws_free_set_NULL(context->pt[0].fds);
 
+	/* free all the vhost allocations */
+
+	vh = context->vhost_list;
+	while (vh) {
+		protocol = vh->protocols;
+		if (protocol) {
+			n = 0;
+			while (n < vh->count_protocols) {
+				if (vh->protocol_vh_privs &&
+				    vh->protocol_vh_privs[n]) {
+					lws_free(vh->protocol_vh_privs[n]);
+					vh->protocol_vh_privs[n] = NULL;
+				}
+				protocol++;
+				n++;
+			}
+		}
+		if (vh->protocol_vh_privs)
+			lws_free(vh->protocol_vh_privs);
+		lws_ssl_SSL_CTX_destroy(vh);
+#ifdef LWS_WITH_PLUGINS
+		if (context->plugin_list)
+			lws_free((void *)vh->protocols);
+#ifndef LWS_NO_EXTENSIONS
+		if (context->plugin_extension_count)
+			lws_free((void *)vh->extensions);
+#endif
+#endif
+		vh1 = vh->vhost_next;
+		lws_free(vh);
+		vh = vh1;
+	}
+
 	lws_plat_context_late_destroy(context);
 
 	lws_free(context);
diff --git a/lib/libev.c b/lib/libev.c
index cb8c3c514a54a27760d45d8fec6531e5cacb44b5..39242bb2947513c9cca1e280acdc3595df133510 100644
--- a/lib/libev.c
+++ b/lib/libev.c
@@ -32,7 +32,7 @@ void lws_feature_status_libev(struct lws_context_creation_info *info)
 static void
 lws_accept_cb(struct ev_loop *loop, struct ev_io *watcher, int revents)
 {
-	struct lws_io_watcher *lws_io = container_of(watcher,
+	struct lws_io_watcher *lws_io = lws_container_of(watcher,
 					struct lws_io_watcher, ev_watcher);
 	struct lws_context *context = lws_io->context;
 	struct lws_pollfd eventfd;
diff --git a/lib/libuv.c b/lib/libuv.c
index 14d3f18bb2057fda7dc6caa7d9413e13dded18c1..3cf4a94a68e93e9ab6f6325851afb0210b29d5ff 100644
--- a/lib/libuv.c
+++ b/lib/libuv.c
@@ -31,9 +31,13 @@ lws_feature_status_libuv(struct lws_context_creation_info *info)
 }
 
 static void
-lws_uv_idle(uv_idle_t *handle)
+lws_uv_idle(uv_idle_t *handle
+#if UV_VERSION_MAJOR == 0
+		, int status
+#endif
+)
 {
-	struct lws_context_per_thread *pt = container_of(handle,
+	struct lws_context_per_thread *pt = lws_container_of(handle,
 					struct lws_context_per_thread, uv_idle);
 
 	lwsl_debug("%s\n", __func__);
@@ -57,9 +61,9 @@ lws_uv_idle(uv_idle_t *handle)
 static void
 lws_io_cb(uv_poll_t *watcher, int status, int revents)
 {
-	struct lws_io_watcher *lws_io = container_of(watcher,
+	struct lws_io_watcher *lws_io = lws_container_of(watcher,
 					struct lws_io_watcher, uv_watcher);
-	struct lws *wsi = container_of(lws_io, struct lws, w_read);
+	struct lws *wsi = lws_container_of(lws_io, struct lws, w_read);
 	struct lws_context *context = lws_io->context;
 	struct lws_pollfd eventfd;
 
@@ -117,9 +121,13 @@ lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint,
 }
 
 static void
-lws_uv_timeout_cb(uv_timer_t *timer)
+lws_uv_timeout_cb(uv_timer_t *timer
+#if UV_VERSION_MAJOR == 0
+		, int status
+#endif
+)
 {
-	struct lws_context_per_thread *pt = container_of(timer,
+	struct lws_context_per_thread *pt = lws_container_of(timer,
 			struct lws_context_per_thread, uv_timeout_watcher);
 
 	lwsl_debug("%s\n", __func__);
@@ -138,7 +146,12 @@ lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi)
 
 	if (!loop) {
 		loop = lws_malloc(sizeof(*loop));
+#if UV_VERSION_MAJOR > 0
 		uv_loop_init(loop);
+#else
+		lwsl_err("This libuv is too old to work...\n");
+		return 1;
+#endif
 		pt->ev_loop_foreign = 0;
 	} else
 		pt->ev_loop_foreign = 1;
@@ -217,9 +230,11 @@ lws_libuv_destroyloop(struct lws_context *context, int tsi)
 		uv_stop(pt->io_loop_uv);
 		uv_walk(pt->io_loop_uv, lws_uv_walk_cb, NULL);
 		while (uv_run(pt->io_loop_uv, UV_RUN_NOWAIT));
+#if UV_VERSION_MAJOR > 0
 		m = uv_loop_close(pt->io_loop_uv);
 		if (m == UV_EBUSY)
 			lwsl_debug("%s: uv_loop_close: UV_EBUSY\n", __func__);
+#endif
 		lws_free(pt->io_loop_uv);
 	}
 }
@@ -298,9 +313,8 @@ lws_libuv_init_fd_table(struct lws_context *context)
 	if (!LWS_LIBUV_ENABLED(context))
 		return 0;
 
-	for (n = 0; n < context->count_threads; n++) {
+	for (n = 0; n < context->count_threads; n++)
 		context->pt[n].w_sigint.context = context;
-	}
 
 	return 1;
 }
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index b83b31437f9e885e46939a7dda889e68a6a97510..995881641e94aa104bf1bcac6cbba8100be6c240 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -707,6 +707,18 @@ lws_context_user(struct lws_context *context)
 	return context->user_space;
 }
 
+LWS_VISIBLE struct lws_vhost *
+lws_vhost_get(struct lws *wsi)
+{
+	return wsi->vhost;
+}
+
+LWS_VISIBLE const struct lws_protocols *
+lws_protocol_get(struct lws *wsi)
+{
+	return wsi->protocol;
+}
+
 
 /**
  * lws_callback_all_protocol() - Callback all connections using
@@ -739,6 +751,39 @@ lws_callback_all_protocol(struct lws_context *context,
 	return 0;
 }
 
+/**
+ * lws_callback_all_protocol_vhost() - Callback all connections using
+ *				the given protocol with the given reason
+ *
+ * @vh:		Vhost whose connections will get callbacks
+ * @protocol:	Which protocol to match
+ * @reason:	Callback reason index
+ */
+
+LWS_VISIBLE int
+lws_callback_all_protocol_vhost(struct lws_vhost *vh,
+			  const struct lws_protocols *protocol, int reason)
+{
+	struct lws_context *context = vh->context;
+	struct lws_context_per_thread *pt = &context->pt[0];
+	unsigned int n, m = context->count_threads;
+	struct lws *wsi;
+
+	while (m--) {
+		for (n = 0; n < pt->fds_count; n++) {
+			wsi = wsi_from_fd(context, pt->fds[n].fd);
+			if (!wsi)
+				continue;
+			if (wsi->vhost == vh && wsi->protocol == protocol)
+				protocol->callback(wsi, reason, wsi->user_space,
+						   NULL, 0);
+		}
+		pt++;
+	}
+
+	return 0;
+}
+
 #if LWS_POSIX
 
 /**
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index 9479fce6ac74a0c596048a587f9b52f62b50813a..5cbaaadcf177f231d3e44c646423bae2a0972155 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -280,6 +280,14 @@ LWS_VISIBLE LWS_EXTERN void lwsl_hexdump(void *buf, size_t len);
 #define lwsl_hexdump(a, b)
 
 #endif
+
+#include <stddef.h>
+
+#ifndef lws_container_of
+#define lws_container_of(P,T,M)	((T *)((char *)(P) - offsetof(T, M)))
+#endif
+
+
 struct lws;
 #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
 
@@ -1293,6 +1301,26 @@ struct lws_extension {
 	 * This is part of the ABI, don't needlessly break compatibility */
 };
 
+#define LWS_PLUGIN_API_MAGIC 180
+
+struct lws_plugin_capability {
+	unsigned int api_magic;	/* caller fills this in, plugin fills rest */
+	const struct lws_protocols *protocols;
+	int count_protocols;
+	const struct lws_extension *extensions;
+	int count_extensions;
+};
+
+typedef int (*lws_plugin_init_func)(struct lws_context *,
+				    struct lws_plugin_capability *);
+typedef int (*lws_plugin_destroy_func)(struct lws_context *);
+struct lws_plugin {
+	struct lws_plugin *list;
+	void *l;
+	char name[64];
+	struct lws_plugin_capability caps;
+};
+
 /*
  * The internal exts are part of the public abi
  * If we add more extensions, publish the callback here  ------v
@@ -1414,6 +1442,7 @@ struct lws_context_creation_info {
 	unsigned int timeout_secs;			/* VH */
 	const char *ecdh_curve;				/* VH */
 	const char *vhost_name;				/* VH */
+	const char *plugins_dir;			/* context */
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
@@ -1505,6 +1534,18 @@ lws_create_vhost(struct lws_context *context,
 		 struct lws_context_creation_info *info,
 		 struct lws_http_mount *mounts);
 
+LWS_VISIBLE struct lws_vhost *
+lws_vhost_get(struct lws *wsi);
+
+LWS_VISIBLE const struct lws_protocols *
+lws_protocol_get(struct lws *wsi);
+
+LWS_VISIBLE void *
+lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot,
+			    int size);
+LWS_VISIBLE void *
+lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot);
+
 LWS_VISIBLE LWS_EXTERN int
 lws_finalize_startup(struct lws_context *context);
 
@@ -1740,10 +1781,18 @@ LWS_VISIBLE LWS_EXTERN int
 lws_callback_on_writable_all_protocol(const struct lws_context *context,
 				      const struct lws_protocols *protocol);
 
+LWS_VISIBLE int
+lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
+				      const struct lws_protocols *protocol);
+
 LWS_VISIBLE LWS_EXTERN int
 lws_callback_all_protocol(struct lws_context *context,
 			  const struct lws_protocols *protocol, int reason);
 
+LWS_VISIBLE int
+lws_callback_all_protocol_vhost(struct lws_vhost *vh,
+			  const struct lws_protocols *protocol, int reason);
+
 LWS_VISIBLE LWS_EXTERN int
 lws_get_socket_fd(struct lws *wsi);
 
diff --git a/lib/lws-plat-unix.c b/lib/lws-plat-unix.c
index 2b89e22bde2cc70dfae8dd2db369a69919bf9106..c726e9e8725ced761d9a12f4aced3cace7a2e86e 100644
--- a/lib/lws-plat-unix.c
+++ b/lib/lws-plat-unix.c
@@ -3,6 +3,10 @@
 #include <pwd.h>
 #include <grp.h>
 
+#include <dlfcn.h>
+#include <dirent.h>
+
+
 /*
  * included from libwebsockets.c for unix builds
  */
@@ -294,6 +298,141 @@ lws_plat_drop_app_privileges(struct lws_context_creation_info *info)
 	}
 }
 
+#ifdef LWS_WITH_PLUGINS
+
+static int filter(const struct dirent *ent)
+{
+	if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
+		return 0;
+
+	return 1;
+}
+
+LWS_VISIBLE int
+lws_plat_plugins_init(struct lws_context * context, const char *d)
+{
+	struct lws_plugin_capability lcaps;
+	struct lws_plugin *plugin;
+	lws_plugin_init_func initfunc;
+	struct dirent **namelist;
+	int n, i, m, ret = 0;
+	char path[256];
+	void *l;
+
+
+	n = scandir(d, &namelist, filter, alphasort);
+	if (n < 0) {
+		lwsl_err("Scandir on %d failed\n", d);
+		return 1;
+	}
+
+	lwsl_notice("  Plugins:\n");
+
+	for (i = 0; i < n; i++) {
+		if (strlen(namelist[i]->d_name) < 7)
+			goto inval;
+
+		lwsl_notice("   %s\n", namelist[i]->d_name);
+
+		snprintf(path, sizeof(path) - 1, "%s/%s", d,
+			 namelist[i]->d_name);
+		l = dlopen(path, RTLD_NOW);
+		if (!l) {
+			lwsl_err("Error loading DSO: %s\n", dlerror());
+			while (i++ < n)
+				free(namelist[i]);
+			goto bail;
+		}
+		/* we could open it, can we get his init function? */
+		m = snprintf(path, sizeof(path) - 1, "init_%s",
+			     namelist[i]->d_name + 3 /* snip lib... */);
+		path[m - 3] = '\0'; /* snip the .so */
+		initfunc = dlsym(l, path);
+		if (!initfunc) {
+			lwsl_err("Failed to get init on %s: %s",
+					namelist[i]->d_name, dlerror());
+			dlclose(l);
+		}
+		lcaps.api_magic = LWS_PLUGIN_API_MAGIC;
+		m = initfunc(context, &lcaps);
+		if (m) {
+			lwsl_err("Initializing %s failed %d\n",
+				namelist[i]->d_name, m);
+			dlclose(l);
+			goto skip;
+		}
+
+		plugin = lws_malloc(sizeof(*plugin));
+		if (!plugin) {
+			lwsl_err("OOM\n");
+			goto bail;
+		}
+		plugin->list = context->plugin_list;
+		context->plugin_list = plugin;
+		strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1);
+		plugin->name[sizeof(plugin->name) - 1] = '\0';
+		plugin->l = l;
+		plugin->caps = lcaps;
+		context->plugin_protocol_count += lcaps.count_protocols;
+		context->plugin_extension_count += lcaps.count_extensions;
+
+		free(namelist[i]);
+		continue;
+
+skip:
+		dlclose(l);
+inval:
+		free(namelist[i]);
+	}
+
+bail:
+	free(namelist);
+
+	return ret;
+}
+
+LWS_VISIBLE int
+lws_plat_plugins_destroy(struct lws_context * context)
+{
+	struct lws_plugin *plugin = context->plugin_list, *p;
+	lws_plugin_destroy_func func;
+	char path[256];
+	int m;
+
+	if (!plugin)
+		return 0;
+
+	lwsl_notice("%s\n", __func__);
+
+	while (plugin) {
+		p = plugin;
+		m = snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + 3);
+		path[m - 3] = '\0';
+		func = dlsym(plugin->l, path);
+		if (!func) {
+			lwsl_err("Failed to get destroy on %s: %s",
+					plugin->name, dlerror());
+			goto next;
+		}
+		m = func(context);
+		if (m)
+			lwsl_err("Initializing %s failed %d\n",
+				plugin->name, m);
+next:
+		dlclose(p->l);
+		plugin = p->list;
+		p->list = NULL;
+		free(p);
+	}
+
+	context->plugin_list = NULL;
+
+	return 0;
+}
+
+#endif
+
+
 static void
 sigpipe_handler(int x)
 {
@@ -326,6 +465,11 @@ lws_plat_context_late_destroy(struct lws_context *context)
 	struct lws_context_per_thread *pt = &context->pt[0];
 	int m = context->count_threads;
 
+#ifdef LWS_WITH_PLUGINS
+	if (context->plugin_list)
+		lws_plat_plugins_destroy(context);
+#endif
+
 	if (context->lws_lookup)
 		lws_free(context->lws_lookup);
 
@@ -513,6 +657,7 @@ _lws_plat_file_write(struct lws *wsi, lws_filefd_type fd, unsigned long *amount,
 	return 0;
 }
 
+
 LWS_VISIBLE int
 lws_plat_init(struct lws_context *context,
 	      struct lws_context_creation_info *info)
@@ -565,5 +710,10 @@ lws_plat_init(struct lws_context *context,
 	context->fops.read	= _lws_plat_file_read;
 	context->fops.write	= _lws_plat_file_write;
 
+#ifdef LWS_WITH_PLUGINS
+	if (info->plugins_dir)
+		lws_plat_plugins_init(context, info->plugins_dir);
+#endif
+
 	return 0;
 }
diff --git a/lib/pollfd.c b/lib/pollfd.c
index 1d359bda8c54cc9b1ab50986b74046e16367887f..7a4f17e489b8258ae222be84723e7df124601913 100644
--- a/lib/pollfd.c
+++ b/lib/pollfd.c
@@ -374,3 +374,37 @@ lws_callback_on_writable_all_protocol(const struct lws_context *context,
 
 	return 0;
 }
+
+
+/**
+ * lws_callback_on_writable_all_protocol_vhost() - Request a callback for
+ *			all connections using the given protocol when it
+ *			becomes possible to write to each socket without
+ *			blocking in turn.
+ *
+ * @vhost:	Only consider connections on this lws_vhost
+ * @protocol:	Protocol whose connections will get callbacks
+ */
+
+LWS_VISIBLE int
+lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost,
+				      const struct lws_protocols *protocol)
+{
+	const struct lws_context *context = vhost->context;
+	const struct lws_context_per_thread *pt = &context->pt[0];
+	unsigned int n, m = context->count_threads;
+	struct lws *wsi;
+
+	while (m--) {
+		for (n = 0; n < pt->fds_count; n++) {
+			wsi = wsi_from_fd(context, pt->fds[n].fd);
+			if (!wsi)
+				continue;
+			if (wsi->vhost == vhost && wsi->protocol == protocol)
+				lws_callback_on_writable(wsi);
+		}
+		pt++;
+	}
+
+	return 0;
+}
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 5ebb97433897389d5ef47d8b7f13bc58149cfded..839eab541c5640915090dd8c3df4e4a7feb5b276 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -261,12 +261,6 @@ typedef unsigned __int64 u_int64_t;
 #endif
 #endif
 
-#include <stddef.h>
-
-#ifndef container_of
-#define container_of(P,T,M)	((T *)((char *)(P) - offsetof(T, M)))
-#endif
-
 #else
 
 #include <sys/stat.h>
@@ -283,11 +277,6 @@ typedef unsigned __int64 u_int64_t;
 #ifdef __cplusplus
 extern "C" {
 #endif
-#include <stddef.h>
-
-#ifndef container_of
-#define container_of(P,T,M)	((T *)((char *)(P) - offsetof(T, M)))
-#endif
 
 #if defined(__QNX__)
 	#include <gulliver.h>
@@ -664,6 +653,7 @@ struct lws_vhost {
 	const char *name;
 	const char *iface;
 	const struct lws_protocols *protocols;
+	void **protocol_vh_privs;
 #ifdef LWS_OPENSSL_SUPPORT
 	SSL_CTX *ssl_ctx;
 	SSL_CTX *ssl_client_ctx;
@@ -704,6 +694,7 @@ struct lws_context {
 	struct lws **lws_lookup;  /* fd to wsi */
 #endif
 	struct lws_vhost *vhost_list;
+	struct lws_plugin *plugin_list;
 	const struct lws_token_limits *token_limits;
 	void *user_space;
 
@@ -755,9 +746,12 @@ struct lws_context {
 	short max_http_header_data;
 	short max_http_header_pool;
 	short count_threads;
+	short plugin_protocol_count;
+	short plugin_extension_count;
 
 	unsigned int being_destroyed:1;
 	unsigned int requested_kill:1;
+	unsigned int protocol_init_done:1;
 };
 
 #define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x]
@@ -1671,6 +1665,9 @@ lws_get_addresses(struct lws_context *context, void *ads, char *name,
 LWS_EXTERN int
 lws_cgi_kill_terminated(struct lws_context_per_thread *pt);
 
+int
+lws_protocol_init(struct lws_context *context);
+
 /*
  * custom allocator
  */
diff --git a/lib/server.c b/lib/server.c
index 51dd427d9a50f7d35c9a1471fe0acaf11df4cd75..3103256e2b48a25fdb70b829847424680e52be06 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -194,6 +194,9 @@ static const char * get_mimetype(const char *file)
 	if (!strcmp(&file[n - 4], ".png"))
 		return "image/png";
 
+	if (!strcmp(&file[n - 4], ".jpg"))
+		return "image/jpeg";
+
 	if (!strcmp(&file[n - 5], ".html"))
 		return "text/html";
 
@@ -220,7 +223,7 @@ int lws_http_serve(struct lws *wsi, char *uri, const char *origin)
 	}
 
 	n = lws_serve_http_file(wsi, path, mimetype, NULL, 0);
-	if (n < 0)
+
 	if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi)))
 		return -1; /* error or can't reuse connection: close the socket */
 
diff --git a/lib/service.c b/lib/service.c
index 80e513c540d96362ec1b5eba5c1d00e8804117ca..2e66812bbc0bd0dc87023dc0353337317308c891 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -637,6 +637,9 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
 	int n = 0, m;
 	int more;
 
+	if (!context->protocol_init_done)
+		lws_protocol_init(context);
+
 	/*
 	 * you can call us with pollfd = NULL to just allow the once-per-second
 	 * global timeout checks; if less than a second since the last check
diff --git a/lws_config.h.in b/lws_config.h.in
index f90abeb9cf5b4e9e35ec4d000c665e807191b373..452e969350f2b020a6bf29d20ac6ac493f634a1d 100644
--- a/lws_config.h.in
+++ b/lws_config.h.in
@@ -17,6 +17,8 @@
 #cmakedefine LWS_USE_MBEDTLS
 #cmakedefine LWS_USE_POLARSSL
 
+#cmakedefine LWS_WITH_PLUGINS
+
 /* The Libwebsocket version */
 #cmakedefine LWS_LIBRARY_VERSION "${LWS_LIBRARY_VERSION}"
 
diff --git a/lwsws/etc-lwsws-conf-EXAMPLE b/lwsws/etc-lwsws-conf-EXAMPLE
new file mode 100644
index 0000000000000000000000000000000000000000..ac2c9ac54382e0e248dff23cbf4bd31221098651
--- /dev/null
+++ b/lwsws/etc-lwsws-conf-EXAMPLE
@@ -0,0 +1,14 @@
+# 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"
+ }
+}
+
diff --git a/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE b/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE
new file mode 100644
index 0000000000000000000000000000000000000000..b4e307dbb12c26dbb6e42583106c0a1c71f69e7a
--- /dev/null
+++ b/lwsws/etc-lwsws-conf.d-libwebsockets.org-EXAMPLE
@@ -0,0 +1,50 @@
+# comment
+
+{
+ "vhosts": [ {
+     "name": "libwebsockets.org",
+     "port": "443",
+     "host-ssl-key":  "/etc/pki/tls/private/libwebsockets.org.key",
+     "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
+     "host-ssl-ca":   "/etc/pki/tls/certs/libwebsockets.org.cer",
+     "mounts": [{
+       "mountpoint": "/",
+       "origin": "file:///var/www/libwebsockets.org",
+       "default": "index.html"
+       }, {
+        "mountpoint": "/git",
+        "origin": "http://git.warmcat.com",
+        "default": "/" 
+       }, {
+        "mountpoint": "/mailman",
+        "origin": "cgi://usr/lib/mailman/cgi-bin/",
+        "default": "/list-info"
+    }]
+   },
+    {
+    "name": "libwebsockets.org", # disambiguated by port, must be same for SNI
+    "port": "7681",
+     "host-ssl-key":  "/etc/pki/tls/private/libwebsockets.org.key",
+     "host-ssl-cert": "/etc/pki/tls/certs/libwebsockets.org.crt",
+     "host-ssl-ca":   "/etc/pki/tls/certs/libwebsockets.org.cer",
+     "ws-protocols": [{
+       "wsprotocol": "dumb-increment-protocol",
+       "wsprotocol": "lws-mirror-protocol",
+       "wsprotocol": "lws-status"
+     }],
+     "ws-extensions": [{
+       "extension": "permessage-deflate"
+     }],
+     "mounts": [{
+       "mountpoint": "/",
+       "origin": "file:///usr/local/share/libwebsockets-test-server",
+       "default": "test.html"
+     }]
+   },
+    {
+    "name": "libwebsockets.org",
+    "port": "80",
+    "global-redirect": "https://libwebsockets.org"
+ }]
+}
+
diff --git a/lwsws/http.c b/lwsws/http.c
index 1b27a7d213b2f220177214264ca574a75983ba84..66f398f9babf5054ceed104e32de734ddf5cbb8c 100644
--- a/lwsws/http.c
+++ b/lwsws/http.c
@@ -47,6 +47,9 @@ const char * get_mimetype(const char *file)
 	if (!strcmp(&file[n - 4], ".png"))
 		return "image/png";
 
+	if (!strcmp(&file[n - 4], ".jpg"))
+		return "image/jpeg";
+
 	if (!strcmp(&file[n - 5], ".html"))
 		return "text/html";
 
@@ -190,7 +193,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 			return 0;
 
 		/* check for the "send a big file by hand" example case */
-
+		lwsl_notice("%s\n", in);
 		if (!strcmp((const char *)in, "/leaf.jpg")) {
 			if (strlen(resource_path) > sizeof(leaf_path) - 10)
 				return -1;
diff --git a/lwsws/main.c b/lwsws/main.c
index 741fe7a319efdd89361545a787969bf0a23d0f0a..24fc31d1bf9f0d808705f96a00cb453bbf88792b 100644
--- a/lwsws/main.c
+++ b/lwsws/main.c
@@ -62,6 +62,7 @@ static struct lws_protocols protocols[] = {
 		sizeof (struct per_session_data__http),	/* per_session_data_size */
 		0,			/* max frame size / rx buffer */
 	},
+	{ }
 };
 
 void sighandler(int sig)
@@ -177,17 +178,16 @@ int main(int argc, char **argv)
 
 	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
+			      LWS_SERVER_OPTION_EXPLICIT_VHOSTS |
+			      LWS_SERVER_OPTION_LIBUV;
+
+	info.plugins_dir = INSTALL_DATADIR"/libwebsockets-test-server/plugins/";
 
 	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;
 
@@ -214,21 +214,14 @@ int main(int argc, char **argv)
 	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
+	lws_libuv_run(context, 0);
 
 bail:
 	lws_context_destroy(context);
-	lwsl_notice("lwsws exited cleanly\n");
+	fprintf(stderr, "lwsws exited cleanly\n");
 
 #ifndef _WIN32
 	closelog();
diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c
new file mode 100644
index 0000000000000000000000000000000000000000..499073dd27f014fe213ee535e602d475931bdd22
--- /dev/null
+++ b/plugins/protocol_dumb_increment.c
@@ -0,0 +1,141 @@
+/*
+ * ws protocol handler plugin for "dumb increment"
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * These test plugins are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "../lib/libwebsockets.h"
+
+struct per_vhost_data__dumb_increment {
+	uv_timer_t timeout_watcher;
+	struct lws_context *context;
+	struct lws_vhost *vhost;
+	const struct lws_protocols *protocol;
+};
+
+struct per_session_data__dumb_increment {
+	int number;
+};
+
+static void
+uv_timeout_cb_dumb_increment(uv_timer_t *w
+#if UV_VERSION_MAJOR == 0
+		, int status
+#endif
+)
+{
+	struct per_vhost_data__dumb_increment *vhd = lws_container_of(w,
+			struct per_vhost_data__dumb_increment, timeout_watcher);
+	lws_callback_on_writable_all_protocol(vhd->context, vhd->protocol);
+}
+
+static int
+callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
+			void *user, void *in, size_t len)
+{
+	struct per_session_data__dumb_increment *pss =
+			(struct per_session_data__dumb_increment *)user;
+	struct per_vhost_data__dumb_increment *vhd =
+			(struct per_vhost_data__dumb_increment *)
+			lws_protocol_vh_priv_get(lws_vhost_get(wsi),
+					lws_protocol_get(wsi));
+	unsigned char buf[LWS_PRE + 512];
+	unsigned char *p = &buf[LWS_PRE];
+	int n, m;
+
+	switch (reason) {
+	case LWS_CALLBACK_PROTOCOL_INIT:
+		vhd = lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi),
+				lws_protocol_get(wsi),
+				sizeof(struct per_vhost_data__dumb_increment));
+		vhd->context = lws_get_context(wsi);
+		vhd->protocol = lws_protocol_get(wsi);
+		vhd->vhost = lws_vhost_get(wsi);
+		uv_timer_init(lws_uv_getloop(vhd->context, 0),
+			      &vhd->timeout_watcher);
+		uv_timer_start(&vhd->timeout_watcher,
+			       uv_timeout_cb_dumb_increment, 50, 50);
+		break;
+
+	case LWS_CALLBACK_PROTOCOL_DESTROY:
+		uv_timer_stop(&vhd->timeout_watcher);
+		break;
+
+	case LWS_CALLBACK_ESTABLISHED:
+		pss->number = 0;
+		break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+		n = sprintf((char *)p, "%d", pss->number++);
+		m = lws_write(wsi, p, n, LWS_WRITE_TEXT);
+		if (m < n) {
+			lwsl_err("ERROR %d writing to di socket\n", n);
+			return -1;
+		}
+		break;
+
+	case LWS_CALLBACK_RECEIVE:
+		if (len < 6)
+			break;
+		if (strcmp((const char *)in, "reset\n") == 0)
+			pss->number = 0;
+		if (strcmp((const char *)in, "closeme\n") == 0) {
+			lwsl_notice("dumb_inc: closing as requested\n");
+			lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
+					 (unsigned char *)"seeya", 5);
+			return -1;
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+	{
+		"dumb-increment-protocol",
+		callback_dumb_increment,
+		sizeof(struct per_session_data__dumb_increment),
+		10, /* rx buf size must be >= permessage-deflate rx size */
+	},
+};
+
+LWS_VISIBLE int
+init_protocol_dumb_increment(struct lws_context *context,
+			     struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_dumb_increment(struct lws_context *context)
+{
+	return 0;
+}
diff --git a/plugins/protocol_lws_mirror.c b/plugins/protocol_lws_mirror.c
new file mode 100644
index 0000000000000000000000000000000000000000..ba656603327086c553881800ba49cd171a1d8b20
--- /dev/null
+++ b/plugins/protocol_lws_mirror.c
@@ -0,0 +1,177 @@
+/*
+ * libwebsockets-test-server - libwebsockets test implementation
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * The test apps are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "../lib/libwebsockets.h"
+
+/* lws-mirror_protocol */
+
+#define MAX_MESSAGE_QUEUE 512
+
+struct per_session_data__lws_mirror {
+	struct lws *wsi;
+	int ringbuffer_tail;
+};
+
+struct a_message {
+	void *payload;
+	size_t len;
+};
+
+struct per_vhost_data__lws_mirror {
+	struct a_message ringbuffer[MAX_MESSAGE_QUEUE];
+	int ringbuffer_head;
+};
+
+static int
+callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
+		    void *user, void *in, size_t len)
+{
+	struct per_session_data__lws_mirror *pss =
+			(struct per_session_data__lws_mirror *)user;
+	struct per_vhost_data__lws_mirror *v =
+			(struct per_vhost_data__lws_mirror *)
+			lws_protocol_vh_priv_get(lws_vhost_get(wsi),
+					lws_protocol_get(wsi));
+	int n, m;
+
+	switch (reason) {
+
+	case LWS_CALLBACK_ESTABLISHED:
+		lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
+		pss->ringbuffer_tail = v->ringbuffer_head;
+		pss->wsi = wsi;
+		break;
+
+	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
+		lws_protocol_vh_priv_zalloc(lws_vhost_get(wsi),
+				lws_protocol_get(wsi),
+				sizeof(struct per_vhost_data__lws_mirror));
+		break;
+
+	case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */
+		lwsl_info("%s: mirror protocol cleaning up %p\n", __func__, v);
+		for (n = 0; n < ARRAY_SIZE(v->ringbuffer); n++)
+			if (v->ringbuffer[n].payload) {
+				free(v->ringbuffer[n].payload);
+				v->ringbuffer[n].payload = NULL;
+			}
+		break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+		while (pss->ringbuffer_tail != v->ringbuffer_head) {
+			m = v->ringbuffer[pss->ringbuffer_tail].len;
+			n = lws_write(wsi, (unsigned char *)
+				   v->ringbuffer[pss->ringbuffer_tail].payload +
+				   LWS_PRE, m, LWS_WRITE_TEXT);
+			if (n < 0) {
+				lwsl_err("ERROR %d writing to mirror socket\n", n);
+				return -1;
+			}
+			if (n < m)
+				lwsl_err("mirror partial write %d vs %d\n", n, m);
+
+			if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1))
+				pss->ringbuffer_tail = 0;
+			else
+				pss->ringbuffer_tail++;
+
+			if (((v->ringbuffer_head - pss->ringbuffer_tail) &
+			    (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15))
+				lws_rx_flow_allow_all_protocol(lws_get_context(wsi),
+					       lws_get_protocol(wsi));
+
+			if (lws_send_pipe_choked(wsi)) {
+				lws_callback_on_writable(wsi);
+				break;
+			}
+		}
+		break;
+
+	case LWS_CALLBACK_RECEIVE:
+		if (((v->ringbuffer_head - pss->ringbuffer_tail) &
+		    (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
+			lwsl_err("dropping!\n");
+			goto choke;
+		}
+
+		if (v->ringbuffer[v->ringbuffer_head].payload)
+			free(v->ringbuffer[v->ringbuffer_head].payload);
+
+		v->ringbuffer[v->ringbuffer_head].payload = malloc(LWS_PRE + len);
+		v->ringbuffer[v->ringbuffer_head].len = len;
+		memcpy((char *)v->ringbuffer[v->ringbuffer_head].payload +
+		       LWS_PRE, in, len);
+		if (v->ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
+			v->ringbuffer_head = 0;
+		else
+			v->ringbuffer_head++;
+
+		if (((v->ringbuffer_head - pss->ringbuffer_tail) &
+		    (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
+			goto done;
+
+choke:
+		lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi);
+		lws_rx_flow_control(wsi, 0);
+
+done:
+		lws_callback_on_writable_all_protocol(lws_get_context(wsi),
+						      lws_get_protocol(wsi));
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+	{
+		"lws-mirror-protocol",
+		callback_lws_mirror,
+		sizeof(struct per_session_data__lws_mirror),
+		128, /* rx buf size must be >= permessage-deflate rx size */
+	},
+};
+
+LWS_VISIBLE int
+init_protocol_lws_mirror(struct lws_context *context,
+			     struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_lws_mirror(struct lws_context *context)
+{
+	return 0;
+}
+
diff --git a/plugins/protocol_lws_status.c b/plugins/protocol_lws_status.c
new file mode 100644
index 0000000000000000000000000000000000000000..fabaf6d0b351dbb169a6cd76f728cd5e7270059f
--- /dev/null
+++ b/plugins/protocol_lws_status.c
@@ -0,0 +1,208 @@
+/*
+ * libwebsockets-test-server - libwebsockets test implementation
+ *
+ * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com>
+ *
+ * This file is made available under the Creative Commons CC0 1.0
+ * Universal Public Domain Dedication.
+ *
+ * The person who associated a work with this deed has dedicated
+ * the work to the public domain by waiving all of his or her rights
+ * to the work worldwide under copyright law, including all related
+ * and neighboring rights, to the extent allowed by law. You can copy,
+ * modify, distribute and perform the work, even for commercial purposes,
+ * all without asking permission.
+ *
+ * The test apps are intended to be adapted for use in your code, which
+ * may be proprietary.  So unlike the library itself, they are licensed
+ * Public Domain.
+ */
+#include "../lib/libwebsockets.h"
+#include <time.h>
+
+struct per_session_data__lws_status {
+	struct per_session_data__lws_status *list;
+	struct timeval tv_established;
+	int last;
+	char ip[270];
+	char user_agent[512];
+	const char *pos;
+	int len;
+};
+
+
+static unsigned char server_info[1024];
+static int server_info_len;
+static int current;
+static char cache[16384];
+static int cache_len;
+static struct per_session_data__lws_status *list;
+static int live_wsi;
+
+
+static void
+update_status(struct lws *wsi, struct per_session_data__lws_status *pss)
+{
+	struct per_session_data__lws_status **pp = &list;
+	int subsequent = 0;
+	char *p = cache + LWS_PRE, *start = p;
+	char date[128];
+	time_t t;
+	struct tm *ptm;
+#ifndef WIN32
+	struct tm tm;
+#endif
+
+	p += snprintf(p, 512, " { %s, \"wsi\":\"%d\", \"conns\":[",
+		     server_info, live_wsi);
+
+	/* render the list */
+	while (*pp) {
+		t = (*pp)->tv_established.tv_sec;
+#ifdef WIN32
+		ptm = localtime(&t);
+		if (!ptm)
+#else
+		ptm = &tm;
+		if (!localtime_r(&t, &tm))
+#endif
+			strcpy(date, "unknown");
+		else
+			strftime(date, sizeof(date), "%F %H:%M %Z", ptm);
+		if ((p - start) > (sizeof(cache) - 512))
+			break;
+		if (subsequent)
+			*p++ = ',';
+		subsequent = 1;
+		p += snprintf(p, sizeof(cache) - (p - start) - 1,
+				"{\"peer\":\"%s\",\"time\":\"%s\","
+				"\"ua\":\"%s\"}",
+			     (*pp)->ip, date, (*pp)->user_agent);
+		pp = &((*pp)->list);
+	}
+
+	p += sprintf(p, "]}");
+	cache_len = p - start;
+	lwsl_err("cache_len %d\n", cache_len);
+	*p = '\0';
+
+	/* since we changed the list, increment the 'version' */
+	current++;
+	/* update everyone */
+	lws_callback_on_writable_all_protocol(lws_get_context(wsi),
+					      lws_get_protocol(wsi));
+}
+
+
+/* lws-status protocol */
+
+int
+callback_lws_status(struct lws *wsi, enum lws_callback_reasons reason,
+		    void *user, void *in, size_t len)
+{
+	struct per_session_data__lws_status *pss =
+			(struct per_session_data__lws_status *)user,
+			**pp;
+	char name[128], rip[128];
+	int m;
+
+	switch (reason) {
+
+	case LWS_CALLBACK_PROTOCOL_INIT:
+		/*
+		 * Prepare the static server info
+		 */
+		server_info_len = sprintf((char *)server_info,
+					  "\"version\":\"%s\","
+					  " \"hostname\":\"%s\"",
+					  lws_get_library_version(),
+					  lws_canonical_hostname(
+							lws_get_context(wsi)));
+		break;
+
+	case LWS_CALLBACK_ESTABLISHED:
+		/*
+		 * we keep a linked list of live pss, so we can walk it
+		 */
+		pss->last = 0;
+		pss->list = list;
+		list = pss;
+		live_wsi++;
+		lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name,
+				       sizeof(name), rip, sizeof(rip));
+		sprintf(pss->ip, "%s (%s)", name, rip);
+		gettimeofday(&pss->tv_established, NULL);
+		strcpy(pss->user_agent, "unknown");
+		lws_hdr_copy(wsi, pss->user_agent, sizeof(pss->user_agent),
+			     WSI_TOKEN_HTTP_USER_AGENT);
+		update_status(wsi, pss);
+		break;
+
+	case LWS_CALLBACK_SERVER_WRITEABLE:
+		m = lws_write(wsi, (unsigned char *)cache + LWS_PRE, cache_len,
+			      LWS_WRITE_TEXT);
+		if (m < server_info_len) {
+			lwsl_err("ERROR %d writing to di socket\n", m);
+			return -1;
+		}
+		break;
+
+	case LWS_CALLBACK_CLOSED:
+		/*
+		 * remove ourselves from live pss list
+		 */
+		lwsl_err("CLOSING pss %p ********\n", pss);
+
+		pp = &list;
+		while (*pp) {
+			if (*pp == pss) {
+				*pp = pss->list;
+				pss->list = NULL;
+				live_wsi--;
+				break;
+			}
+			pp = &((*pp)->list);
+		}
+
+		update_status(wsi, pss);
+		break;
+
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct lws_protocols protocols[] = {
+	{
+		"lws-status",
+		callback_lws_status,
+		sizeof(struct per_session_data__lws_status),
+		128, /* rx buf size must be >= permessage-deflate rx size */
+	},
+};
+
+LWS_VISIBLE int
+init_protocol_lws_status(struct lws_context *context,
+			     struct lws_plugin_capability *c)
+{
+	if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
+		lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
+			 c->api_magic);
+		return 1;
+	}
+
+	c->protocols = protocols;
+	c->count_protocols = ARRAY_SIZE(protocols);
+	c->extensions = NULL;
+	c->count_extensions = 0;
+
+	return 0;
+}
+
+LWS_VISIBLE int
+destroy_protocol_lws_status(struct lws_context *context)
+{
+	return 0;
+}
diff --git a/test-server/test-server-libuv.c b/test-server/test-server-libuv.c
index fb95f7fd655bc3d0ea7b2a8d7e913d8c09b148ef..183c2b75100ae8452279275d98bc12fda3c0f48a 100644
--- a/test-server/test-server-libuv.c
+++ b/test-server/test-server-libuv.c
@@ -128,7 +128,11 @@ void signal_cb(uv_signal_t *watcher, int signum)
 }
 
 static void
-uv_timeout_cb_dumb_increment(uv_timer_t *w)
+uv_timeout_cb_dumb_increment(uv_timer_t *w
+#if UV_VERSION_MAJOR == 0
+		, int status
+#endif
+)
 {
 	lws_callback_on_writable_all_protocol(context,
 					&protocols[PROTOCOL_DUMB_INCREMENT]);
diff --git a/travis_install.sh b/travis_install.sh
index 96ac72d9727911bfc36e54a1b67b962c0e244e8e..f51f087e77ac3bbcf97c9ab0f52ffbeb8d460483 100755
--- a/travis_install.sh
+++ b/travis_install.sh
@@ -10,6 +10,12 @@ then
 	then
 		sudo apt-get install -y -qq libev-dev;
 	fi
+
+	if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ];
+	then
+		sudo apt-get install -y -qq libuv-dev;
+	fi
+
 fi
 
 if [ "$TRAVIS_OS_NAME" == "osx" ];
@@ -18,6 +24,12 @@ then
 	then
 		brew install libev;
 	fi
+
+	if [ "$LWS_METHOD" == "libuv" -o "$LWS_METHOD" == "lwsws" ];
+	then
+		brew install libuv;
+	fi
+
 fi