diff --git a/.travis.yml b/.travis.yml
index 099c6de8b44d39a0a97c267c809575c8b36252dd..77bad91cf817bf2a4dbb7052f9facdceb756e5b8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,6 +16,7 @@ env:
     - LWS_METHOD=cgi CMAKE_ARGS="-DLWS_WITH_CGI=ON"
     - LWS_METHOD=nologs CMAKE_ARGS="-DLWS_WITH_NO_LOGS=ON"
     - LWS_METHOD=smp CMAKE_ARGS="-DLWS_MAX_SMP=32 -DLWS_WITH_MINIMAL_EXAMPLES=1"
+    - LWS_METHOD=nows CMAKE_ARGS="-DLWS_ROLE_WS=0"
 
 os:
   - linux
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 70c079aa2757a7fe8dc6e0cf76dd86563a9a4c6a..517ea33add2a8ed91217fc246549fd2d89780ab4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,6 +9,8 @@ set(LWS_WITH_BUNDLED_ZLIB_DEFAULT OFF)
 if(WIN32)
 	set(LWS_WITH_BUNDLED_ZLIB_DEFAULT ON)
 endif()
+set(LWS_ROLE_H1 1)
+set(LWS_ROLE_RAW 1)
 
 #
 # Select features recommended for PC distro packaging
@@ -18,6 +20,7 @@ option(LWS_WITH_DISTRO_RECOMMENDED "Enable features recommended for distro packa
 #
 # Major individual features
 #
+option(LWS_ROLE_WS "Compile with support for websockets" ON)
 option(LWS_WITH_HTTP2 "Compile with server support for HTTP/2" OFF)
 option(LWS_WITH_LWSWS "Libwebsockets Webserver" OFF)
 option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF)
@@ -70,7 +73,6 @@ option(LWS_WITHOUT_TEST_SERVER "Don't build the test server" OFF)
 option(LWS_WITHOUT_TEST_SERVER_EXTPOLL "Don't build the test server version that uses external poll" OFF)
 option(LWS_WITHOUT_TEST_PING "Don't build the ping test application" OFF)
 option(LWS_WITHOUT_TEST_CLIENT "Don't build the client test application" OFF)
-option(LWS_WITHOUT_TEST_FRAGGLE "Don't build the ping test application" OFF)
 #
 # Extensions (permessage-deflate)
 #
@@ -197,9 +199,6 @@ endif()
 
 # translate old functionality enables to set up ROLE enables so nothing changes
 
-set(LWS_ROLE_H1 1)
-set(LWS_ROLE_WS 1)
-set(LWS_ROLE_RAW 1)
 if (LWS_WITH_HTTP2)
 	set(LWS_ROLE_H2 1)
 endif()
@@ -207,6 +206,10 @@ if (LWS_WITH_CGI)
 	set(LWS_ROLE_CGI 1)
 endif()
 
+if (NOT LWS_ROLE_WS)
+	set(LWS_WITHOUT_EXTENSIONS 1)
+endif()
+
 if (LWS_WITH_HTTP2 AND LWS_WITHOUT_SERVER)
 	message(FATAL_ERROR "HTTP2 can only be used with server at the moment")
 endif()
@@ -709,7 +712,7 @@ if (LWS_ROLE_WS)
 	if (NOT LWS_WITHOUT_CLIENT)
 		list(APPEND SOURCES
 			lib/roles/ws/client-ws.c
-			lib/roles/ws/client-parser.c)
+			lib/roles/ws/client-parser-ws.c)
 	endif()
 	if (NOT LWS_WITHOUT_SERVER)
 		list(APPEND SOURCES
@@ -1592,15 +1595,6 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 					""
 					"")
 			endif()
-			if (UNIX AND NOT LWS_WITHOUT_SERVER AND NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang")) AND LWS_HAVE_PTHREAD_H)
-				create_test_app(test-server-pthreads
-					"test-apps/test-server-pthreads.c"
-					"test-apps/test-server-http.c"
-					"test-apps/test-server-dumb-increment.c"
-					""
-					""
-					"")
-			endif()
 			if (NOT ((CMAKE_C_COMPILER_ID MATCHES "Clang") OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
 				AND LWS_WITH_LIBEV)
 				create_test_app(test-server-libev
@@ -1720,13 +1714,6 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 			create_test_app(test-client "test-apps/test-client.c" "" "" "" "" "")
 		endif()
 
-		#
-		# test-fraggle
-		#
-		if (NOT LWS_WITHOUT_TEST_FRAGGLE AND NOT LWS_WITHOUT_SERVER)
-			create_test_app(test-fraggle "test-apps/test-fraggle.c" "" "" "" "" "")
-		endif()
-
 		#
 		# test-ping
 		#
@@ -1786,27 +1773,15 @@ if (NOT LWS_WITHOUT_TESTAPPS)
 
 		endmacro()
 		
-
-		create_plugin(protocol_lws_meta ""
-			      "plugins/protocol_lws_meta.c" "" "")
+if (LWS_ROLE_WS)
 		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" "" "")
-		create_plugin(protocol_post_demo ""
-			      "plugins/protocol_post_demo.c" "" "")
 		create_plugin(protocol_lws_table_dirlisting ""
 			      "plugins/generic-table/protocol_table_dirlisting.c" "" "")
-if (LWS_WITH_SSL)
-		create_plugin(protocol_lws_ssh_base "plugins/ssh-base/include"
-			      "plugins/ssh-base/sshd.c;plugins/ssh-base/telnet.c;plugins/ssh-base/kex-25519.c" "plugins/ssh-base/crypto/chacha.c;plugins/ssh-base/crypto/ed25519.c;plugins/ssh-base/crypto/fe25519.c;plugins/ssh-base/crypto/ge25519.c;plugins/ssh-base/crypto/poly1305.c;plugins/ssh-base/crypto/sc25519.c;plugins/ssh-base/crypto/smult_curve25519_ref.c" "")
-		create_plugin(protocol_lws_sshd_demo "plugins/ssh-base/include" "plugins/protocol_lws_sshd_demo.c" "" "")
-
-		include_directories("${PROJECT_SOURCE_DIR}/plugins/ssh-base/include")
-endif()
-
 		if (NOT WIN32)
 			create_plugin(protocol_lws_raw_test ""
 			      "plugins/protocol_lws_raw_test.c" "" "")
@@ -1820,14 +1795,29 @@ endif()
 if (NOT LWS_WITHOUT_CLIENT)
 		create_plugin(protocol_client_loopback_test ""
                               "plugins/protocol_client_loopback_test.c" "" "")
-endif(NOT LWS_WITHOUT_CLIENT)
+endif()
+
+endif()
+
+		create_plugin(protocol_post_demo ""
+			      "plugins/protocol_post_demo.c" "" "")
+
+if (LWS_WITH_SSL)
+		create_plugin(protocol_lws_ssh_base "plugins/ssh-base/include"
+			      "plugins/ssh-base/sshd.c;plugins/ssh-base/telnet.c;plugins/ssh-base/kex-25519.c" "plugins/ssh-base/crypto/chacha.c;plugins/ssh-base/crypto/ed25519.c;plugins/ssh-base/crypto/fe25519.c;plugins/ssh-base/crypto/ge25519.c;plugins/ssh-base/crypto/poly1305.c;plugins/ssh-base/crypto/sc25519.c;plugins/ssh-base/crypto/smult_curve25519_ref.c" "")
+		create_plugin(protocol_lws_sshd_demo "plugins/ssh-base/include" "plugins/protocol_lws_sshd_demo.c" "" "")
+
+		include_directories("${PROJECT_SOURCE_DIR}/plugins/ssh-base/include")
+endif()
+
+
 
 if (LWS_WITH_ACME)
 		create_plugin(protocol_lws_acme_client ""
 			      "plugins/acme-client/protocol_lws_acme_client.c" "" "")
 endif()
 
-if (LWS_WITH_GENERIC_SESSIONS)
+if (LWS_WITH_GENERIC_SESSIONS AND LWS_ROLE_WS)
 	create_plugin(protocol_generic_sessions ""
                       "plugins/generic-sessions/protocol_generic_sessions.c"
 		      "plugins/generic-sessions/utils.c"
@@ -2157,7 +2147,6 @@ message(" LWS_WITHOUT_TEST_SERVER = ${LWS_WITHOUT_TEST_SERVER}")
 message(" LWS_WITHOUT_TEST_SERVER_EXTPOLL = ${LWS_WITHOUT_TEST_SERVER_EXTPOLL}")
 message(" LWS_WITHOUT_TEST_PING = ${LWS_WITHOUT_TEST_PING}")
 message(" LWS_WITHOUT_TEST_CLIENT = ${LWS_WITHOUT_TEST_CLIENT}")
-message(" LWS_WITHOUT_TEST_FRAGGLE = ${LWS_WITHOUT_TEST_FRAGGLE}")
 message(" LWS_WITHOUT_EXTENSIONS = ${LWS_WITHOUT_EXTENSIONS}")
 message(" LWS_WITH_LATENCY = ${LWS_WITH_LATENCY}")
 message(" LWS_WITHOUT_DAEMONIZE = ${LWS_WITHOUT_DAEMONIZE}")
diff --git a/README.md b/README.md
index da1e2a42f5c456d4624d274c6a72849192fa975b..235554967ac3c396e9119cef8f6e9bcdf24d2693 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,8 @@ The Travis build of lws done on every commit now runs
 
 Tests|Count|Explanation
 ---|---|---
-Build / Linux / gcc|12|-Wall -Werror
-Build / Mac / Clang|12|-Wall -Werror
+Build / Linux / gcc|13|-Wall -Werror
+Build / Mac / Clang|13|-Wall -Werror
 Build / Windows / MSVC|7|default
 Selftests|29|minimal examples built and run against each other and remote server
 attack.sh|225|Correctness, robustness and security tests for http parser
diff --git a/lib/context.c b/lib/context.c
index 7dbf611913358e282f1bd14b9d4328b3dbda2065..770edac2e1295b2c3defc9b851a6e116af02747c 100644
--- a/lib/context.c
+++ b/lib/context.c
@@ -25,8 +25,7 @@
 #define LWS_BUILD_HASH "unknown-build-hash"
 #endif
 
-#if defined(LWS_WITH_TLS)
-static const struct lws_role_ops * available_roles[] = {
+const struct lws_role_ops * available_roles[] = {
 #if defined(LWS_ROLE_H2)
 	&role_ops_h2,
 #endif
@@ -36,8 +35,10 @@ static const struct lws_role_ops * available_roles[] = {
 #if defined(LWS_ROLE_WS)
 	&role_ops_ws,
 #endif
+	NULL
 };
 
+#if defined(LWS_WITH_TLS)
 static char alpn_discovered[32];
 #endif
 
@@ -60,17 +61,15 @@ int
 lws_role_call_alpn_negotiated(struct lws *wsi, const char *alpn)
 {
 #if defined(LWS_WITH_TLS)
-	int n;
-
 	if (!alpn)
 		return 0;
 
 	lwsl_info("%s: '%s'\n", __func__, alpn);
 
-	for (n = 0; n < (int)LWS_ARRAY_SIZE(available_roles); n++)
-		if (!strcmp(available_roles[n]->alpn, alpn) &&
-		     available_roles[n]->alpn_negotiated)
-			return available_roles[n]->alpn_negotiated(wsi, alpn);
+	LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+		if (!strcmp(ar->alpn, alpn) && ar->alpn_negotiated)
+			return ar->alpn_negotiated(wsi, alpn);
+	LWS_FOR_EVERY_AVAILABLE_ROLE_END;
 #endif
 	return 0;
 }
@@ -565,13 +564,6 @@ lws_create_vhost(struct lws_context *context,
 	if (info->options & LWS_SERVER_OPTION_ONLY_RAW)
 		lwsl_info("%s set to only support RAW\n", vh->name);
 
-#if defined(LWS_WITH_HTTP2)
-	vh->h2.set = context->set;
-	if (info->http2_settings[0])
-		for (n = 1; n < LWS_H2_SETTINGS_LEN; n++)
-			vh->h2.set.s[n] = info->http2_settings[n];
-#endif
-
 	vh->iface = info->iface;
 #if !defined(LWS_WITH_ESP32) && \
     !defined(OPTEE_TA) && !defined(WIN32)
@@ -589,9 +581,11 @@ lws_create_vhost(struct lws_context *context,
 	vh->user = info->user;
 	vh->alpn = info->alpn;
 
-#if defined(LWS_ROLE_H2)
-	role_ops_h2.init_vhost(vh, info);
-#endif
+	LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+		if (ar->init_vhost)
+			if (ar->init_vhost(vh, info))
+				return NULL;
+	LWS_FOR_EVERY_AVAILABLE_ROLE_END;
 
 	vh->ssl_info_event_mask = info->ssl_info_event_mask;
 	if (info->keepalive_timeout)
@@ -740,40 +734,6 @@ lws_create_vhost(struct lws_context *context,
 		mounts = mounts->mount_next;
 	}
 
-#if !defined(LWS_WITHOUT_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->ws.extensions = lws_zalloc(sizeof(struct lws_extension) *
-				     (m + context->plugin_extension_count + 1),
-				     "extensions");
-		if (!vh->ws.extensions)
-			return NULL;
-
-		memcpy((struct lws_extension *)vh->ws.extensions, info->extensions,
-		       sizeof(struct lws_extension) * m);
-		plugin = context->plugin_list;
-		while (plugin) {
-			memcpy((struct lws_extension *)&vh->ws.extensions[m],
-				plugin->caps.extensions,
-			       sizeof(struct lws_extension) *
-			       plugin->caps.count_extensions);
-			m += plugin->caps.count_extensions;
-			plugin = plugin->list;
-		}
-	} else
-#endif
-		vh->ws.extensions = info->extensions;
-#endif
-
 	vh->listen_port = info->port;
 	vh->http_proxy_port = 0;
 	vh->http_proxy_address[0] = '\0';
@@ -1122,16 +1082,17 @@ lws_create_context(struct lws_context_creation_info *info)
 	else {
 		char *p = alpn_discovered, first = 1;
 
-		for (n = 0; n < (int)LWS_ARRAY_SIZE(available_roles); n++) {
-			if (available_roles[n]->alpn) {
+		LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar) {
+			if (ar->alpn) {
 				if (!first)
 					*p++ = ',';
 				p += lws_snprintf(p, alpn_discovered +
 						sizeof(alpn_discovered) - 2 - p,
-					    "%s", available_roles[n]->alpn);
+					    "%s", ar->alpn);
 				first = 0;
 			}
-		}
+		} LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
 		context->alpn_default = alpn_discovered;
 	}
 
@@ -1596,12 +1557,11 @@ lws_vhost_destroy2(struct lws_vhost *vh)
 			lws_free((void *)vh->protocols);
 	}
 
-#ifdef LWS_WITH_PLUGINS
-#if !defined(LWS_WITHOUT_EXTENSIONS)
-	if (context->plugin_extension_count)
-		lws_free((void *)vh->ws.extensions);
-#endif
-#endif
+	LWS_FOR_EVERY_AVAILABLE_ROLE_START(ar)
+		if (ar->destroy_vhost)
+			ar->destroy_vhost(vh);
+	LWS_FOR_EVERY_AVAILABLE_ROLE_END;
+
 #ifdef LWS_WITH_ACCESS_LOG
 	if (vh->log_fd != (int)LWS_INVALID_FILE)
 		close(vh->log_fd);
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index bf8cc7c4d4cfca93025d9c5d66351e1bdf12bd5d..af77b3a8b4946b1a6d80544618ec595134dca314 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -95,7 +95,6 @@ __lws_free_wsi(struct lws *wsi)
 
 	lws_buflist_destroy_all_segments(&wsi->buflist);
 	lws_free_set_NULL(wsi->trunc_alloc);
-	lws_free_set_NULL(wsi->ws);
 	lws_free_set_NULL(wsi->udp);
 
 	/* we may not have an ah, but may be on the waiting list... */
@@ -834,11 +833,6 @@ just_kill_connection:
 		wsi->told_user_closed = 1;
 	}
 
-	/* deallocate any active extension contexts */
-
-	if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
-		lwsl_warn("extension destruction failed\n");
-
 async_close:
 	wsi->socket_is_permanently_unusable = 1;
 
diff --git a/lib/output.c b/lib/output.c
index 3453086826c7c33a3e5affd3efeb9365f1464be7..b6149c474d3cd6854c537d33edeb82b21993062e 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -249,7 +249,6 @@ lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len)
 		if (wsi->vhost)
 			wsi->vhost->conn_stats.rx += n;
 		lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n);
-		lws_restart_ws_ping_pong_timer(wsi);
 
 		return n;
 	}
diff --git a/lib/plat/lws-plat-esp32.c b/lib/plat/lws-plat-esp32.c
index 3c4dfb5116d22dacfd054e3d92e838f62c4a9bee..3ba6ceb89bc44c0f07e670463772d9d1b9477d2b 100644
--- a/lib/plat/lws-plat-esp32.c
+++ b/lib/plat/lws-plat-esp32.c
@@ -218,10 +218,10 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
 	}
 
 #if defined(LWS_WITH_TLS)
-	if (!pt->rx_draining_ext_list &&
+	if (!pt->ws.rx_draining_ext_list &&
 	    !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
 #else
-	if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
+	if (!pt->ws.rx_draining_ext_list && !n) /* poll timeout */ {
 #endif
 		lws_service_fd_tsi(context, NULL, tsi);
 		return 0;
diff --git a/lib/plat/lws-plat-optee.c b/lib/plat/lws-plat-optee.c
index 4003f7c80f3c5c940ffcb7e5cd756d00d8e32d62..f6354011562f65298c8fc94970ce6142eda75979 100644
--- a/lib/plat/lws-plat-optee.c
+++ b/lib/plat/lws-plat-optee.c
@@ -141,10 +141,10 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
 	n = poll(pt->fds, pt->fds_count, timeout_ms);
 
 #if defined(LWS_WITH_TLS)
-	if (!pt->rx_draining_ext_list &&
+	if (!pt->ws.rx_draining_ext_list &&
 	    !lws_ssl_anybody_has_buffered_read_tsi(context, tsi) && !n) {
 #else
-	if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
+	if (!pt->ws.rx_draining_ext_list && !n) /* poll timeout */ {
 #endif
 		lws_service_fd_tsi(context, NULL, tsi);
 		return 0;
diff --git a/lib/plat/lws-plat-unix.c b/lib/plat/lws-plat-unix.c
index c5c0ce617038e116f671c7cd33a92302dcc179f1..caf4be667c5e50004fdb5eb6ed16fd1f48959b17 100644
--- a/lib/plat/lws-plat-unix.c
+++ b/lib/plat/lws-plat-unix.c
@@ -259,12 +259,16 @@ _lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi)
 
 	lws_pt_unlock(pt);
 
+	m = 0;
+
 #if defined(LWS_WITH_TLS)
-	if (!n && !pt->rx_draining_ext_list &&
-	    !lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) {
-#else
-	if (!pt->rx_draining_ext_list && !n) /* poll timeout */ {
+	m |= !n && !lws_ssl_anybody_has_buffered_read_tsi(context, tsi);
 #endif
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+	m |= !n && !pt->ws.rx_draining_ext_list;
+#endif
+
+	if (m) {
 		lws_service_fd_tsi(context, NULL, tsi);
 		lws_service_do_ripe_rxflow(pt);
 
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 6bf3794e89af160b7f3e600e69bc31a65372c401..1985e0b4c5e316a4a0c1dc93c7a51f6b4efea466 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -527,12 +527,14 @@ struct lws_role_ops {
 	 * ws-over-h2 is upgraded from h2 like this.
 	 */
 	int (*check_upgrades)(struct lws *wsi);
-	/* role-specific context init during vhost creation */
+	/* role-specific context init during context creation */
 	int (*init_context)(struct lws_context *context,
 			    struct lws_context_creation_info *info);
 	/* role-specific per-vhost init during vhost creation */
 	int (*init_vhost)(struct lws_vhost *vh,
 			  struct lws_context_creation_info *info);
+	/* role-specific per-vhost destructor during vhost destroy */
+	int (*destroy_vhost)(struct lws_vhost *vh);
 	/* generic 1Hz callback for the role itself */
 	int (*periodic_checks)(struct lws_context *context, int tsi,
 			       time_t now);
@@ -582,9 +584,22 @@ struct lws_role_ops {
 	uint16_t close_cb[2];
 };
 
+/* null-terminated array of pointers to roles lws built with */
+extern const struct lws_role_ops *available_roles[];
+
+#define LWS_FOR_EVERY_AVAILABLE_ROLE_START(xx) { \
+		const struct lws_role_ops **ppxx = available_roles; \
+		while (*ppxx) { \
+			const struct lws_role_ops *xx = *ppxx++;
+
+#define LWS_FOR_EVERY_AVAILABLE_ROLE_END }}
+
+/* core roles */
 extern struct lws_role_ops role_ops_raw_skt, role_ops_raw_file, role_ops_listen,
 			   role_ops_pipe;
 
+/* bring in role private declarations */
+
 #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
  #include "roles/http/private.h"
 #else
@@ -767,9 +782,8 @@ struct lws_context_per_thread {
 #endif
 	struct lws_pollfd *fds;
 	volatile struct lws_foreign_thread_pollfd * volatile foreign_pfd_list;
-#if defined(LWS_ROLE_WS)
-	struct lws *rx_draining_ext_list;
-	struct lws *tx_draining_ext_list;
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
+	struct lws_pt_role_ws ws;
 #endif
 	struct lws_dll_lws dll_head_timeout;
 	struct lws_dll_lws dll_head_hrtimer;
@@ -887,7 +901,7 @@ struct lws_vhost {
 #if defined(LWS_ROLE_H2)
 	struct lws_vhost_role_h2 h2;
 #endif
-#if defined(LWS_ROLE_WS)
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
 	struct lws_vhost_role_ws ws;
 #endif
 
@@ -1368,6 +1382,9 @@ struct lws {
 #if defined(LWS_ROLE_H2)
 	struct _lws_h2_related h2;
 #endif
+#if defined(LWS_ROLE_WS)
+	struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */
+#endif
 
 	/* lifetime members */
 
@@ -1388,7 +1405,7 @@ struct lws {
 	struct lws *parent; /* points to parent, if any */
 	struct lws *child_list; /* points to first child */
 	struct lws *sibling_list; /* subsequent children at same level */
-	struct _lws_websocket_related *ws; /* allocated if we upgrade to ws */
+
 #ifdef LWS_WITH_CGI
 	struct lws_cgi *cgi; /* wsi being cgi master have one of these */
 #endif
diff --git a/lib/roles/README.md b/lib/roles/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..0e3a01c49857eaf24691f7736b7f5840a6698c91
--- /dev/null
+++ b/lib/roles/README.md
@@ -0,0 +1,161 @@
+## Information for new role implementers
+
+### Introduction
+
+In lws the "role" is the job the wsi is doing in the system, eg,
+being an http1 or h2, or ws connection, or being a listen socket, etc.
+
+This is different than, eg, a new ws protocol or a different callback
+for an existing role.  A new role is needed when you want to add support for
+something completely new, like a completely new wire protocol that
+doesn't use http or ws.
+
+So... what's the point of implementing the protocol inside the lws role framework?
+
+You inherit all the well-maintained lws core functionality around:
+
+ - connection lifecycle sequencing in a valgrind-clean way
+
+ - proxy support, HTTP and Socks5
+
+ - tls support working equally on mbedTLS and OpenSSL and derivatives without any code in the role
+
+ - apis for cert lifecycle management and parsing
+
+ - event loop support working on all the lws event loops (poll, libuv , ev, and event)
+
+ - clean connection tracking and closing even on advanced event loops
+
+ - user code follows the same simple callbacks on wsi
+
+ - multi-vhost support
+
+ - core multithreaded service support with usually no locking requirement on the role code
+
+ - direct compatibility with all other lws roles + protocols in the same event loop
+
+ - compatibility with higher-level stuff like lwsws as the server application
+
+### Code placement
+
+The code specific to that role should live in `./lib/roles/**role name**`
+
+If a role is asymmetic between a client and server side, like http is, it
+should generally be implemented as a single role.
+
+### Allowing control over enabling roles
+
+All roles should add a cmake define `LWS_ROLE_**role name**` and make its build
+dependent on it in CMakeLists.txt.  Export the cmakedefine in `./cmake/lws_config.h.in`
+as well so user builds can understand if the role is available in the lws build it is
+trying to bind to.
+
+If the role is disabled in cmake, nothing in its directory is built.
+
+### Role ops struct
+
+The role is defined by `struct lws_role_ops` in `lib/private/libwebsockets.h`,
+each role instantiates one of these and fills in the appropriate ops
+callbacks to perform its job.  By convention that lives in
+`./lib/roles/**role name**/ops-**role_name**.c`.
+
+### Private role declarations
+
+Truly private declarations for the role can go in the role directory as you like.
+However when the declarations must be accessible to other things in lws build, eg,
+the role adds members to `struct lws` when enabled, they should be in the role
+directory in a file `private.h`.
+
+Search for "bring in role private declarations" in `./lib/private-libwebsockets.h
+and add your private role file there following the style used for the other roles,
+eg,
+
+```
+#if defined(LWS_ROLE_WS)
+ #include "roles/ws/private.h"
+#else
+ #define lwsi_role_ws(wsi) (0)
+#endif
+```
+
+If the role is disabled at cmake, nothing from its private.h should be used anywhere.
+
+### Integrating role assets to lws
+
+If your role needs special storage in lws objects, that's no problem.  But to keep
+things sane, there are some rules.
+
+ - declare a "container struct" in your private.h for everything, eg, the ws role wants
+   to add storage in lws_vhost for enabled extensions, it declares in its private.h
+
+```
+struct lws_vhost_role_ws {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	const struct lws_extension *extensions;
+#endif
+};
+```
+
+ - add your role content in one place in the lws struct, protected by `#if defined(LWS_ROLE_**role name**)`,
+   eg, again for LWS_ROLE_WS
+
+```
+	struct lws_vhost {
+
+...
+
+#if defined(LWS_ROLE_WS)
+	struct lws_vhost_role_ws ws;
+#endif
+
+...
+```
+
+### Adding to lws available roles list
+
+Edit the NULL-terminated array `available_roles` at the top of `./lib/context.c` to include
+a pointer to your new role's ops struct, following the style already there.
+
+```
+const struct lws_role_ops * available_roles[] = {
+#if defined(LWS_ROLE_H2)
+	&role_ops_h2,
+#endif
+...
+```
+
+This makes lws aware that your role exists, and it can auto-generate some things like
+ALPN lists, and call your role ops callbacks for things like hooking vhost creation.
+
+### Enabling role adoption
+
+The primary way wsi get bound to a specific role is via the lws adoption api
+`lws_adopt_descriptor_vhost()`.  Add flags as necessary in `./lib/libwebsockets.h`
+`enum lws_adoption_type` and follow the existing code in `lws_adopt_descriptor_vhost()`
+to bind a wsi with suitable flags to your role ops.
+
+### Implementation of the role
+
+After that plumbing-in is completed, the role ops you declare are "live" on a wsi
+bound to them via the adoption api.
+
+The core support for wsis in lws has some generic concepts
+
+ - the wsi holds a pointer member `role_ops` that indicates which role ops the
+   wsi is bound to
+
+ - the wsi holds a generic uint32 `wsistate` that contains role flags and wsi state
+
+ - role flags are provided (LWSIFR_CLIENT, LWSIFR_SERVER) to differentiate between
+   client and server connections inside a wsi, along with helpers `lwsi_role_client(wsi)`
+   and `lwsi_role_server(wsi)`.
+
+ - lws provides around 30 generic states for the wsi starting from 'unconnected' through
+   various proxy or tunnel states, to 'established', and then various states shutting
+   down until 'dead socket'.  The states have testable flags and helpers to discover if
+   the wsi state is before establishment `lwsi_state_est(wsi)` and if in the state it is
+   in, it can handle pollout `lwsi_state_can_handle_POLLOUT(wsi)`.
+
+ - You set the initial binding, role flags and state using `lws_role_transition()`.  Afterwards
+   you can adjust the state using `lwsi_set_state()`.
+
diff --git a/lib/roles/cgi/ops-cgi.c b/lib/roles/cgi/ops-cgi.c
index 091547e379289bb89abc89c61cc457b33ca5f34d..d113fbf7a6e9666898f20caeba4cdb0a326d034f 100644
--- a/lib/roles/cgi/ops-cgi.c
+++ b/lib/roles/cgi/ops-cgi.c
@@ -81,6 +81,7 @@ struct lws_role_ops role_ops_cgi = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		rops_periodic_checks_cgi,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_cgi,
diff --git a/lib/roles/h1/ops-h1.c b/lib/roles/h1/ops-h1.c
index 976aa47b0e697af9005195a13839ffe6974e5aa3..00485faac69f793b44e66521d9c2e5faf3d766e3 100644
--- a/lib/roles/h1/ops-h1.c
+++ b/lib/roles/h1/ops-h1.c
@@ -25,54 +25,6 @@
 #define min(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
-#if !defined(LWS_NO_CLIENT)
-static int
-lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
-{
-	int m;
-
-	if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
-	    (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
-	    (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
-	    !lwsi_role_client(wsi))
-		return 0;
-
-	// lwsl_notice("%s: hs client gets %d in\n", __func__, (int)len);
-
-	while (len) {
-		/*
-		 * we were accepting input but now we stopped doing so
-		 */
-		if (lws_is_flowcontrolled(wsi)) {
-			//lwsl_notice("%s: caching %ld\n", __func__, (long)len);
-			lws_rxflow_cache(wsi, *buf, 0, (int)len);
-			*buf += len;
-			return 0;
-		}
-		if (wsi->ws->rx_draining_ext) {
-			//lwsl_notice("%s: draining ext\n", __func__);
-			if (lwsi_role_client(wsi))
-				m = lws_ws_client_rx_sm(wsi, 0);
-			else
-				m = lws_ws_rx_sm(wsi, 0, 0);
-			if (m < 0)
-				return -1;
-			continue;
-		}
-		/* caller will account for buflist usage */
-
-		if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
-			lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
-				    __func__, (int)len);
-			return -1;
-		}
-		len--;
-	}
-	// lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
-
-	return 0;
-}
-#endif
 
 /*
  * We have to take care about parsing because the headers may be split
@@ -115,8 +67,8 @@ lws_read_h1(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
 			assert(0);
 		}
 		lwsl_parser("issuing %d bytes to parser\n", (int)len);
-#if !defined(LWS_NO_CLIENT)
-		if (lws_handshake_client(wsi, &buf, (size_t)len))
+#if defined(LWS_ROLE_WS) && !defined(LWS_NO_CLIENT)
+		if (lws_ws_handshake_client(wsi, &buf, (size_t)len))
 			goto bail;
 #endif
 		last_char = buf;
@@ -248,9 +200,9 @@ postbody_completion:
 	case LRS_SHUTDOWN:
 
 ws_mode:
-#if !defined(LWS_NO_CLIENT)
+#if !defined(LWS_NO_CLIENT) && defined(LWS_ROLE_WS)
 		// lwsl_notice("%s: ws_mode\n", __func__);
-		if (lws_handshake_client(wsi, &buf, (size_t)len))
+		if (lws_ws_handshake_client(wsi, &buf, (size_t)len))
 			goto bail;
 #endif
 #if defined(LWS_ROLE_WS)
@@ -263,7 +215,8 @@ ws_mode:
 			goto bail;
 		}
 #endif
-		// lwsl_notice("%s: ws_mode: buf moved on by %d\n", __func__, lws_ptr_diff(buf, oldbuf));
+		// lwsl_notice("%s: ws_mode: buf moved on by %d\n", __func__,
+		//	       lws_ptr_diff(buf, oldbuf));
 		break;
 
 	case LRS_DEFERRING_ACTION:
@@ -293,14 +246,20 @@ read_ok:
 
 bail:
 	/*
-	 * h2 / h2-ws calls us recursively in lws_read()->lws_h2_parser()->
-	 * lws_read() pattern, having stripped the h2 framing in the middle.
+	 * h2 / h2-ws calls us recursively in
+	 *
+	 * lws_read_h1()->
+	 *   lws_h2_parser()->
+	 *     lws_read_h1()
+	 *
+	 * pattern, having stripped the h2 framing in the middle.
 	 *
 	 * When taking down the whole connection, make sure that only the
 	 * outer lws_read() does the wsi close.
 	 */
 	if (!wsi->outer_will_close)
-		lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "lws_read bail");
+		lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
+				   "lws_read_h1 bail");
 
 	return -1;
 }
@@ -526,19 +485,6 @@ rops_handle_POLLIN_h1(struct lws_context_per_thread *pt, struct lws *wsi,
 	}
 #endif
 
-        if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
-            lwsi_state(wsi) == LRS_WAITING_TO_SEND_CLOSE ||
-            lwsi_state(wsi) == LRS_AWAITING_CLOSE_ACK) {
-                /*
-                 * we stopped caring about anything except control
-                 * packets.  Force flow control off, defeat tx
-                 * draining.
-                 */
-                lws_rx_flow_control(wsi, 1);
-                if (wsi->ws)
-                        wsi->ws->tx_draining_ext = 0;
-        }
-
         if (lws_is_flowcontrolled(wsi))
                 /* We cannot deal with any kind of new RX because we are
                  * RX-flowcontrolled.
@@ -669,6 +615,7 @@ struct lws_role_ops role_ops_h1 = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_h1,
diff --git a/lib/roles/h2/http2.c b/lib/roles/h2/http2.c
index c663172961aa1f101e9d8cb4ba1f4866219ff27c..41bc7d232536e7b9c42114adca659687defa0ba1 100644
--- a/lib/roles/h2/http2.c
+++ b/lib/roles/h2/http2.c
@@ -1715,7 +1715,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
 
 			case LWS_H2_FRAME_TYPE_DATA:
 
-				lwsl_notice("%s: LWS_H2_FRAME_TYPE_DATA\n", __func__);
+				lwsl_info("%s: LWS_H2_FRAME_TYPE_DATA\n", __func__);
 
 				/* let the network wsi live a bit longer if subs are active...
 				 * our frame may take a long time to chew through */
@@ -1805,7 +1805,7 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
 					 */
 
 					n = lws_read_h1(h2n->swsi, in - 1, n);
-					lwsl_notice("%s: lws_read_h1 %d\n", __func__, n);
+					// lwsl_notice("%s: lws_read_h1 %d\n", __func__, n);
 					h2n->swsi->outer_will_close = 0;
 					/*
 					 * can return 0 in POST body with
@@ -1819,8 +1819,8 @@ lws_h2_parser(struct lws *wsi, unsigned char *in, lws_filepos_t inlen,
 						h2n->inside = h2n->length;
 						h2n->count = h2n->length - 1;
 
-						if (n < 0)
-							goto already_closed_swsi;
+						//if (n < 0)
+						//	goto already_closed_swsi;
 						goto close_swsi_and_return;
 					}
 
@@ -1983,7 +1983,7 @@ close_swsi_and_return:
 	h2n->frame_state = 0;
 	h2n->count = 0;
 
-already_closed_swsi:
+// already_closed_swsi:
 	*inused = in - oldin;
 
 	return 2;
@@ -2144,7 +2144,7 @@ lws_h2_ws_handshake(struct lws *wsi)
 	 */
 
 	lwsi_set_state(wsi, LRS_ESTABLISHED);
-	wsi->lws_rx_parse_state = LWS_RXPS_NEW;
+	wsi->lws_rx_parse_state = 0; // ==LWS_RXPS_NEW;
 
 	uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_PATH);
 	n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH);
diff --git a/lib/roles/h2/ops-h2.c b/lib/roles/h2/ops-h2.c
index 51845ce774893c74772fb1717e02af179ba72cf8..47bbaff1f41643bdc5c435f1f92cbf97589cc391 100644
--- a/lib/roles/h2/ops-h2.c
+++ b/lib/roles/h2/ops-h2.c
@@ -155,8 +155,10 @@ rops_handle_POLLIN_h2(struct lws_context_per_thread *pt, struct lws *wsi,
 		 * draining.
 		 */
 		lws_rx_flow_control(wsi, 1);
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws)
 			wsi->ws->tx_draining_ext = 0;
+#endif
 	}
 
 	if (wsi->http2_substream || wsi->upgraded_to_http2) {
@@ -254,15 +256,12 @@ drain:
 
 	if (ebuf.len) {
 		n = 0;
-		if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY) {
+		if (lwsi_role_h2(wsi) && lwsi_state(wsi) != LRS_BODY)
 			n = lws_read_h2(wsi, (unsigned char *)ebuf.token,
 				        ebuf.len);
-			// lwsl_notice("h2 n = %d\n", n);
-		} else {
+		else
 			n = lws_read_h1(wsi, (unsigned char *)ebuf.token,
 				        ebuf.len);
-			// lwsl_notice("h1 n = %d\n", n);
-		}
 
 		if (n < 0) {
 			/* we closed wsi */
@@ -352,20 +351,23 @@ static int
 rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len,
 			    enum lws_write_protocol *wp)
 {
-	unsigned char flags = 0;
+	unsigned char flags = 0, base = (*wp) & 0x1f;
 	int n;
 
 	/* if not in a state to send stuff, then just send nothing */
 
 	if (!lwsi_role_ws(wsi) &&
-	    ((*wp) & 0x1f) != LWS_WRITE_HTTP &&
-	    ((*wp) & 0x1f) != LWS_WRITE_HTTP_FINAL &&
-	    ((*wp) & 0x1f) != LWS_WRITE_HTTP_HEADERS_CONTINUATION &&
-	    ((*wp) & 0x1f) != LWS_WRITE_HTTP_HEADERS &&
+	    base != LWS_WRITE_HTTP &&
+	    base != LWS_WRITE_HTTP_FINAL &&
+	    base != LWS_WRITE_HTTP_HEADERS_CONTINUATION &&
+	    base != LWS_WRITE_HTTP_HEADERS &&
 	    ((lwsi_state(wsi) != LRS_RETURNED_CLOSE &&
 	      lwsi_state(wsi) != LRS_WAITING_TO_SEND_CLOSE &&
-	      lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK) ||
-	     ((*wp) & 0x1f) != LWS_WRITE_CLOSE)) {
+	      lwsi_state(wsi) != LRS_AWAITING_CLOSE_ACK)
+#if defined(LWS_ROLE_WS)
+	   || base != LWS_WRITE_CLOSE
+#endif
+	)) {
 		//assert(0);
 		lwsl_notice("binning wsistate 0x%x %d\n", wsi->wsistate, *wp);
 		return 0;
@@ -376,7 +378,7 @@ rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len,
 	 */
 
 	n = LWS_H2_FRAME_TYPE_DATA;
-	if ((*wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) {
+	if (base == LWS_WRITE_HTTP_HEADERS) {
 		n = LWS_H2_FRAME_TYPE_HEADERS;
 		if (!((*wp) & LWS_WRITE_NO_FIN))
 			flags = LWS_H2_FLAG_END_HEADERS;
@@ -387,41 +389,35 @@ rops_write_role_protocol_h2(struct lws *wsi, unsigned char *buf, size_t len,
 		}
 	}
 
-	if ((*wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION) {
+	if (base == LWS_WRITE_HTTP_HEADERS_CONTINUATION) {
 		n = LWS_H2_FRAME_TYPE_CONTINUATION;
 		if (!((*wp) & LWS_WRITE_NO_FIN))
 			flags = LWS_H2_FLAG_END_HEADERS;
-		if (wsi->h2.send_END_STREAM ||
-		    ((*wp) & LWS_WRITE_H2_STREAM_END)) {
+		if (wsi->h2.send_END_STREAM || ((*wp) & LWS_WRITE_H2_STREAM_END)) {
 			flags |= LWS_H2_FLAG_END_STREAM;
 			wsi->h2.send_END_STREAM = 1;
 		}
 	}
 
-	if (((*wp & 0x1f) == LWS_WRITE_HTTP ||
-	     (*wp & 0x1f) == LWS_WRITE_HTTP_FINAL) &&
-	    wsi->http.tx_content_length) {
+	if ((base == LWS_WRITE_HTTP ||
+	     base == LWS_WRITE_HTTP_FINAL) &&
+	     wsi->http.tx_content_length) {
 		wsi->http.tx_content_remain -= len;
-		lwsl_info("%s: wsi %p: tx_content_remain = %llu\n",
-			  __func__, wsi,
+		lwsl_info("%s: wsi %p: tx_content_rem = %llu\n", __func__, wsi,
 			  (unsigned long long)wsi->http.tx_content_remain);
 		if (!wsi->http.tx_content_remain) {
-			lwsl_info("%s: selecting final write mode\n",
-				  __func__);
-			*wp = LWS_WRITE_HTTP_FINAL;
+			lwsl_info("%s: selecting final write mode\n", __func__);
+			base = *wp = LWS_WRITE_HTTP_FINAL;
 		}
 	}
 
-	if ((*wp & 0x1f) == LWS_WRITE_HTTP_FINAL ||
-	    ((*wp) & LWS_WRITE_H2_STREAM_END)) {
-	    //lws_get_network_wsi(wsi)->h2.END_STREAM) {
+	if (base == LWS_WRITE_HTTP_FINAL || ((*wp) & LWS_WRITE_H2_STREAM_END)) {
 		lwsl_info("%s: setting END_STREAM\n", __func__);
 		flags |= LWS_H2_FLAG_END_STREAM;
 		wsi->h2.send_END_STREAM = 1;
 	}
 
-	return lws_h2_frame_write(wsi, n, flags, wsi->h2.my_sid,
-				  (int)len, buf);
+	return lws_h2_frame_write(wsi, n, flags, wsi->h2.my_sid, (int)len, buf);
 }
 
 static int
@@ -471,6 +467,13 @@ static int
 rops_init_vhost_h2(struct lws_vhost *vh,
 		   struct lws_context_creation_info *info)
 {
+	int n;
+
+	vh->h2.set = vh->context->set;
+	if (info->http2_settings[0])
+		for (n = 1; n < LWS_H2_SETTINGS_LEN; n++)
+			vh->h2.set.s[n] = info->http2_settings[n];
+
 	return 0;
 }
 
@@ -510,7 +513,7 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason)
 	if (wsi->http2_substream && wsi->h2_stream_carries_ws)
 		lws_h2_rst_stream(wsi, 0, "none");
 
-	if (wsi->h2.parent_wsi) {
+	if (wsi->h2.parent_wsi && lwsl_visible(LLL_INFO)) {
 		lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi,
 			  wsi->h2.parent_wsi);
 		lws_start_foreach_llp(struct lws **, w,
@@ -523,7 +526,7 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason)
 	if (wsi->upgraded_to_http2 || wsi->http2_substream || wsi->client_h2_substream) {
 		lwsl_info("closing %p: parent %p\n", wsi, wsi->h2.parent_wsi);
 
-		if (wsi->h2.child_list) {
+		if (wsi->h2.child_list && lwsl_visible(LLL_INFO)) {
 			lwsl_info(" parent %p: closing children: list:\n", wsi);
 			lws_start_foreach_llp(struct lws **, w,
 					      wsi->h2.child_list) {
@@ -531,6 +534,8 @@ rops_close_kill_connection_h2(struct lws *wsi, enum lws_close_status reason)
 					  (*w)->role_ops ? (*w)->role_ops->name : "?",
 					  *w);
 			} lws_end_foreach_llp(w, h2.sibling_list);
+		}
+		if (wsi->h2.child_list) {
 			/* trigger closing of all of our http2 children first */
 			lws_start_foreach_llp(struct lws **, w,
 					      wsi->h2.child_list) {
@@ -732,7 +737,10 @@ static int
 rops_perform_user_POLLOUT_h2(struct lws *wsi)
 {
 	struct lws **wsi2, *wsi2a;
-	int write_type = LWS_WRITE_PONG, n;
+#if defined(LWS_ROLE_WS)
+	int write_type = LWS_WRITE_PONG;
+#endif
+	int n;
 
 	wsi = lws_get_network_wsi(wsi);
 
@@ -880,9 +888,11 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			goto next_child;
 		}
 
+#if defined(LWS_ROLE_WS)
+
 		/* Notify peer that we decided to close */
 
-		if (lwsi_state(w) == LRS_WAITING_TO_SEND_CLOSE) {
+		if (lwsi_role_ws(w) && lwsi_state(w) == LRS_WAITING_TO_SEND_CLOSE) {
 			lwsl_debug("sending close packet\n");
 			w->waiting_to_send_close_frame = 0;
 			n = lws_write(w, &w->ws->ping_payload_buf[LWS_PRE],
@@ -934,7 +944,7 @@ rops_perform_user_POLLOUT_h2(struct lws *wsi)
 			/* otherwise for PING, leave POLLOUT active either way */
 			goto next_child;
 		}
-
+#endif
 		if (lws_callback_as_writeable(w)) {
 			lwsl_info("Closing POLLOUT child (end stream %d)\n",
 				  w->h2.send_END_STREAM);
@@ -1023,6 +1033,7 @@ struct lws_role_ops role_ops_h2 = {
 	/* check_upgrades */		rops_check_upgrades_h2,
 	/* init_context */		rops_init_context_h2,
 	/* init_vhost */		rops_init_vhost_h2,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_h2,
diff --git a/lib/roles/http/client/client.c b/lib/roles/http/client/client.c
index b4bc63d119583d7d9e1a3a2f9c327ace93818af1..79811b549686733600589bcde56b5869c2325c88 100644
--- a/lib/roles/http/client/client.c
+++ b/lib/roles/http/client/client.c
@@ -1027,9 +1027,10 @@ lws_generate_client_handshake(struct lws *wsi, char *pkt)
 				     lws_hdr_simple_ptr(wsi,
 						     _WSI_TOKEN_CLIENT_ORIGIN));
 	}
-
+#if defined(LWS_ROLE_WS)
 	if (wsi->do_ws)
 		p = lws_generate_client_ws_handshake(wsi, p);
+#endif
 
 	/* give userland a chance to append, eg, cookies */
 
diff --git a/lib/roles/http/server/server.c b/lib/roles/http/server/server.c
index 816e988ac9befcbb9968b48359e3a3ee91916aa6..666f061d96bb1c3eb886d375ba1e1e624dfad89e 100644
--- a/lib/roles/http/server/server.c
+++ b/lib/roles/http/server/server.c
@@ -1519,9 +1519,11 @@ raw_transition:
 			if (!strcasecmp(lws_hdr_simple_ptr(wsi,
 							   WSI_TOKEN_UPGRADE),
 					"websocket")) {
+#if defined(LWS_ROLE_WS)
 				wsi->vhost->conn_stats.ws_upg++;
 				lwsl_info("Upgrade to ws\n");
 				goto upgrade_ws;
+#endif
 			}
 #if defined(LWS_WITH_HTTP2)
 			if (!strcasecmp(lws_hdr_simple_ptr(wsi,
@@ -1601,13 +1603,13 @@ upgrade_h2c:
 
 		return 0;
 #endif
-
+#if defined(LWS_ROLE_WS)
 upgrade_ws:
 		if (lws_process_ws_upgrade(wsi))
 			goto bail_nuke_ah;
 
 		return 0;
-
+#endif
 	} /* while all chars are handled */
 
 	return 0;
@@ -1871,6 +1873,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
                        lwsl_notice("OOM trying to get user_space\n");
 			goto bail;
                }
+#if defined(LWS_ROLE_WS)
                if (type & LWS_ADOPT_WS_PARENTIO) {
 			new_wsi->desc.sockfd = LWS_SOCK_INVALID;
 			lwsl_debug("binding to %s\n", new_wsi->protocol->name);
@@ -1887,6 +1890,7 @@ lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
 
 			return new_wsi;
                }
+#endif
 	} else
 		if (type & LWS_ADOPT_HTTP) {/* he will transition later */
 			new_wsi->protocol =
diff --git a/lib/roles/listen/ops-listen.c b/lib/roles/listen/ops-listen.c
index 5cbbdd64357e954893bae0816b77531fc808a155..cea1494faabf8c1f3ea08ff8bc3a0676491b5cbe 100644
--- a/lib/roles/listen/ops-listen.c
+++ b/lib/roles/listen/ops-listen.c
@@ -155,6 +155,7 @@ struct lws_role_ops role_ops_listen = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_listen,
diff --git a/lib/roles/pipe/ops-pipe.c b/lib/roles/pipe/ops-pipe.c
index a4374a77301dd239a91d0e9238c1f2d87a0e10ac..13673444090449553df230ad83760f223c1d4868 100644
--- a/lib/roles/pipe/ops-pipe.c
+++ b/lib/roles/pipe/ops-pipe.c
@@ -60,6 +60,7 @@ struct lws_role_ops role_ops_pipe = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_pipe,
diff --git a/lib/roles/raw/ops-raw.c b/lib/roles/raw/ops-raw.c
index 7096101536cbd9941a387d344d4cf6369b2758bd..24006fed2d8d64d984410d9e69a20ed72abcf219 100644
--- a/lib/roles/raw/ops-raw.c
+++ b/lib/roles/raw/ops-raw.c
@@ -169,6 +169,7 @@ struct lws_role_ops role_ops_raw_skt = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_raw_skt,
@@ -195,6 +196,7 @@ struct lws_role_ops role_ops_raw_file = {
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
 	/* init_vhost */		NULL,
+	/* destroy_vhost */		NULL,
 	/* periodic_checks */		NULL,
 	/* service_flag_pending */	NULL,
 	/* handle_POLLIN */		rops_handle_POLLIN_raw_file,
diff --git a/lib/roles/ws/client-parser.c b/lib/roles/ws/client-parser-ws.c
similarity index 99%
rename from lib/roles/ws/client-parser.c
rename to lib/roles/ws/client-parser-ws.c
index 9fc87f4b74b7c0d9d764227cb99b7320857532d8..e15025c8e72ddd9094b973772c41553c2e06c2bc 100644
--- a/lib/roles/ws/client-parser.c
+++ b/lib/roles/ws/client-parser-ws.c
@@ -37,6 +37,7 @@ int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
 	ebuf.token = NULL;
 	ebuf.len = 0;
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	if (wsi->ws->rx_draining_ext) {
 		assert(!c);
 
@@ -46,6 +47,7 @@ int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
 
 		goto drain_extension;
 	}
+#endif
 
 	if (wsi->socket_is_permanently_unusable)
 		return -1;
@@ -310,10 +312,10 @@ int lws_ws_client_rx_sm(struct lws *wsi, unsigned char c)
 	case LWS_RXPS_WS_FRAME_PAYLOAD:
 
 		assert(wsi->ws->rx_ubuf);
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws->rx_draining_ext)
 			goto drain_extension;
-
+#endif
 		if (wsi->ws->this_frame_masked && !wsi->ws->all_zero_nonce)
 			c ^= wsi->ws->mask[(wsi->ws->mask_idx++) & 3];
 
@@ -486,8 +488,8 @@ ping_drop:
 		if (wsi->ws->opcode == LWSWSOPC_PONG && !ebuf.len)
 			goto already_done;
 
-drain_extension:
 #if !defined(LWS_WITHOUT_EXTENSIONS)
+drain_extension:
 		lwsl_ext("%s: passing %d to ext\n", __func__, ebuf.len);
 
 		n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &ebuf, 0);
@@ -548,9 +550,9 @@ utf8_fail:
 
 		if (
 				/* coverity says dead code otherwise */
-#if !defined(LWS_WITHOUT_EXTENSIONS)
+//#if !defined(LWS_WITHOUT_EXTENSIONS)
 				n &&
-#endif
+//#endif
 				ebuf.len)
 			/* extension had more... main loop will come back
 			 * we want callback to be done with this set, if so,
diff --git a/lib/roles/ws/client-ws.c b/lib/roles/ws/client-ws.c
index f7ef6935e4c4b1ef894a465e69d1f4b46ea6e86f..2fe5b4f9d5f36913ea57c6a882caf857b46e3193 100644
--- a/lib/roles/ws/client-ws.c
+++ b/lib/roles/ws/client-ws.c
@@ -61,6 +61,57 @@ lws_create_client_ws_object(struct lws_client_connect_info *i, struct lws *wsi)
 	return 0;
 }
 
+#if !defined(LWS_NO_CLIENT)
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
+{
+	if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
+	    (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
+	    (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
+	    !lwsi_role_client(wsi))
+		return 0;
+
+	// lwsl_notice("%s: hs client gets %d in\n", __func__, (int)len);
+
+	while (len) {
+		/*
+		 * we were accepting input but now we stopped doing so
+		 */
+		if (lws_is_flowcontrolled(wsi)) {
+			//lwsl_notice("%s: caching %ld\n", __func__, (long)len);
+			lws_rxflow_cache(wsi, *buf, 0, (int)len);
+			*buf += len;
+			return 0;
+		}
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+		if (wsi->ws->rx_draining_ext) {
+			int m;
+
+			//lwsl_notice("%s: draining ext\n", __func__);
+			if (lwsi_role_client(wsi))
+				m = lws_ws_client_rx_sm(wsi, 0);
+			else
+				m = lws_ws_rx_sm(wsi, 0, 0);
+			if (m < 0)
+				return -1;
+			continue;
+		}
+#endif
+		/* caller will account for buflist usage */
+
+		if (lws_ws_client_rx_sm(wsi, *(*buf)++)) {
+			lwsl_notice("%s: client_rx_sm exited, DROPPING %d\n",
+				    __func__, (int)len);
+			return -1;
+		}
+		len--;
+	}
+	// lwsl_notice("%s: finished with %ld\n", __func__, (long)len);
+
+	return 0;
+}
+#endif
+
 char *
 lws_generate_client_ws_handshake(struct lws *wsi, char *p)
 {
diff --git a/lib/roles/ws/ops-ws.c b/lib/roles/ws/ops-ws.c
index 369b892090aca3c7b2aa9469dd7223515d0e2530..acc20235069fc1e6da69f6a513a3b45c92daf4e7 100644
--- a/lib/roles/ws/ops-ws.c
+++ b/lib/roles/ws/ops-ws.c
@@ -48,6 +48,7 @@ lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c)
 
 	switch (wsi->lws_rx_parse_state) {
 	case LWS_RXPS_NEW:
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws->rx_draining_ext) {
 			ebuf.token = NULL;
 			ebuf.len = 0;
@@ -57,6 +58,7 @@ lws_ws_rx_sm(struct lws *wsi, char already_processed, unsigned char c)
 
 			goto drain_extension;
 		}
+#endif
 		switch (wsi->ws->ietf_spec_revision) {
 		case 13:
 			/*
@@ -385,12 +387,12 @@ handle_first:
 			wsi->lws_rx_parse_state = LWS_RXPS_NEW;
 			goto spill;
 		}
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws->rx_draining_ext) {
 			lwsl_debug("%s: UNTIL_EXHAUSTED draining\n", __func__);
 			goto drain_extension;
 		}
-
+#endif
 		/*
 		 * if there's no protocol max frame size given, we are
 		 * supposed to default to context->pt_serv_buf_size
@@ -554,8 +556,9 @@ ping_drop:
 
 		if (wsi->ws->opcode == LWSWSOPC_PONG && !ebuf.len)
 			goto already_done;
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 drain_extension:
+#endif
 		// lwsl_notice("%s: passing %d to ext\n", __func__, ebuf.len);
 
 		if (lwsi_state(wsi) == LRS_RETURNED_CLOSE ||
@@ -676,6 +679,7 @@ LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi)
 void
 lws_add_wsi_to_draining_ext_list(struct lws *wsi)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
 
 	if (wsi->ws->rx_draining_ext)
@@ -684,15 +688,17 @@ lws_add_wsi_to_draining_ext_list(struct lws *wsi)
 	lwsl_debug("%s: RX EXT DRAINING: Adding to list\n", __func__);
 
 	wsi->ws->rx_draining_ext = 1;
-	wsi->ws->rx_draining_ext_list = pt->rx_draining_ext_list;
-	pt->rx_draining_ext_list = wsi;
+	wsi->ws->rx_draining_ext_list = pt->ws.rx_draining_ext_list;
+	pt->ws.rx_draining_ext_list = wsi;
+#endif
 }
 
 void
 lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
-	struct lws **w = &pt->rx_draining_ext_list;
+	struct lws **w = &pt->ws.rx_draining_ext_list;
 
 	if (!wsi->ws->rx_draining_ext)
 		return;
@@ -711,6 +717,7 @@ lws_remove_wsi_from_draining_ext_list(struct lws *wsi)
 		w = &((*w)->ws->rx_draining_ext_list);
 	}
 	wsi->ws->rx_draining_ext_list = NULL;
+#endif
 }
 
 LWS_EXTERN void
@@ -801,11 +808,15 @@ lws_server_init_wsi_for_ws(struct lws *wsi)
 LWS_VISIBLE int
 lws_is_final_fragment(struct lws *wsi)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
        lwsl_debug("%s: final %d, rx pk length %ld, draining %ld\n", __func__,
 			wsi->ws->final, (long)wsi->ws->rx_packet_length,
 			(long)wsi->ws->rx_draining_ext);
 	return wsi->ws->final && !wsi->ws->rx_packet_length &&
 	       !wsi->ws->rx_draining_ext;
+#else
+	return wsi->ws->final && !wsi->ws->rx_packet_length;
+#endif
 }
 
 LWS_VISIBLE int
@@ -933,10 +944,12 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 		 * draining.
 		 */
 		lws_rx_flow_control(wsi, 1);
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws)
 			wsi->ws->tx_draining_ext = 0;
+#endif
 	}
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	if (wsi->ws->tx_draining_ext)
 		/*
 		 * We cannot deal with new RX until the TX ext path has
@@ -947,7 +960,7 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 		 * blocking.
 		 */
 		return LWS_HPI_RET_HANDLED;
-
+#endif
 	if (lws_is_flowcontrolled(wsi)) {
 		/* We cannot deal with any kind of new RX because we are
 		 * RX-flowcontrolled.
@@ -969,6 +982,7 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 	}
 #endif
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	/* 2: RX Extension needs to be drained
 	 */
 
@@ -995,6 +1009,7 @@ rops_handle_POLLIN_ws(struct lws_context_per_thread *pt, struct lws *wsi,
 		 * priority either.
 		 */
 		return LWS_HPI_RET_HANDLED;
+#endif
 
 	/* 3: buflist needs to be drained
 	 */
@@ -1062,6 +1077,8 @@ read:
 		}
 		// lwsl_notice("Actual RX %d\n", ebuf.len);
 
+		lws_restart_ws_ping_pong_timer(wsi);
+
 		/*
 		 * coverity thinks ssl_capable_read() may read over
 		 * 2GB.  Dissuade it...
@@ -1157,8 +1174,10 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 #endif
 	int n;
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	lwsl_debug("%s: %s: wsi->ws->tx_draining_ext %d\n", __func__,
 			wsi->protocol->name, wsi->ws->tx_draining_ext);
+#endif
 
 	/* Priority 3: pending control packets (pong or close)
 	 *
@@ -1250,6 +1269,7 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 	if (lwsi_state(wsi) == LRS_RETURNED_CLOSE)
 		return LWS_HP_RET_USER_SERVICE;
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	/* Priority 5: Tx path extension with more to send
 	 *
 	 *	       These are handled as new fragments each time around
@@ -1267,7 +1287,6 @@ int rops_handle_POLLOUT_ws(struct lws *wsi)
 
 	/* Priority 6: extensions
 	 */
-#if !defined(LWS_WITHOUT_EXTENSIONS)
 	if (!wsi->ws->extension_data_pending)
 		return LWS_HP_RET_USER_SERVICE;
 
@@ -1412,6 +1431,7 @@ rops_periodic_checks_ws(struct lws_context *context, int tsi, time_t now)
 static int
 rops_service_flag_pending_ws(struct lws_context *context, int tsi)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	struct lws_context_per_thread *pt = &context->pt[tsi];
 	struct lws *wsi;
 	int forced = 0;
@@ -1422,7 +1442,7 @@ rops_service_flag_pending_ws(struct lws_context *context, int tsi)
 	 * 1) For all guys with already-available ext data to drain, if they are
 	 * not flowcontrolled, fake their POLLIN status
 	 */
-	wsi = pt->rx_draining_ext_list;
+	wsi = pt->ws.rx_draining_ext_list;
 	while (wsi) {
 		pt->fds[wsi->position_in_fds_table].revents |=
 			pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
@@ -1433,6 +1453,9 @@ rops_service_flag_pending_ws(struct lws_context *context, int tsi)
 	}
 
 	return forced;
+#else
+	return 0;
+#endif
 }
 
 static int
@@ -1466,8 +1489,9 @@ rops_close_via_role_protocol_ws(struct lws *wsi, enum lws_close_status reason)
 static int
 rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	if (wsi->ws->rx_draining_ext) {
-		struct lws **w = &pt->rx_draining_ext_list;
+		struct lws **w = &pt->ws.rx_draining_ext_list;
 
 		wsi->ws->rx_draining_ext = 0;
 		/* remove us from context draining ext list */
@@ -1482,7 +1506,7 @@ rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
 	}
 
 	if (wsi->ws->tx_draining_ext) {
-		struct lws **w = &pt->tx_draining_ext_list;
+		struct lws **w = &pt->ws.tx_draining_ext_list;
 		lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__);
 		wsi->ws->tx_draining_ext = 0;
 		/* remove us from context draining ext list */
@@ -1495,6 +1519,7 @@ rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
 		}
 		wsi->ws->tx_draining_ext_list = NULL;
 	}
+#endif
 	lws_free_set_NULL(wsi->ws->rx_ubuf);
 
 	if (wsi->trunc_alloc)
@@ -1504,6 +1529,11 @@ rops_close_role_ws(struct lws_context_per_thread *pt, struct lws *wsi)
 	wsi->ws->ping_payload_len = 0;
 	wsi->ws->ping_pending_flag = 0;
 
+	/* deallocate any active extension contexts */
+
+	if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0)
+		lwsl_warn("extension destruction failed\n");
+
 	return 0;
 }
 
@@ -1511,20 +1541,22 @@ static int
 rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 			    enum lws_write_protocol *wp)
 {
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
+	enum lws_write_protocol wpt;
+#endif
 	int masked7 = lwsi_role_client(wsi);
 	unsigned char is_masked_bit = 0;
 	unsigned char *dropmask = NULL;
-	enum lws_write_protocol wpt;
 	struct lws_tokens ebuf;
 	size_t orig_len = len;
 	int pre = 0, n = 0;
 
 	// lwsl_err("%s: wp 0x%x len %d\n", __func__, *wp, (int)len);
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	if (wsi->ws->tx_draining_ext) {
 		/* remove us from the list */
-		struct lws **w = &pt->tx_draining_ext_list;
+		struct lws **w = &pt->ws.tx_draining_ext_list;
 
 		lwsl_notice("%s: CLEARING tx_draining_ext\n", __func__);
 		wsi->ws->tx_draining_ext = 0;
@@ -1555,7 +1587,7 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 				wsi->ws->tx_draining_stashed_wp, wpt);
 		// assert(0);
 	}
-
+#endif
 	lws_restart_ws_ping_pong_timer(wsi);
 
 	if (((*wp) & 0x1f) == LWS_WRITE_HTTP ||
@@ -1610,8 +1642,8 @@ rops_write_role_protocol_ws(struct lws *wsi, unsigned char *buf, size_t len,
 			lwsl_notice("write drain len %d (wp 0x%x) SETTING tx_draining_ext\n", (int)ebuf.len, *wp);
 			/* extension requires further draining */
 			wsi->ws->tx_draining_ext = 1;
-			wsi->ws->tx_draining_ext_list = pt->tx_draining_ext_list;
-			pt->tx_draining_ext_list = wsi;
+			wsi->ws->tx_draining_ext_list = pt->ws.tx_draining_ext_list;
+			pt->ws.tx_draining_ext_list = wsi;
 			/* we must come back to do more */
 			lws_callback_on_writable(wsi);
 			/*
@@ -1861,12 +1893,77 @@ rops_callback_on_writable_ws(struct lws *wsi)
 	return 0;
 }
 
+static int
+rops_init_vhost_ws(struct lws_vhost *vh, struct lws_context_creation_info *info)
+{
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+#ifdef LWS_WITH_PLUGINS
+	struct lws_plugin *plugin = vh->context->plugin_list;
+	int m;
+
+	if (vh->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->ws.extensions = lws_zalloc(sizeof(struct lws_extension) *
+				     (m + vh->context->plugin_extension_count + 1),
+				     "extensions");
+		if (!vh->ws.extensions)
+			return 1;
+
+		memcpy((struct lws_extension *)vh->ws.extensions, info->extensions,
+		       sizeof(struct lws_extension) * m);
+		plugin = vh->context->plugin_list;
+		while (plugin) {
+			memcpy((struct lws_extension *)&vh->ws.extensions[m],
+				plugin->caps.extensions,
+			       sizeof(struct lws_extension) *
+			       plugin->caps.count_extensions);
+			m += plugin->caps.count_extensions;
+			plugin = plugin->list;
+		}
+	} else
+#endif
+		vh->ws.extensions = info->extensions;
+#endif
+
+	return 0;
+}
+
+static int
+rops_destroy_vhost_ws(struct lws_vhost *vh)
+{
+#ifdef LWS_WITH_PLUGINS
+#if !defined(LWS_WITHOUT_EXTENSIONS)
+	if (vh->context->plugin_extension_count)
+		lws_free((void *)vh->ws.extensions);
+#endif
+#endif
+
+	return 0;
+}
+
+static int
+rops_destroy_role_ws(struct lws *wsi)
+{
+	lws_free_set_NULL(wsi->ws);
+
+	return 0;
+}
+
 struct lws_role_ops role_ops_ws = {
 	/* role name */			"ws",
 	/* alpn id */			NULL,
 	/* check_upgrades */		NULL,
 	/* init_context */		NULL,
-	/* init_vhost */		NULL,
+	/* init_vhost */		rops_init_vhost_ws,
+	/* destroy_vhost */		rops_destroy_vhost_ws,
 	/* periodic_checks */		rops_periodic_checks_ws,
 	/* service_flag_pending */	rops_service_flag_pending_ws,
 	/* handle_POLLIN */		rops_handle_POLLIN_ws,
@@ -1880,7 +1977,7 @@ struct lws_role_ops role_ops_ws = {
 	/* close_via_role_protocol */	rops_close_via_role_protocol_ws,
 	/* close_role */		rops_close_role_ws,
 	/* close_kill_connection */	rops_close_kill_connection_ws,
-	/* destroy_role */		NULL,
+	/* destroy_role */		rops_destroy_role_ws,
 	/* writeable cb clnt, srv */	{ LWS_CALLBACK_CLIENT_WRITEABLE,
 					  LWS_CALLBACK_SERVER_WRITEABLE },
 	/* close cb clnt, srv */	{ LWS_CALLBACK_CLIENT_CLOSED,
diff --git a/lib/roles/ws/private.h b/lib/roles/ws/private.h
index 2fd1aa65d7fd68f0589f6df3c444dff9393de4a1..8f81b9e8c0ed41c1d60b655e5106ab406bb54710 100644
--- a/lib/roles/ws/private.h
+++ b/lib/roles/ws/private.h
@@ -73,20 +73,25 @@ enum lws_websocket_opcodes_07 {
 #define ALREADY_PROCESSED_IGNORE_CHAR 1
 #define ALREADY_PROCESSED_NO_CB 2
 
-struct lws_vhost_role_ws {
 #if !defined(LWS_WITHOUT_EXTENSIONS)
+struct lws_vhost_role_ws {
 	const struct lws_extension *extensions;
-#endif
 };
 
+struct lws_pt_role_ws {
+	struct lws *rx_draining_ext_list;
+	struct lws *tx_draining_ext_list;
+};
+#endif
+
 struct _lws_websocket_related {
 	char *rx_ubuf;
 #if !defined(LWS_WITHOUT_EXTENSIONS)
 	const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE];
 	void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE];
-#endif
 	struct lws *rx_draining_ext_list;
 	struct lws *tx_draining_ext_list;
+#endif
 	/* Also used for close content... control opcode == < 128 */
 	uint8_t ping_payload_buf[128 - 3 + LWS_PRE];
 	uint8_t mask[4];
@@ -120,20 +125,22 @@ struct _lws_websocket_related {
 	unsigned int owed_a_fin:1;
 	unsigned int check_utf8:1;
 	unsigned int defeat_check_utf8:1;
-	unsigned int pmce_compressed_message:1;
 	unsigned int stashed_write_pending:1;
-	unsigned int rx_draining_ext:1;
-	unsigned int tx_draining_ext:1;
 	unsigned int send_check_ping:1;
 	unsigned int first_fragment:1;
 	unsigned int peer_has_sent_close:1;
 #if !defined(LWS_WITHOUT_EXTENSIONS)
 	unsigned int extension_data_pending:1;
+	unsigned int rx_draining_ext:1;
+	unsigned int tx_draining_ext:1;
 
 	uint8_t count_act_ext;
 #endif
 };
 
+int
+lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len);
+
 #if !defined(LWS_WITHOUT_EXTENSIONS)
 LWS_VISIBLE void
 lws_context_init_extensions(struct lws_context_creation_info *info,
diff --git a/lib/roles/ws/server-ws.c b/lib/roles/ws/server-ws.c
index 3dca2e8aa76d76e2016e1e7ccebe4f39007c1e56..8327918629c52f70e905dad1ba761ac41ec25e21 100644
--- a/lib/roles/ws/server-ws.c
+++ b/lib/roles/ws/server-ws.c
@@ -736,8 +736,10 @@ utf8_fail:
 
 	wsi->ws->first_fragment = 0;
 
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 	lwsl_info("%s: input used %d, output %d, rem len %d, rx_draining_ext %d\n",
 		  __func__, avail, ebuf.len, (int)len, wsi->ws->rx_draining_ext);
+#endif
 
 	return avail; /* how much we used from the input */
 }
@@ -765,7 +767,7 @@ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
 			*buf += len; /* stashing it is taking care of it */
 			return 1;
 		}
-
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 		if (wsi->ws->rx_draining_ext) {
 			lwsl_debug("%s: draining rx ext\n", __func__);
 			m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR, 0);
@@ -773,6 +775,7 @@ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
 				return -1;
 			continue;
 		}
+#endif
 
 		/* consume payload bytes efficiently */
 		while (wsi->lws_rx_parse_state == LWS_RXPS_WS_FRAME_PAYLOAD &&
@@ -807,10 +810,12 @@ lws_parse_ws(struct lws *wsi, unsigned char **buf, size_t len)
 			 * We already handled this byte in bulk, just deal
 			 * with the ramifications
 			 */
+#if !defined(LWS_WITHOUT_EXTENSIONS)
 			lwsl_debug("%s: coming out of bulk with len %d, "
 				   "wsi->ws->rx_draining_ext %d\n",
 				   __func__, (int)len,
 				   wsi->ws->rx_draining_ext);
+#endif
 			m = lws_ws_rx_sm(wsi, ALREADY_PROCESSED_IGNORE_CHAR |
 					 ALREADY_PROCESSED_NO_CB, 0);
 		}
diff --git a/lib/service.c b/lib/service.c
index 3c44ef29a9e74744ed00b30694877f6928b2aa32..2ead7ffdf82247e9819eac39d0f4420a12e6c838 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -41,8 +41,6 @@ lws_callback_as_writeable(struct lws *wsi)
 	}
 #endif
 
-	assert(!(lwsi_role_ws(wsi) && wsi->ws->tx_draining_ext));
-
 	n = wsi->role_ops->writeable_cb[lwsi_role_server(wsi)];
 
 	m = user_callback_handle_rxflow(wsi->protocol->callback,
@@ -319,9 +317,9 @@ lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi)
 	 * We only need to wait if really nothing already to do and we have
 	 * to wait for something from network
 	 */
-#if defined(LWS_ROLE_WS)
+#if defined(LWS_ROLE_WS) && !defined(LWS_WITHOUT_EXTENSIONS)
 	/* 1) if we know we are draining rx ext, do not wait in poll */
-	if (pt->rx_draining_ext_list)
+	if (pt->ws.rx_draining_ext_list)
 		return 0;
 #endif
 
diff --git a/lib/tls/openssl/ssl.c b/lib/tls/openssl/ssl.c
index b85567f282ea4135e47eaa8ca15774b841384438..14928435fb13bef72b970987ca1a31e96d436572 100644
--- a/lib/tls/openssl/ssl.c
+++ b/lib/tls/openssl/ssl.c
@@ -255,8 +255,6 @@ lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len)
 	if (wsi->vhost)
 		wsi->vhost->conn_stats.rx += n;
 
-	lws_restart_ws_ping_pong_timer(wsi);
-
 	// lwsl_hexdump_err(buf, n);
 
 	/*
diff --git a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt
index 6e0af9cb6d78570104d7e88ae6d7588730f6b7fc..c76f91e86d35a3f4194167b99175795ff61698ab 100644
--- a/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt
+++ b/minimal-examples/ws-client/minimal-ws-client-pmd-bulk/CMakeLists.txt
@@ -63,8 +63,8 @@ MACRO(require_lws_config reqconfig _val result)
 ENDMACRO()
 
 set(requirements 1)
-require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
-require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+require_lws_config(LWS_WITHOUT_CLIENT 0 requirements)
+#require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
 
 if (requirements)
 	add_executable(${SAMP} ${SRCS})
diff --git a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt
index 82b706526d3a7118a247fc14718ded77a192e744..962c98832f041cda470d8f784ed1656bbea0e215 100644
--- a/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt
+++ b/minimal-examples/ws-server/minimal-ws-server-pmd-bulk/CMakeLists.txt
@@ -64,7 +64,7 @@ ENDMACRO()
 
 set(requirements 1)
 require_lws_config(LWS_WITHOUT_SERVER 0 requirements)
-require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
+#require_lws_config(LWS_WITHOUT_EXTENSIONS 0 requirements)
 
 if (requirements)
 	add_executable(${SAMP} ${SRCS})
@@ -75,4 +75,4 @@ if (requirements)
 	else()
 		target_link_libraries(${SAMP} websockets)
 	endif()
-endif()
\ No newline at end of file
+endif()
diff --git a/plugins/protocol_dumb_increment.c b/plugins/protocol_dumb_increment.c
index 8af2edd6bb70043ca7e5777d94342af05def4525..43e707d6f37553eacf8c36ce8a67517c289a4acd 100644
--- a/plugins/protocol_dumb_increment.c
+++ b/plugins/protocol_dumb_increment.c
@@ -109,9 +109,9 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 	case LWS_CALLBACK_RECEIVE:
 		if (len < 6)
 			break;
-		if (strcmp((const char *)in, "reset\n") == 0)
+		if (strncmp((const char *)in, "reset\n", 6) == 0)
 			pss->number = 0;
-		if (strcmp((const char *)in, "closeme\n") == 0) {
+		if (strncmp((const char *)in, "closeme\n", 8) == 0) {
 			lwsl_notice("dumb_inc: closing as requested\n");
 			lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
 					 (unsigned char *)"seeya", 5);
diff --git a/plugins/protocol_lws_meta.c b/plugins/protocol_lws_meta.c
deleted file mode 100644
index 69324db070a5c4f458647cffb03422893ac3d5b6..0000000000000000000000000000000000000000
--- a/plugins/protocol_lws_meta.c
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * lws meta protocol handler
- *
- * Copyright (C) 2017 Andy Green <andy@warmcat.com>
- *
- *  This library is free software; you can redistribute it and/or
- *  modify it under the terms of the GNU Lesser 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
- *  Lesser General Public License for more details.
- *
- *  You should have received a copy of the GNU Lesser 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
- *
- */
-
-#if !defined (LWS_PLUGIN_STATIC)
-#define LWS_DLL
-#define LWS_INTERNAL
-#include "../lib/libwebsockets.h"
-#endif
-
-#include <string.h>
-#include <stdlib.h>
-
-#define MAX_SUBCHANNELS 8
-
-enum lws_meta_parser_state {
-	MP_IDLE, /* in body of message */
-
-	MP_CMD, /* await cmd */
-
-	MP_OPEN_SUBCHANNEL_PROTOCOL,
-	MP_OPEN_SUBCHANNEL_URL,
-	MP_OPEN_SUBCHANNEL_COOKIE,
-
-	MP_CLOSE_CHID,
-	MP_CLOSE_LEN,
-	MP_CLOSE_CODEM,
-	MP_CLOSE_CODEL,
-	MP_CLOSE_PAYLOAD,
-
-	MP_WRITE_CHID,
-};
-
-enum {
-	PENDING_TYPE_OPEN_RESULT = 0,
-	PENDING_TYPE_CHILD_CLOSE
-};
-
-/*
- * while we haven't reported the result yet, we keep a linked-list of
- * connection opens and their result.
- */
-struct pending_conn {
-	struct pending_conn *next;
-	char protocol[123];
-	char cookie[8];
-	int ch;
-	int len;
-
-	unsigned char type;
-};
-
-/*
- * the parent, lws-meta connection
- */
-struct per_session_data__lws_meta {
-	struct lws *wsi[MAX_SUBCHANNELS + 1];
-	char told_closing[MAX_SUBCHANNELS + 1];
-	struct pending_conn *first;
-	struct pending_conn *pend;
-	char suburl[64];
-	unsigned char close[126];
-	int active_subchannel_tx, active_subchannel_rx;
-	enum lws_meta_parser_state state;
-	int pos;
-	int count_pending;
-	int round_robin;
-	int close_status_16;
-	int close_len;
-	int which_close;
-	int ch;
-};
-
-static int
-lws_find_free_channel(struct per_session_data__lws_meta *pss)
-{
-	int n;
-
-	for (n = 1; n <= MAX_SUBCHANNELS; n++)
-		if (pss->wsi[n] == NULL)
-			return n;
-
-	return 0; /* none free */
-}
-
-static struct lws *
-lws_get_channel_wsi(struct per_session_data__lws_meta *pss, int ch)
-{
-	if (!ch)
-		return 0;
-	return pss->wsi[ch];
-}
-
-static int
-lws_get_channel_id(struct lws *wsi)
-{
-	return (int)(lws_intptr_t)lws_get_opaque_parent_data(wsi);
-}
-
-static void
-lws_set_channel_id(struct lws *wsi, int id)
-{
-	lws_set_opaque_parent_data(wsi, (void *)(lws_intptr_t)id);
-}
-
-static struct pending_conn *
-new_pending(struct per_session_data__lws_meta *pss)
-{
-	struct pending_conn *pend;
-
-	if (pss->count_pending >= MAX_SUBCHANNELS * 2) {
-		lwsl_notice("too many pending open subchannel\n");
-
-		return NULL;
-	}
-
-	pss->count_pending++;
-
-	pend = malloc(sizeof(*pend));
-	if (!pend) {
-		lwsl_notice("OOM\n");
-
-		return NULL;
-	}
-
-	memset(pend, 0, sizeof(*pend));
-
-	return pend;
-}
-
-static int
-callback_lws_meta(struct lws *wsi, enum lws_callback_reasons reason,
-		    void *user, void *in, size_t len)
-{
-	struct per_session_data__lws_meta *pss =
-			(struct per_session_data__lws_meta *)user;
-	struct lws_write_passthru *pas;
-	struct pending_conn *pend, *pend1;
-	struct lws *cwsi;
-	lws_sock_file_fd_type fd;
-	unsigned char *bin, buf[LWS_PRE + 512], *start = &buf[LWS_PRE],
-			*end = &buf[sizeof(buf) - 1], *p = start;
-	int n, m;
-
-	switch (reason) {
-
-	case LWS_CALLBACK_ESTABLISHED:
-		lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__);
-		pss->state = MP_CMD;
-		pss->pos = 0;
-		break;
-
-	case LWS_CALLBACK_CLOSED:
-		break;
-
-	case LWS_CALLBACK_CHILD_CLOSING:
-		cwsi = (struct lws *)in;
-
-		/* remove it from our tracking */
-		pss->wsi[lws_get_channel_id(cwsi)] = NULL;
-
-		if (pss->told_closing[lws_get_channel_id(cwsi)]) {
-			pss->told_closing[lws_get_channel_id(cwsi)] = 0;
-			break;
-		}
-
-		pend = new_pending(pss);
-		if (!pend)
-			return -1;
-
-		/* note which channel id */
-		pend->ch = lws_get_channel_id(cwsi);
-
-		if (lws_get_close_length(cwsi)) {
-			pend->len = lws_get_close_length(cwsi);
-			memcpy(pend->protocol, lws_get_close_payload(cwsi),
-					pend->len);
-		}
-
-		pend->type = PENDING_TYPE_CHILD_CLOSE;
-		pend->next = pss->first;
-		pss->first = pend;
-
-		/*
-		 * nothing else will complete from this wsi, so abandon
-		 * tracking in-process messages from this wsi.
-		 */
-
-		if (pss->active_subchannel_tx == pend->ch)
-			pss->active_subchannel_tx = 0;
-
-		if (pss->active_subchannel_rx == pend->ch)
-			pss->active_subchannel_rx = 0;
-		break;
-
-	case LWS_CALLBACK_SERVER_WRITEABLE:
-
-		if (!pss->active_subchannel_tx) {
-
-			/* not in the middle of a message...
-			 *
-			 * PRIORITY 1: pending open and close notifications
-			 */
-
-			pend = pss->first;
-			while (pend && p < end - 128) {
-				switch (pend->type) {
-				case PENDING_TYPE_OPEN_RESULT:
-					lwsl_debug("open result %s %s\n",
-						pend->cookie, pend->protocol);
-					*p++ = LWS_META_CMD_OPEN_RESULT;
-					memcpy(p, pend->cookie,
-					       strlen(pend->cookie) + 1);
-					p += strlen(pend->cookie) + 1;
-					*p++ = LWS_META_TRANSPORT_OFFSET +
-							pend->ch;
-					memcpy(p, pend->protocol,
-					       strlen(pend->protocol) + 1);
-					p += strlen(pend->protocol) + 1;
-					break;
-				case PENDING_TYPE_CHILD_CLOSE:
-					*p++ = LWS_META_CMD_CLOSE_NOTIFY;
-					*p++ = LWS_META_TRANSPORT_OFFSET +
-							pend->ch;
-					for (n = 0; n < pend->len; n++)
-						*p++ = pend->protocol[n];
-					break;
-				}
-
-				pss->count_pending--;
-				pend1 = pend;
-				pend = pend->next;
-				free(pend1);
-				pss->first = pend;
-			}
-
-			if (p != start) {
-				if (lws_write(wsi, start, p - start,
-					      LWS_WRITE_BINARY) < 0)
-					return 1;
-				if (pend) /* still more */
-					lws_callback_on_writable(wsi);
-				break;
-			}
-
-			/* PRIORITY 2: pick a child for the writable callback */
-
-			cwsi = NULL;
-			for (n = 0; n < MAX_SUBCHANNELS; n++) {
-				m = ((pss->round_robin + n) % MAX_SUBCHANNELS) + 1;
-				if (pss->wsi[m] &&
-				    lws_get_child_pending_on_writable(pss->wsi[m])) {
-					pss->round_robin = m;
-					cwsi = pss->wsi[m];
-					break;
-				}
-			}
-		} else
-			/* one child is in middle of message, stay with it */
-			cwsi = pss->wsi[pss->active_subchannel_tx];
-
-		if (!cwsi)
-			break;
-
-		lws_clear_child_pending_on_writable(cwsi);
-		if (lws_handle_POLLOUT_event(cwsi, NULL))
-			return -1;
-		break;
-
-	case LWS_CALLBACK_RECEIVE:
-		bin = (unsigned char *)in;
-
-		/*
-		 * at the start of a message, we may have one or more
-		 * lws_meta command blocks.
-		 */
-		while (pss->state != MP_IDLE &&
-		       (unsigned int)(bin - (unsigned char *)in) < len) {
-
-			switch (pss->state) {
-			case MP_IDLE: /* in body of message */
-
-				if (!lws_is_first_fragment(wsi))
-					break;
-
-				pss->state = MP_CMD;
-
-				/* fallthru */
-
-			case MP_CMD: /* await cmd */
-
-				pss->pos = 0;
-
-				switch (*bin++) {
-				case LWS_META_CMD_OPEN_SUBCHANNEL:
-
-					pss->pend = new_pending(pss);
-					if (!pss->pend)
-						return -1;
-
-					pss->state = MP_OPEN_SUBCHANNEL_PROTOCOL;
-
-					break;
-				case LWS_META_CMD_CLOSE_NOTIFY:
-				case LWS_META_CMD_CLOSE_RQ:
-					pss->which_close = bin[-1];
-					pss->state = MP_CLOSE_CHID;
-					break;
-				case LWS_META_CMD_WRITE:
-					pss->state = MP_WRITE_CHID;
-					break;
-
-				// open result is also illegal to receive
-				default:
-					lwsl_notice("bad lws_meta cmd 0x%x\n",
-						    bin[-1]);
-
-					return -1;
-				}
-
-				break;
-
-			case MP_OPEN_SUBCHANNEL_PROTOCOL:
-				pss->pend->protocol[pss->pos++] = *bin++;
-				if (pss->pos == sizeof(pss->pend->protocol) - 1) {
-					lwsl_notice("protocol name too long\n");
-					return -1;
-				}
-
-				if (bin[-1] != '\0')
-					break;
-
-				pss->state = MP_OPEN_SUBCHANNEL_URL;
-				pss->pos = 0;
-				break;
-
-			case MP_OPEN_SUBCHANNEL_URL:
-				pss->suburl[pss->pos++] = *bin++;
-				if (pss->pos == sizeof(pss->suburl) - 1) {
-					lwsl_notice("suburl too long\n");
-					return -1;
-				}
-
-				if (bin[-1] != '\0')
-					break;
-
-				pss->state = MP_OPEN_SUBCHANNEL_COOKIE;
-				pss->pos = 0;
-				break;
-
-			case MP_OPEN_SUBCHANNEL_COOKIE:
-				pss->pend->cookie[pss->pos++] = *bin++;
-				if (pss->pos == sizeof(pss->pend->cookie) - 1) {
-					lwsl_notice("cookie too long\n");
-					return -1;
-				}
-
-				if (bin[-1] != '\0')
-					break;
-
-				lwsl_debug("%s: %s / %s / %s\n", __func__,
-					    pss->pend->protocol,
-					    pss->suburl,
-					    pss->pend->cookie);
-
-				pss->pend->ch = lws_find_free_channel(pss);
-				if (pss->pend->ch) {
-
-					fd.sockfd = 0; // not going to be used
-
-					cwsi = lws_adopt_descriptor_vhost(
-							lws_get_vhost(wsi),
-							LWS_ADOPT_WS_PARENTIO,
-							fd, pss->pend->protocol,
-							wsi);
-
-					if (!cwsi) {
-						lwsl_notice("open failed\n");
-						pss->pend->ch = 0;
-					} else {
-						pss->wsi[pss->pend->ch] = cwsi;
-						lws_set_channel_id(cwsi,
-								pss->pend->ch);
-						lwsl_debug("cwsi %p on parent %p open OK %s\n",
-							cwsi, wsi, pss->pend->protocol);
-					}
-
-				} else
-					lwsl_notice("no free subchannels\n");
-
-				pss->pend->type = PENDING_TYPE_OPEN_RESULT;
-				pss->pend->next = pss->first;
-				pss->first = pss->pend;
-
-				lws_callback_on_writable(wsi);
-
-				pss->state = MP_CMD;
-				pss->pos = 0;
-				break;
-
-			case MP_CLOSE_CHID:
-				pss->ch = (*bin++) - LWS_META_TRANSPORT_OFFSET;
-				pss->state = MP_CLOSE_LEN;
-				pss->pos = 0;
-				break;
-			case MP_CLOSE_LEN:
-				pss->close_len = (*bin++) -
-					LWS_META_TRANSPORT_OFFSET;
-				lwsl_debug("close len %d\n", pss->close_len);
-				pss->state = MP_CLOSE_CODEM;
-				pss->pos = 0;
-				break;
-			case MP_CLOSE_CODEM:
-				pss->close[pss->pos++] = *bin;
-				pss->close_status_16 = (*bin++) * 256;
-				pss->state = MP_CLOSE_CODEL;
-				break;
-			case MP_CLOSE_CODEL:
-				pss->close[pss->pos++] = *bin;
-				pss->close_status_16 |= *bin++;
-				pss->state = MP_CLOSE_PAYLOAD;
-				break;
-			case MP_CLOSE_PAYLOAD:
-				pss->close[pss->pos++] = *bin++;
-				if (pss->pos == sizeof(pss->close) - 1) {
-					lwsl_notice("close payload too long\n");
-					return -1;
-				}
-				if (--pss->close_len)
-					break;
-
-				pss->state = MP_CMD;
-
-				cwsi = lws_get_channel_wsi(pss, pss->ch);
-				if (!cwsi) {
-					lwsl_notice("close (%d) bad ch %d\n",
-						pss->which_close, pss->ch);
-					break;
-				}
-
-				if (pss->which_close == LWS_META_CMD_CLOSE_RQ) {
-					if (lws_get_protocol(cwsi)->callback(
-					    cwsi,
-					    LWS_CALLBACK_WS_PEER_INITIATED_CLOSE,
-					    lws_wsi_user(cwsi), &pss->close,
-					    pss->pos))
-						return -1;
-
-					/*
-					 * we need to echo back the close payload
-					 * when we send the close notification
-					 */
-					lws_close_reason(cwsi,
-							 pss->close_status_16,
-							 &pss->close[2],
-							 pss->pos - 2);
-				}
-
-				/* so force him closed */
-
-				lws_set_timeout(cwsi,
-					PENDING_TIMEOUT_KILLED_BY_PARENT,
-					LWS_TO_KILL_SYNC);
-				break;
-
-			case MP_WRITE_CHID:
-				pss->active_subchannel_rx = (*bin++) -
-					LWS_META_TRANSPORT_OFFSET;
-				pss->state = MP_IDLE;
-				break;
-			}
-		}
-
-		len -= bin - (unsigned char *)in;
-
-		if (!len)
-			break;
-
-		cwsi = lws_get_channel_wsi(pss, pss->active_subchannel_rx);
-		if (!cwsi) {
-			lwsl_notice("bad ch %d\n", pss->active_subchannel_rx);
-
-			return -1;
-		}
-
-		// lwsl_debug("%s: RX len %d\n", __func__, (int)len);
-
-		if (lws_get_protocol(cwsi)->callback(cwsi,
-					LWS_CALLBACK_RECEIVE,
-					lws_wsi_user(cwsi), bin, len))
-			lws_set_timeout(cwsi,
-				PENDING_TIMEOUT_KILLED_BY_PARENT,
-				LWS_TO_KILL_SYNC);
-
-		if (lws_is_final_fragment(wsi)) {
-			pss->active_subchannel_rx = 0;
-			pss->state = MP_CMD;
-		}
-		break;
-
-	/*
-	 * child wrote something via lws_write.... which passed it up to us to
-	 * deal with, because we are the parent.  Prepend two bytes for
-	 * lws-meta command and channel index, and send it out on parent
-	 */
-	case LWS_CALLBACK_CHILD_WRITE_VIA_PARENT:
-		pas = in;
-		bin = ((unsigned char *)pas->buf);
-
-		if ((pas->wp & 7) == 4 /*LWS_WRITE_CLOSE */) {
-			*p++ = LWS_META_CMD_CLOSE_NOTIFY;
-			*p++ = LWS_META_TRANSPORT_OFFSET +
-					lws_get_channel_id(pas->wsi);
-			*p++ = (unsigned char)pas->len +
-					LWS_META_TRANSPORT_OFFSET - 2;
-			*p++ = *bin++;
-			*p++ = *bin++;
-			for (n = 0; n < (int)pas->len - 2; n++)
-				*p++ = bin[n];
-
-			if (lws_write(wsi, start, p - start,
-				      LWS_WRITE_BINARY) < 0)
-				return 1;
-
-			pss->told_closing[lws_get_channel_id(pas->wsi)] = 1;
-			break;
-		}
-
-		if ((pas->wp & 7) == LWS_WRITE_TEXT ||
-		    (pas->wp & 7) == LWS_WRITE_BINARY) {
-
-			if (pas->wp & LWS_WRITE_NO_FIN)
-				pss->active_subchannel_tx =
-						lws_get_channel_id(pas->wsi);
-
-			/* start of message, prepend the subchannel id */
-
-			bin -= 2;
-			bin[0] = LWS_META_CMD_WRITE;
-			bin[1] = lws_get_channel_id(pas->wsi) +
-					LWS_META_TRANSPORT_OFFSET;
-			if (lws_write(wsi, bin, pas->len + 2, pas->wp) < 0)
-				return 1;
-		} else
-			if (lws_write(wsi, bin, pas->len, pas->wp) < 0)
-				return 1;
-
-		/* track EOM */
-
-		if (!(pas->wp & LWS_WRITE_NO_FIN))
-			pss->active_subchannel_tx = 0;
-		break;
-
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-#define LWS_PLUGIN_PROTOCOL_LWS_META { \
-		"lws-meta", \
-		callback_lws_meta, \
-		sizeof(struct per_session_data__lws_meta), \
-		1024, /* rx buf size must be >= permessage-deflate rx size */ \
-		0, NULL, 0 \
-	}
-
-#if !defined (LWS_PLUGIN_STATIC)
-
-static const struct lws_protocols protocols[] = {
-	LWS_PLUGIN_PROTOCOL_LWS_META
-};
-
-LWS_EXTERN LWS_VISIBLE int
-init_protocol_lws_meta(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_EXTERN LWS_VISIBLE int
-destroy_protocol_lws_meta(struct lws_context *context)
-{
-	return 0;
-}
-#endif
diff --git a/test-apps/test-fraggle.c b/test-apps/test-fraggle.c
deleted file mode 100644
index c234c00cb673499d0f86600a733615edb2de8592..0000000000000000000000000000000000000000
--- a/test-apps/test-fraggle.c
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * libwebsockets-test-fraggle - random fragmentation test
- *
- * Copyright (C) 2011-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 <stdio.h>
-#include <stdlib.h>
-#include <getopt.h>
-#include <string.h>
-#include "../lib/libwebsockets.h"
-
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-
-static int client;
-static int terminate;
-
-enum demo_protocols {
-	PROTOCOL_FRAGGLE,
-
-	/* always last */
-	DEMO_PROTOCOL_COUNT
-};
-
-/* fraggle protocol */
-
-enum fraggle_states {
-	FRAGSTATE_START_MESSAGE,
-	FRAGSTATE_RANDOM_PAYLOAD,
-	FRAGSTATE_POST_PAYLOAD_SUM,
-};
-
-struct per_session_data__fraggle {
-	int packets_left;
-	int total_message;
-	unsigned long sum;
-	enum fraggle_states state;
-};
-
-static int
-callback_fraggle(struct lws *wsi, enum lws_callback_reasons reason,
-		 void *user, void *in, size_t len)
-{
-	unsigned char buf[LWS_PRE + 8000], *bp = &buf[LWS_PRE];
-	struct per_session_data__fraggle *psf = user;
-	unsigned char *p = (unsigned char *)in;
-	int n, chunk, flags, ran;
-	unsigned long sum;
-
-	switch (reason) {
-
-	case LWS_CALLBACK_ESTABLISHED:
-
-		fprintf(stderr, "server sees client connect\n");
-		psf->state = FRAGSTATE_START_MESSAGE;
-		/* start the ball rolling */
-		lws_callback_on_writable(wsi);
-		break;
-
-	case LWS_CALLBACK_CLIENT_ESTABLISHED:
-
-		fprintf(stderr, "client connects to server\n");
-		psf->state = FRAGSTATE_START_MESSAGE;
-		break;
-
-	case LWS_CALLBACK_CLIENT_RECEIVE:
-
-		switch (psf->state) {
-
-		case FRAGSTATE_START_MESSAGE:
-
-			psf->state = FRAGSTATE_RANDOM_PAYLOAD;
-			psf->sum = 0;
-			psf->total_message = 0;
-			psf->packets_left = 0;
-
-			/* fallthru */
-
-		case FRAGSTATE_RANDOM_PAYLOAD:
-
-			for (n = 0; (unsigned int)n < len; n++)
-				psf->sum += p[n];
-
-			psf->total_message += (int)len;
-			psf->packets_left++;
-
-			if (lws_is_final_fragment(wsi))
-				psf->state = FRAGSTATE_POST_PAYLOAD_SUM;
-			break;
-
-		case FRAGSTATE_POST_PAYLOAD_SUM:
-
-			sum = ((unsigned int)p[0]) << 24;
-			sum |= p[1] << 16;
-			sum |= p[2] << 8;
-			sum |= p[3];
-			if (sum == psf->sum)
-				fprintf(stderr, "EOM received %d correctly "
-						"from %d fragments\n",
-					psf->total_message, psf->packets_left);
-			else
-				fprintf(stderr, "**** ERROR at EOM: "
-						"length %d, rx sum = 0x%lX, "
-						"server says it sent 0x%lX\n",
-					     psf->total_message, psf->sum, sum);
-
-			psf->state = FRAGSTATE_START_MESSAGE;
-			break;
-		}
-		break;
-
-	case LWS_CALLBACK_SERVER_WRITEABLE:
-
-		switch (psf->state) {
-
-		case FRAGSTATE_START_MESSAGE:
-			lws_get_random(lws_get_context(wsi), &ran, sizeof(ran));
-			psf->packets_left = (ran & 1023) + 1;
-			fprintf(stderr, "Spamming %d random fragments\n",
-							     psf->packets_left);
-			psf->sum = 0;
-			psf->total_message = 0;
-			psf->state = FRAGSTATE_RANDOM_PAYLOAD;
-
-			/* fallthru */
-
-		case FRAGSTATE_RANDOM_PAYLOAD:
-
-			/*
-			 * note how one chunk can be 8000, but we use the
-			 * default rx buffer size of 4096, so we exercise the
-			 * code for rx spill because the rx buffer is full
-			 */
-
-			lws_get_random(lws_get_context(wsi), &ran, sizeof(ran));
-			chunk = (ran & 511) + 1;
-			psf->total_message += chunk;
-
-			lws_get_random(lws_get_context(wsi), bp, chunk);
-			for (n = 0; n < chunk; n++)
-				psf->sum += bp[n];
-
-			psf->packets_left--;
-			flags = lws_write_ws_flags(LWS_WRITE_BINARY, !psf->sum,
-						   !psf->packets_left);
-			if (!psf->packets_left)
-				psf->state = FRAGSTATE_POST_PAYLOAD_SUM;
-
-			n = lws_write(wsi, bp, chunk, flags);
-			if (n < 0)
-				return -1;
-
-			lws_callback_on_writable(wsi);
-			break;
-
-		case FRAGSTATE_POST_PAYLOAD_SUM:
-
-			fprintf(stderr, "Spamming session over, "
-					"len = %d. sum = 0x%lX\n",
-						  psf->total_message, psf->sum);
-
-			bp[0] = psf->sum >> 24;
-			bp[1] = (unsigned char)(psf->sum >> 16);
-			bp[2] = (unsigned char)(psf->sum >> 8);
-			bp[3] = (unsigned char)psf->sum;
-
-			n = lws_write(wsi, (unsigned char *)bp,
-							   4, LWS_WRITE_BINARY);
-			if (n < 0)
-				return -1;
-			if (n < 4) {
-				lwsl_err("Partial write\n");
-				return -1;
-			}
-
-			psf->state = FRAGSTATE_START_MESSAGE;
-
-			lws_callback_on_writable(wsi);
-			break;
-		}
-		break;
-
-	case LWS_CALLBACK_CLOSED:
-
-		terminate = 1;
-		break;
-
-	/* because we are protocols[0] ... */
-
-	case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
-		if (strcmp(in, "deflate-stream") == 0) {
-			fprintf(stderr, "denied deflate-stream extension\n");
-			return 1;
-		}
-		break;
-
-	default:
-		break;
-	}
-
-	return 0;
-}
-
-
-/* list of supported protocols and callbacks */
-
-static struct lws_protocols protocols[] = {
-	{
-		"fraggle-protocol",
-		callback_fraggle,
-		sizeof(struct per_session_data__fraggle),
-	},
-	{
-		NULL, NULL, 0		/* End of list */
-	}
-};
-
-static const struct lws_extension exts[] = {
-	{
-		"permessage-deflate",
-		lws_extension_callback_pm_deflate,
-		"permessage-deflate; client_no_context_takeover; client_max_window_bits"
-	},
-	{
-		"deflate-frame",
-		lws_extension_callback_pm_deflate,
-		"deflate_frame"
-	},
-	{ NULL, NULL, NULL /* terminator */ }
-};
-
-static struct option options[] = {
-	{ "help",	no_argument,		NULL, 'h' },
-	{ "debug",	required_argument,	NULL, 'd' },
-	{ "port",	required_argument,	NULL, 'p' },
-	{ "ssl",	no_argument,		NULL, 's' },
-	{ "interface",  required_argument,	NULL, 'i' },
-	{ "client",	no_argument,		NULL, 'c' },
-	{ NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-	int n = 0;
-	int port = 7681;
-	int use_ssl = 0;
-	struct lws_context *context;
-	int opts = 0;
-	char interface_name[128] = "", ads_port[300];
-	const char *iface = NULL;
-	struct lws *wsi;
-	const char *address = NULL;
-	int server_port = port;
-	struct lws_context_creation_info info;
-
-	memset(&info, 0, sizeof info);
-	lwsl_notice("libwebsockets test server fraggle - license LGPL2.1+SLE\n");
-	lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
-
-	while (n >= 0) {
-		n = getopt_long(argc, argv, "ci:hsp:d:", options, NULL);
-		if (n < 0)
-			continue;
-		switch (n) {
-		case 'd':
-			lws_set_log_level(atoi(optarg), NULL);
-			break;
-		case 's':
-			use_ssl = 1;
-			break;
-		case 'p':
-			port = atoi(optarg);
-			server_port = port;
-			break;
-		case 'i':
-			lws_strncpy(interface_name, optarg, sizeof interface_name);
-			iface = interface_name;
-			break;
-		case 'c':
-			client = 1;
-			fprintf(stderr, " Client mode\n");
-			break;
-		case 'h':
-			fprintf(stderr, "Usage: libwebsockets-test-fraggle "
-					"[--port=<p>] [--ssl] "
-					"[-d <log bitfield>] "
-					"[--client]\n");
-			exit(1);
-		}
-	}
-
-	if (client) {
-		server_port = CONTEXT_PORT_NO_LISTEN;
-		if (optind >= argc) {
-			fprintf(stderr, "Must give address of server\n");
-			return 1;
-		}
-	}
-
-	info.port = server_port;
-	info.iface = iface;
-	info.protocols = protocols;
-	info.extensions = exts;
-
-	if (use_ssl) {
-		info.ssl_cert_filepath = LOCAL_RESOURCE_PATH
-				"/libwebsockets-test-server.pem";
-		info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH
-				"/libwebsockets-test-server.key.pem";
-	}
-	info.gid = -1;
-	info.uid = -1;
-	info.options = opts;
-	info.extensions = exts;
-
-	if (use_ssl)
-		info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
-
-	context = lws_create_context(&info);
-	if (context == NULL) {
-		fprintf(stderr, "libwebsocket init failed\n");
-		return -1;
-	}
-
-	if (client) {
-		struct lws_client_connect_info i;
-
-		address = argv[optind];
-		lws_snprintf(ads_port, sizeof(ads_port), "%s:%u",
-			 address, port & 65535);
-		memset(&i, 0, sizeof(i));
-		i.context = context;
-		i.address = address;
-		i.port = port;
-		i.ssl_connection = use_ssl;
-		i.path = "/";
-		i.host = ads_port;
-		i.origin = ads_port;
-		i.protocol = protocols[PROTOCOL_FRAGGLE].name;
-
-		lwsl_notice("Connecting to %s:%u\n", address, port);
-		wsi = lws_client_connect_via_info(&i);
-		if (wsi == NULL) {
-			fprintf(stderr, "Client connect to server failed\n");
-			goto bail;
-		}
-	}
-
-	n = 0;
-	while (!n && !terminate)
-		n = lws_service(context, 50);
-
-	fprintf(stderr, "Terminating...\n");
-
-bail:
-	lws_context_destroy(context);
-
-	return 0;
-}
diff --git a/test-apps/test-server-dumb-increment.c b/test-apps/test-server-dumb-increment.c
index 36ade9d6d502386c4f2a3ccd4f2443fabaec1a37..0e30f02729eb77b9c04e22345d07b23629123f52 100644
--- a/test-apps/test-server-dumb-increment.c
+++ b/test-apps/test-server-dumb-increment.c
@@ -19,6 +19,8 @@
  */
 #include "test-server.h"
 
+#if defined(LWS_ROLE_WS)
+
 /* dumb_increment protocol */
 
 int
@@ -53,11 +55,12 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 		break;
 
 	case LWS_CALLBACK_RECEIVE:
-		if (len < 6)
-			break;
-		if (strcmp((const char *)in, "reset\n") == 0)
+//		if (len < 6)
+//			break;
+		lwsl_hexdump_notice(in, len);
+		if (strncmp((const char *)in, "reset\n", 6) == 0)
 			pss->number = 0;
-		if (strcmp((const char *)in, "closeme\n") == 0) {
+		if (strncmp((const char *)in, "closeme\n", 8) == 0) {
 			lwsl_notice("dumb_inc: closing as requested\n");
 			lws_close_reason(wsi, LWS_CLOSE_STATUS_GOINGAWAY,
 					 (unsigned char *)"seeya", 5);
@@ -100,3 +103,4 @@ callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
 
 	return 0;
 }
+#endif
\ No newline at end of file
diff --git a/test-apps/test-server-libuv.c b/test-apps/test-server-libuv.c
index c27e779fb1a5d5110af9fd50b7e76ea824ed82e8..f0602e0b86782c0b53b1b31ea1a244ab85c2a50d 100644
--- a/test-apps/test-server-libuv.c
+++ b/test-apps/test-server-libuv.c
@@ -47,10 +47,11 @@ void test_server_unlock(int care)
 }
 
 #define LWS_PLUGIN_STATIC
+#if defined(LWS_ROLE_WS)
 #include "../plugins/protocol_dumb_increment.c"
 #include "../plugins/protocol_lws_mirror.c"
 #include "../plugins/protocol_lws_status.c"
-#include "../plugins/protocol_lws_meta.c"
+#endif
 
 /*
  * This demo server shows how to use libwebsockets for one or more
@@ -74,7 +75,6 @@ enum demo_protocols {
 	PROTOCOL_DUMB_INCREMENT,
 	PROTOCOL_LWS_MIRROR,
 	PROTOCOL_LWS_STATUS,
-	PROTOCOL_LWS_META,
 
 	/* always last */
 	DEMO_PROTOCOL_COUNT
@@ -91,13 +91,15 @@ static struct lws_protocols protocols[] = {
 		sizeof (struct per_session_data__http),	/* per_session_data_size */
 		0,			/* max frame size / rx buffer */
 	},
+#if defined(LWS_ROLE_WS)
 	LWS_PLUGIN_PROTOCOL_DUMB_INCREMENT,
 	LWS_PLUGIN_PROTOCOL_MIRROR,
 	LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-	LWS_PLUGIN_PROTOCOL_LWS_META,
+#endif
 	{ NULL, NULL, 0, 0 } /* terminator */
 };
 
+
 static const struct lws_extension exts[] = {
 	{
 		"permessage-deflate",
diff --git a/test-apps/test-server-pthreads.c b/test-apps/test-server-pthreads.c
deleted file mode 100644
index b7dbd42c2968ff9c6c36ccf6ee4d5ddd53bfe131..0000000000000000000000000000000000000000
--- a/test-apps/test-server-pthreads.c
+++ /dev/null
@@ -1,401 +0,0 @@
-/*
- * 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 "test-server.h"
-#include <pthread.h>
-
-int close_testing;
-int max_poll_elements;
-int debug_level = 7;
-
-#ifdef EXTERNAL_POLL
-struct lws_pollfd *pollfds;
-int *fd_lookup;
-int count_pollfds;
-#endif
-volatile int force_exit = 0;
-struct lws_context *context;
-
-#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
-char crl_path[1024] = "";
-#endif
-
-#define LWS_PLUGIN_STATIC
-#include "../plugins/protocol_lws_mirror.c"
-#include "../plugins/protocol_lws_status.c"
-
-/*
- * This mutex lock protects code that changes or relies on wsi list outside of
- * the service thread.	The service thread will acquire it when changing the
- * wsi list and other threads should acquire it while dereferencing wsis or
- * calling apis like lws_callback_on_writable_all_protocol() which
- * use the wsi list and wsis from a different thread context.
- */
-pthread_mutex_t lock_established_conns;
-
-/* http server gets files from this path */
-#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
-char *resource_path = LOCAL_RESOURCE_PATH;
-
-/*
- * multithreaded version - protect wsi lifecycle changes in the library
- * these are called from protocol 0 callbacks
- */
-
-void test_server_lock(int care)
-{
-	if (care)
-		pthread_mutex_lock(&lock_established_conns);
-}
-void test_server_unlock(int care)
-{
-	if (care)
-		pthread_mutex_unlock(&lock_established_conns);
-}
-
-/*
- * This demo server shows how to use libwebsockets for one or more
- * websocket protocols in the same server
- *
- * It defines the following websocket protocols:
- *
- *  dumb-increment-protocol:  once the socket is opened, an incrementing
- *				ascii string is sent down it every 50ms.
- *				If you send "reset\n" on the websocket, then
- *				the incrementing number is reset to 0.
- *
- *  lws-mirror-protocol: copies any received packet to every connection also
- *				using this protocol, including the sender
- */
-
-enum demo_protocols {
-	/* always first */
-	PROTOCOL_HTTP = 0,
-
-	PROTOCOL_DUMB_INCREMENT,
-	PROTOCOL_LWS_MIRROR,
-	PROTOCOL_LWS_STATUS,
-
-	/* 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 */
-	},
-	{
-		"dumb-increment-protocol",
-		callback_dumb_increment,
-		sizeof(struct per_session_data__dumb_increment),
-		10, /* rx buf size must be >= permessage-deflate rx size
-		     * dumb-increment only sends very small packets, so we set
-		     * this accordingly.  If your protocol will send bigger
-		     * things, adjust this to match */
-	},
-	LWS_PLUGIN_PROTOCOL_MIRROR,
-	LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-	{ NULL, NULL, 0, 0 } /* terminator */
-};
-
-void *thread_dumb_increment(void *threadid)
-{
-	while (!force_exit) {
-		/*
-		 * this lock means wsi in the active list cannot
-		 * disappear underneath us, because the code to add and remove
-		 * them is protected by the same lock
-		 */
-		pthread_mutex_lock(&lock_established_conns);
-		lws_callback_on_writable_all_protocol(context,
-				&protocols[PROTOCOL_DUMB_INCREMENT]);
-		pthread_mutex_unlock(&lock_established_conns);
-		usleep(100000);
-	}
-
-	pthread_exit(NULL);
-}
-
-void *thread_service(void *threadid)
-{
-	while (lws_service_tsi(context, 50, (int)(lws_intptr_t)threadid) >= 0 && !force_exit)
-		;
-
-	pthread_exit(NULL);
-}
-
-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; client_no_context_takeover; client_max_window_bits"
-	},
-	{
-		"deflate-frame",
-		lws_extension_callback_pm_deflate,
-		"deflate_frame"
-	},
-	{ NULL, NULL, NULL /* terminator */ }
-};
-
-static struct option options[] = {
-	{ "help",	no_argument,		NULL, 'h' },
-	{ "debug",	required_argument,	NULL, 'd' },
-	{ "port",	required_argument,	NULL, 'p' },
-	{ "ssl",	no_argument,		NULL, 's' },
-	{ "allow-non-ssl",	no_argument,	NULL, 'a' },
-	{ "interface",	required_argument,	NULL, 'i' },
-	{ "closetest",	no_argument,		NULL, 'c' },
-	{ "libev",  no_argument,		NULL, 'e' },
-	{ "threads",  required_argument,	NULL, 'j' },
-#ifndef LWS_NO_DAEMONIZE
-	{ "daemonize",	no_argument,		NULL, 'D' },
-#endif
-	{ "resource_path", required_argument,	NULL, 'r' },
-	{ NULL, 0, 0, 0 }
-};
-
-int main(int argc, char **argv)
-{
-	struct lws_context_creation_info info;
-	char interface_name[128] = "";
-	const char *iface = NULL;
-	pthread_t pthread_dumb, pthread_service[32];
-	char cert_path[1024];
-	char key_path[1024];
-	int threads = 1;
-	int use_ssl = 0;
-	void *retval;
-	int opts = 0;
-	int n = 0;
-#ifndef _WIN32
-/* LOG_PERROR is not POSIX standard, and may not be portable */
-#ifdef __sun
-	int syslog_options = LOG_PID;
-#else
-	int syslog_options = LOG_PID | LOG_PERROR;
-#endif
-#endif
-#ifndef LWS_NO_DAEMONIZE
-	int daemonize = 0;
-#endif
-
-	/*
-	 * take care to zero down the info struct, he contains random garbaage
-	 * from the stack otherwise
-	 */
-	memset(&info, 0, sizeof info);
-	info.port = 7681;
-
-	pthread_mutex_init(&lock_established_conns, NULL);
-
-	while (n >= 0) {
-		n = getopt_long(argc, argv, "eci:hsap:d:Dr:j:", options, NULL);
-		if (n < 0)
-			continue;
-		switch (n) {
-		case 'j':
-			threads = atoi(optarg);
-			if (threads > (int)ARRAY_SIZE(pthread_service)) {
-				lwsl_err("Max threads %lu\n",
-					 (unsigned long)ARRAY_SIZE(pthread_service));
-				return 1;
-			}
-			break;
-		case 'e':
-			opts |= LWS_SERVER_OPTION_LIBEV;
-			break;
-#ifndef LWS_NO_DAEMONIZE
-		case 'D':
-			daemonize = 1;
-			#if !defined(_WIN32) && !defined(__sun)
-			syslog_options &= ~LOG_PERROR;
-			#endif
-			break;
-#endif
-		case 'd':
-			debug_level = atoi(optarg);
-			break;
-		case 's':
-			use_ssl = 1;
-			break;
-		case 'a':
-			opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
-			break;
-		case 'p':
-			info.port = atoi(optarg);
-			break;
-		case 'i':
-			lws_strncpy(interface_name, optarg, sizeof interface_name);
-			iface = interface_name;
-			break;
-		case 'c':
-			close_testing = 1;
-			fprintf(stderr, " Close testing mode -- closes on "
-					   "client after 50 dumb increments"
-					   "and suppresses lws_mirror spam\n");
-			break;
-		case 'r':
-			resource_path = optarg;
-			printf("Setting resource path to \"%s\"\n", resource_path);
-			break;
-		case 'h':
-			fprintf(stderr, "Usage: test-server "
-					"[--port=<p>] [--ssl] "
-					"[-d <log bitfield>] "
-					"[--resource_path <path>]\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 1;
-	}
-#endif
-
-	signal(SIGINT, sighandler);
-
-#ifndef _WIN32
-	/* we will only try to log things according to our debug_level */
-	setlogmask(LOG_UPTO (LOG_DEBUG));
-	openlog("lwsts", syslog_options, LOG_DAEMON);
-#endif
-
-	/* tell the library what debug level to emit and to send it to syslog */
-	lws_set_log_level(debug_level, NULL);
-	lwsl_notice("libwebsockets test server pthreads - license LGPL2.1+SLE\n");
-	lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
-
-	printf("Using resource path \"%s\"\n", resource_path);
-#ifdef EXTERNAL_POLL
-	max_poll_elements = getdtablesize();
-	pollfds = malloc(max_poll_elements * sizeof (struct lws_pollfd));
-	fd_lookup = malloc(max_poll_elements * sizeof (int));
-	if (pollfds == NULL || fd_lookup == NULL) {
-		lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
-		return -1;
-	}
-#endif
-
-	info.iface = iface;
-	info.protocols = protocols;
-	info.extensions = exts;
-
-	info.ssl_cert_filepath = NULL;
-	info.ssl_private_key_filepath = NULL;
-
-	if (use_ssl) {
-		if (strlen(resource_path) > sizeof(cert_path) - 32) {
-			lwsl_err("resource path too long\n");
-			return -1;
-		}
-		sprintf(cert_path, "%s/libwebsockets-test-server.pem",
-			resource_path);
-		if (strlen(resource_path) > sizeof(key_path) - 32) {
-			lwsl_err("resource path too long\n");
-			return -1;
-		}
-		sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
-			resource_path);
-
-		info.ssl_cert_filepath = cert_path;
-		info.ssl_private_key_filepath = key_path;
-	}
-	info.gid = -1;
-	info.uid = -1;
-	info.options = opts;
-	info.count_threads = threads;
-	info.extensions = exts;
-	info.max_http_header_pool = 4;
-	info.pt_serv_buf_size = 128 * 1024;
-
-	/* when doing slow benchmarks with thousands of concurrent
-	 * connections, we need wait longer
-	 */
-	info.timeout_secs = 30;
-	info.keepalive_timeout = 30;
-
-	context = lws_create_context(&info);
-	if (context == NULL) {
-		lwsl_err("libwebsocket init failed\n");
-		return -1;
-	}
-
-	/* start the dumb increment thread */
-
-	n = pthread_create(&pthread_dumb, NULL, thread_dumb_increment, 0);
-	if (n) {
-		lwsl_err("Unable to create dumb thread\n");
-		goto done;
-	}
-
-	/*
-	 * notice the actual number of threads may be capped by the library,
-	 * so use lws_get_count_threads() to get the actual amount of threads
-	 * initialized.
-	 */
-
-	lwsl_notice("Service thread count: %d\n", lws_get_count_threads(context));
-
-	for (n = 0; n < lws_get_count_threads(context); n++)
-		if (pthread_create(&pthread_service[n], NULL, thread_service,
-				   (void *)(lws_intptr_t)n))
-			lwsl_err("Failed to start service thread\n");
-
-	/* wait for all the service threads to exit */
-
-	while ((--n) >= 0)
-		pthread_join(pthread_service[n], &retval);
-
-	/* wait for pthread_dumb to exit */
-	pthread_join(pthread_dumb, &retval);
-
-done:
-	lws_context_destroy(context);
-	pthread_mutex_destroy(&lock_established_conns);
-
-	lwsl_notice("libwebsockets-test-server exited cleanly\n");
-
-#ifndef _WIN32
-	closelog();
-#endif
-
-	return 0;
-}
diff --git a/test-apps/test-server.c b/test-apps/test-server.c
index 9e86590ceb3f7fdcebec86c91be3e3d0156c96f1..edbd5b9965f059d11216d50b4f0069e3ab2c3503 100644
--- a/test-apps/test-server.c
+++ b/test-apps/test-server.c
@@ -66,9 +66,10 @@ char crl_path[1024] = "";
  */
 
 #define LWS_PLUGIN_STATIC
+#if defined(LWS_ROLE_WS)
 #include "../plugins/protocol_lws_mirror.c"
 #include "../plugins/protocol_lws_status.c"
-#include "../plugins/protocol_lws_meta.c"
+#endif
 
 /* singlethreaded version --> no locks */
 
@@ -122,6 +123,7 @@ static struct lws_protocols protocols[] = {
 		sizeof (struct per_session_data__http),	/* per_session_data_size */
 		0,			/* max frame size / rx buffer */
 	},
+#if defined(LWS_ROLE_WS)
 	{
 		"dumb-increment-protocol",
 		callback_dumb_increment,
@@ -133,8 +135,7 @@ static struct lws_protocols protocols[] = {
 	},
 	LWS_PLUGIN_PROTOCOL_MIRROR,
 	LWS_PLUGIN_PROTOCOL_LWS_STATUS,
-
-	LWS_PLUGIN_PROTOCOL_LWS_META,
+#endif
 	{ NULL, NULL, 0, 0 } /* terminator */
 };