diff --git a/CMakeLists.txt b/CMakeLists.txt
index bd6bb3672e275938272ea0ac0ef36448d14bd589..490abd6ae6600af0a6a47454419486380335a096 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -88,6 +88,8 @@ option(LWS_WITH_HTTP2 "Compile with support for http2" OFF)
 option(LWS_MBED3 "Platform is MBED3" OFF)
 option(LWS_SSL_SERVER_WITH_ECDH_CERT "Include SSL server use ECDH certificate" OFF)
 option(LWS_WITH_CGI "Include CGI (spawn process with network-connected stdin/out/err) APIs" OFF)
+option(LWS_WITH_HTTP_PROXY "Support for rewriting HTTP proxying" OFF)
+
 
 if (DEFINED YOTTA_WEBSOCKETS_VERSION_STRING)
 
@@ -108,6 +110,9 @@ if (WIN32)
 set(LWS_MAX_SMP 1)
 endif()
 
+if (LWS_WITH_HTTP_PROXY AND (LWS_WITHOUT_CLIENT OR LWS_WITHOUT_SERVER))
+	message(FATAL_ERROR "You have to enable both client and server for http proxy")
+endif()
 
 # Allow the user to override installation directories.
 set(LWS_INSTALL_LIB_DIR       lib CACHE PATH "Installation directory for libraries")
@@ -211,6 +216,7 @@ if (LWS_WITH_LIBUV)
 	endif()
 endif()
 
+
 # FIXME: This must be runtime-only option.
 # The base dir where the test-apps look for the SSL certs.
 set(LWS_OPENSSL_CLIENT_CERTS ../share CACHE PATH "Server SSL certificate directory")
@@ -489,6 +495,11 @@ if (NOT LWS_WITHOUT_EXTENSIONS)
 		lib/extension-permessage-deflate.c)
 endif()
 
+if (LWS_WITH_HTTP_PROXY)
+	list(APPEND SOURCES
+		lib/rewrite.c)
+endif()
+
 if (LWS_WITH_LIBEV)
 	list(APPEND SOURCES
 		lib/libev.c)
@@ -734,6 +745,11 @@ if (LWS_WITH_LIBUV)
 	include_directories("${LIBUV_INCLUDE_DIRS}")
 	list(APPEND LIB_LIST ${LIBUV_LIBRARIES})
 endif()
+if (LWS_WITH_HTTP_PROXY)
+	find_library(LIBHUBBUB_LIBRARIES NAMES libhubbub)
+	list(APPEND LIB_LIST ${LIBHUBBUB_LIBRARIES} )
+endif()
+
 
 #
 # Platform specific libs.
@@ -1227,6 +1243,8 @@ message(" LWS_SSL_SERVER_WITH_ECDH_CERT = ${LWS_SSL_SERVER_WITH_ECDH_CERT}")
 message(" LWS_MAX_SMP = ${LWS_MAX_SMP}")
 message(" LWS_WITH_CGI = ${LWS_WITH_CGI}")
 message(" LWS_HAVE_OPENSSL_ECDH_H = ${LWS_HAVE_OPENSSL_ECDH_H}")
+message(" LWS_WITH_HTTP_PROXY = ${LWS_WITH_HTTP_PROXY}")
+message(" LIBHUBBUB_LIBRARIES = ${LIBHUBBUB_LIBRARIES}")
 message("---------------------------------------------------------------------")
 
 # These will be available to parent projects including libwebsockets using add_subdirectory()
diff --git a/changelog b/changelog
index ef5a406f9f178de525d2bdfcca300f478e6a4e6d..bc580d68bd4211d703662a181ce69a479fb44905 100644
--- a/changelog
+++ b/changelog
@@ -81,9 +81,14 @@ protocol string to its argument in URL format.  If so, it stays in http[s]
 client mode and doesn't upgrade to ws[s], allowing you to do generic http client
 operations.  Receiving transfer-encoding: chunked is supported.
 
-9) The test server has a new URI path http://localhost:7681/proxytest
-If you visit here, a client connection to http://example.com:80 is spawned,
-and the results piped on to your original connection.
+9) If you enable -DLWS_WITH_HTTP_PROXY=1 at cmake, the test server has a
+new URI path http://localhost:7681/proxytest If you visit here, a client
+connection to http://example.com:80 is spawned, and the results piped on
+to your original connection.
+
+10) Also with LWS_WITH_HTTP_PROXY enabled at cmake, lws wants to link to an
+additional library, "libhubbub".  This allows lws to do html rewriting on the
+fly, adjusting proxied urls in a lightweight and fast way.
 
 
 User API additions
diff --git a/lib/client-handshake.c b/lib/client-handshake.c
index 5504abe83db8dc17b88dd3be5ff572d29b76e46a..b3419a1c0caa8f05723778c4c8c189259933d460 100644
--- a/lib/client-handshake.c
+++ b/lib/client-handshake.c
@@ -338,6 +338,115 @@ lws_client_reset(struct lws *wsi, int ssl, const char *address, int port, const
 	return lws_client_connect_2(wsi);
 }
 
+
+static hubbub_error
+html_parser_cb(const hubbub_token *token, void *pw)
+{
+	struct lws_rewrite *r = (struct lws_rewrite *)pw;
+	char buf[1024], *start = buf + LWS_PRE, *p = start,
+	     *end = &buf[sizeof(buf) - 1];
+	size_t i;
+
+	switch (token->type) {
+	case HUBBUB_TOKEN_DOCTYPE:
+
+		p += snprintf(p, end - p, "<!DOCTYPE %.*s %s ",
+				(int) token->data.doctype.name.len,
+				token->data.doctype.name.ptr,
+				token->data.doctype.force_quirks ?
+						"(force-quirks) " : "");
+
+		if (token->data.doctype.public_missing)
+			printf("\tpublic: missing\n");
+		else
+			p += snprintf(p, end - p, "PUBLIC \"%.*s\"\n",
+				(int) token->data.doctype.public_id.len,
+				token->data.doctype.public_id.ptr);
+
+		if (token->data.doctype.system_missing)
+			printf("\tsystem: missing\n");
+		else
+			p += snprintf(p, end - p, " \"%.*s\">\n",
+				(int) token->data.doctype.system_id.len,
+				token->data.doctype.system_id.ptr);
+
+		break;
+	case HUBBUB_TOKEN_START_TAG:
+		p += snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len,
+				token->data.tag.name.ptr);
+
+/*				(token->data.tag.self_closing) ?
+						"(self-closing) " : "",
+				(token->data.tag.n_attributes > 0) ?
+						"attributes:" : "");
+*/
+		for (i = 0; i < token->data.tag.n_attributes; i++) {
+			if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) ||
+			    !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) ||
+			    !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) {
+				const char *pp = (const char *)token->data.tag.attributes[i].value.ptr;
+				int plen = (int) token->data.tag.attributes[i].value.len;
+
+				if (!hstrcmp(&token->data.tag.attributes[i].value,
+					     r->from, r->from_len)) {
+					pp += r->from_len;
+					plen -= r->from_len;
+				}
+				p += snprintf(p, end - p, " %.*s=\"%s/%.*s\"",
+				       (int) token->data.tag.attributes[i].name.len,
+				       token->data.tag.attributes[i].name.ptr,
+				       r->to, plen, pp);
+
+			} else
+
+				p += snprintf(p, end - p, " %.*s=\"%.*s\"",
+					(int) token->data.tag.attributes[i].name.len,
+					token->data.tag.attributes[i].name.ptr,
+					(int) token->data.tag.attributes[i].value.len,
+					token->data.tag.attributes[i].value.ptr);
+		}
+		p += snprintf(p, end - p, ">\n");
+		break;
+	case HUBBUB_TOKEN_END_TAG:
+		p += snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len,
+				token->data.tag.name.ptr);
+/*
+				(token->data.tag.self_closing) ?
+						"(self-closing) " : "",
+				(token->data.tag.n_attributes > 0) ?
+						"attributes:" : "");
+*/
+		for (i = 0; i < token->data.tag.n_attributes; i++) {
+			p += snprintf(p, end - p, " %.*s='%.*s'\n",
+				(int) token->data.tag.attributes[i].name.len,
+				token->data.tag.attributes[i].name.ptr,
+				(int) token->data.tag.attributes[i].value.len,
+				token->data.tag.attributes[i].value.ptr);
+		}
+		p += snprintf(p, end - p, ">\n");
+		break;
+	case HUBBUB_TOKEN_COMMENT:
+		p += snprintf(p, end - p, "<!-- %.*s -->\n",
+				(int) token->data.comment.len,
+				token->data.comment.ptr);
+		break;
+	case HUBBUB_TOKEN_CHARACTER:
+		p += snprintf(p, end - p, "%.*s", (int) token->data.character.len,
+				token->data.character.ptr);
+		break;
+	case HUBBUB_TOKEN_EOF:
+		p += snprintf(p, end - p, "\n");
+		break;
+	}
+
+	if (user_callback_handle_rxflow(r->wsi->protocol->callback,
+			r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+			r->wsi->user_space, start, p - start))
+		return -1;
+
+	return HUBBUB_OK;
+}
+
 /**
  * lws_client_connect_via_info() - Connect to another websocket server
  * @i:pointer to lws_client_connect_info struct
@@ -452,6 +561,11 @@ lws_client_connect_via_info(struct lws_client_connect_info *i)
 		i->parent_wsi->child_list = wsi;
 	}
 
+	if (i->uri_replace_to)
+		wsi->rw = lws_rewrite_create(wsi, html_parser_cb,
+					     i->uri_replace_from,
+					     i->uri_replace_to);
+
 	return wsi;
 
 bail:
diff --git a/lib/client.c b/lib/client.c
index 37779be4e7ae0994784a9c2eb4b32cbf39c13552..77eb7f08ba76c6b3d7e6935b5110a756e1b6ea66 100644
--- a/lib/client.c
+++ b/lib/client.c
@@ -622,6 +622,15 @@ lws_client_interpret_server_handshake(struct lws *wsi)
 			goto bail2;
 		}
 
+#ifndef LWS_NO_CLIENT
+	wsi->perform_rewrite = 0;
+	if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
+		if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE),
+				"text/html", 9))
+			wsi->perform_rewrite = 1;
+	}
+#endif
+
 		/* allocate the per-connection user memory (if any) */
 		if (lws_ensure_user_space(wsi)) {
 			lwsl_err("Problem allocating wsi user mem\n");
diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c
index 04c2ef5fe241b562838372a60d199bb24de07914..7942ac1d5acea68d2da1c8b41180756e460c1c67 100644
--- a/lib/libwebsockets.c
+++ b/lib/libwebsockets.c
@@ -158,12 +158,18 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 	if (wsi->child_list) {
 		wsi2 = wsi->child_list;
 		while (wsi2) {
-			lwsl_notice("%s: closing %p: close child %p\n",
-					__func__, wsi, wsi2);
+			//lwsl_notice("%s: closing %p: close child %p\n",
+			//		__func__, wsi, wsi2);
 			wsi1 = wsi2->sibling_list;
+			//lwsl_notice("%s: closing %p: next sibling %p\n",
+			//		__func__, wsi2, wsi1);
+			wsi2->parent = NULL;
+			/* stop it doing shutdown processing */
+			wsi2->socket_is_permanently_unusable = 1;
 			lws_close_free_wsi(wsi2, reason);
 			wsi2 = wsi1;
 		}
+		wsi->child_list = NULL;
 	}
 
 #ifdef LWS_WITH_CGI
@@ -186,7 +192,6 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
 
 	if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED &&
 	    wsi->u.http.fd != LWS_INVALID_FILE) {
-		lwsl_debug("closing http file\n");
 		lws_plat_file_close(wsi, wsi->u.http.fd);
 		wsi->u.http.fd = LWS_INVALID_FILE;
 		context->protocols[0].callback(wsi, LWS_CALLBACK_CLOSED_HTTP,
@@ -363,7 +368,7 @@ just_kill_connection:
 	if (wsi->state != LWSS_SHUTDOWN &&
 	    reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY &&
 	    !wsi->socket_is_permanently_unusable) {
-		lwsl_info("%s: shutting down connection: %p\n", __func__, wsi);
+		lwsl_info("%s: shutting down connection: %p (sock %d)\n", __func__, wsi, wsi->sock);
 		n = shutdown(wsi->sock, SHUT_WR);
 		if (n)
 			lwsl_debug("closing: shutdown ret %d\n", LWS_ERRNO);
@@ -384,7 +389,13 @@ just_kill_connection:
 	}
 #endif
 
-	lwsl_info("%s: real just_kill_connection: %p\n", __func__, wsi);
+	lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__,
+		  wsi, wsi->sock);
+
+	if (wsi->rw) {
+		lws_rewrite_destroy(wsi->rw);
+		wsi->rw = NULL;
+	}
 
 	/*
 	 * we won't be servicing or receiving anything further from this guy
@@ -502,6 +513,7 @@ lws_close_free_wsi_final(struct lws *wsi)
 
 	if (!lws_ssl_close(wsi) && lws_socket_is_valid(wsi->sock)) {
 #if LWS_POSIX
+		//lwsl_err("*** closing sockfd %d\n", wsi->sock);
 		n = compatible_close(wsi->sock);
 		if (n)
 			lwsl_debug("closing: close ret %d\n", LWS_ERRNO);
@@ -1742,12 +1754,11 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
 
 		/* ran out of input, ended the headers, or filled up the headers buf */
 		if (!n || wsi->hdr_state == LHCS_PAYLOAD || (p + 4) == end) {
-lwsl_err("a\n");
+
 			m = lws_write(wsi, (unsigned char *)start,
 				      p - start, LWS_WRITE_HTTP_HEADERS);
 			if (m < 0)
 				return -1;
-lwsl_err("b\n");
 			/* writeability becomes uncertain now we wrote
 			 * something, we must return to the event loop
 			 */
@@ -1755,7 +1766,7 @@ lwsl_err("b\n");
 			return 0;
 		}
 	}
-	lwsl_err("%s: stdout\n", __func__);
+	//lwsl_err("%s: stdout\n", __func__);
 	n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]),
 		 start, sizeof(buf) - LWS_PRE);
 
diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h
index f4b20c62e7f16832214f261e2b5cbc2e9b79c7e5..72b88657b84a0f205d0e65f3d72eb673898993f2 100644
--- a/lib/libwebsockets.h
+++ b/lib/libwebsockets.h
@@ -364,6 +364,7 @@ enum lws_callback_reasons {
 	LWS_CALLBACK_CLOSED_CLIENT_HTTP				= 45,
 	LWS_CALLBACK_RECEIVE_CLIENT_HTTP			= 46,
 	LWS_CALLBACK_COMPLETED_CLIENT_HTTP			= 47,
+	LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ			= 48,
 
 	/****** add new things just above ---^ ******/
 
@@ -1415,6 +1416,9 @@ struct lws_context_creation_info {
  * @parent_wsi:	if another wsi is responsible for this connection, give it here.
  *		this is used to make sure if the parent closes so do any
  *		child connections first.
+ * @uri_replace_from: if non-NULL, when this string is found in URIs in
+ *		text/html content-encoding, it's replaced with @uri_replace_to
+ * @uri_replace_to: see above
  */
 
 struct lws_client_connect_info {
@@ -1431,6 +1435,8 @@ struct lws_client_connect_info {
 	const struct lws_extension *client_exts;
 	const char *method;
 	struct lws *parent_wsi;
+	const char *uri_replace_from;
+	const char *uri_replace_to;
 
 	/* Add new things just above here ---^
 	 * This is part of the ABI, don't needlessly break compatibility
@@ -1893,6 +1899,9 @@ LWS_VISIBLE LWS_EXTERN int
 lws_cgi_kill(struct lws *wsi);
 #endif
 
+LWS_VISIBLE LWS_EXTERN int
+lws_http_client_read(struct lws *wsi, char **buf, int *len);
+
 /*
  * Wsi-associated File Operations access helpers
  *
diff --git a/lib/output.c b/lib/output.c
index 3204490fd9d043dc8957513bf21d7e437b8f47ec..b527e9c53149a3950184ab33adbeaf34a11251c8 100644
--- a/lib/output.c
+++ b/lib/output.c
@@ -600,7 +600,6 @@ LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
 all_sent:
 		if (!wsi->trunc_len && wsi->u.http.filepos == wsi->u.http.filelen) {
 			wsi->state = LWSS_HTTP;
-
 			/* we might be in keepalive, so close it off here */
 			lws_plat_file_close(wsi, wsi->u.http.fd);
 			wsi->u.http.fd = LWS_INVALID_FILE;
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 4d69bd56c2dcf86a6d91000f38a92bec3b05458e..b70a31ba8528ab0df6e02e32af980c78240f5c32 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -112,6 +112,10 @@
 #include <netdb.h>
 #include <signal.h>
 #include <sys/socket.h>
+#ifdef LWS_WITH_HTTP_PROXY
+#include <hubbub/hubbub.h>
+#include <hubbub/parser.h>
+#endif
 #ifdef LWS_BUILTIN_GETIFADDRS
  #include <getifaddrs.h>
 #else
@@ -157,7 +161,7 @@
 #define LWS_POLLHUP (POLLHUP|POLLERR)
 #define LWS_POLLIN (POLLIN)
 #define LWS_POLLOUT (POLLOUT)
-#define compatible_close(fd) close(fd)
+static inline int compatible_close(int fd) { return close(fd); }
 #define lws_set_blocking_send(wsi)
 
 #ifdef MBED_OPERATORS
@@ -1068,6 +1072,8 @@ enum lws_chunk_parser {
 };
 #endif
 
+struct lws_rewrite;
+
 struct lws {
 
 	/* structs */
@@ -1118,6 +1124,9 @@ struct lws {
 	BIO *client_bio;
 	struct lws *pending_read_list_prev, *pending_read_list_next;
 #endif
+#ifndef LWS_NO_CLIENT
+	struct lws_rewrite *rw;
+#endif
 #ifdef LWS_LATENCY
 	unsigned long action_start;
 	unsigned long latency_start;
@@ -1144,6 +1153,8 @@ struct lws {
 #ifndef LWS_NO_CLIENT
 	unsigned int do_ws:1; /* whether we are doing http or ws flow */
 	unsigned int chunked:1; /* if the clientside connection is chunked */
+	unsigned int client_rx_avail:1;
+	unsigned int perform_rewrite:1;
 #endif
 #ifndef LWS_NO_EXTENSIONS
 	unsigned int extension_data_pending:1;
@@ -1502,10 +1513,35 @@ lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len);
 LWS_EXTERN int LWS_WARN_UNUSED_RESULT
 lws_ssl_pending_no_ssl(struct lws *wsi);
 
-#ifndef LWS_NO_CLIENT
+#ifdef LWS_WITH_HTTP_PROXY
+struct lws_rewrite {
+	hubbub_parser *parser;
+	hubbub_parser_optparams params;
+	const char *from, *to;
+	int from_len, to_len;
+	unsigned char *p, *end;
+	struct lws *wsi;
+};
+static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len)
+{
+	if (s->len != len)
+		return 1;
+
+	return strncmp((const char *)s->ptr, p, len);
+}
+typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw);
 LWS_EXTERN int lws_client_socket_service(struct lws_context *context,
 					 struct lws *wsi,
 					 struct lws_pollfd *pollfd);
+LWS_EXTERN struct lws_rewrite *
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to);
+LWS_EXTERN void
+lws_rewrite_destroy(struct lws_rewrite *r);
+LWS_EXTERN int
+lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len);
+#endif
+
+#ifndef LWS_NO_CLIENT
 #ifdef LWS_OPENSSL_SUPPORT
 LWS_EXTERN int
 lws_context_init_client_ssl(struct lws_context_creation_info *info,
diff --git a/lib/rewrite.c b/lib/rewrite.c
new file mode 100644
index 0000000000000000000000000000000000000000..db98e556fe2c9dd384f923b4235acaa06c88f9a0
--- /dev/null
+++ b/lib/rewrite.c
@@ -0,0 +1,47 @@
+#include "private-libwebsockets.h"
+
+
+LWS_EXTERN struct lws_rewrite *
+lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to)
+{
+	struct lws_rewrite *r = lws_malloc(sizeof(*r));
+
+	if (hubbub_parser_create("UTF-8", false, &r->parser) != HUBBUB_OK) {
+		lws_free(r);
+
+		return NULL;
+	}
+	r->from = from;
+	r->from_len = strlen(from);
+	r->to = to;
+	r->to_len = strlen(to);
+	r->params.token_handler.handler = cb;
+	r->wsi = wsi;
+	r->params.token_handler.pw = (void *)r;
+	if (hubbub_parser_setopt(r->parser, HUBBUB_PARSER_TOKEN_HANDLER,
+				 &r->params) != HUBBUB_OK) {
+		lws_free(r);
+
+		return NULL;
+	}
+
+	return r;
+}
+
+LWS_EXTERN int
+lws_rewrite_parse(struct lws_rewrite *r,
+		  const unsigned char *in, int in_len)
+{
+	if (hubbub_parser_parse_chunk(r->parser, in, in_len) != HUBBUB_OK)
+		return -1;
+
+	return 0;
+}
+
+LWS_EXTERN void
+lws_rewrite_destroy(struct lws_rewrite *r)
+{
+	hubbub_parser_destroy(r->parser);
+	lws_free(r);
+}
+
diff --git a/lib/server.c b/lib/server.c
index 07e20af4369f791fccbd3c2a839e840da8d43db6..5a19a16ad93157709e6b1ed91827bdfcefcc2ea3 100644
--- a/lib/server.c
+++ b/lib/server.c
@@ -779,7 +779,7 @@ lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
 		return NULL;
 	}
 
-	lwsl_debug("%s: new wsi %p\n", __func__, new_wsi);
+	lwsl_info("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, accept_fd);
 
 	new_wsi->sock = accept_fd;
 
diff --git a/lib/service.c b/lib/service.c
index 9acdcb11650005b8a4ddc8074c7ed3887b8da642..368043ddbf1f0a79795b719b544becd2ebf74d0d 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -473,6 +473,134 @@ lws_service_flag_pending(struct lws_context *context, int tsi)
 	return forced;
 }
 
+
+/*
+ *
+ */
+LWS_VISIBLE int
+lws_http_client_read(struct lws *wsi, char **buf, int *len)
+{
+	int rlen, n;
+
+	rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len);
+	if (rlen < 0)
+		return -1;
+
+	*len = rlen;
+	if (rlen == 0)
+		return 0;
+
+//	lwsl_err("%s: read %d\n", __func__, rlen);
+
+	/* allow the source to signal he has data again next time */
+	wsi->client_rx_avail = 0;
+	lws_change_pollfd(wsi, 0, LWS_POLLIN);
+
+	/*
+	 * server may insist on transfer-encoding: chunked,
+	 * so http client must deal with it
+	 */
+spin_chunks:
+	while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) {
+		switch (wsi->chunk_parser) {
+		case ELCP_HEX:
+			if ((*buf)[0] == '\x0d') {
+				wsi->chunk_parser = ELCP_CR;
+				break;
+			}
+			n = char_to_hex((*buf)[0]);
+			if (n < 0)
+				return -1;
+			wsi->chunk_remaining <<= 4;
+			wsi->chunk_remaining |= n;
+			break;
+		case ELCP_CR:
+			if ((*buf)[0] != '\x0a')
+				return -1;
+			wsi->chunk_parser = ELCP_CONTENT;
+			lwsl_info("chunk %d\n", wsi->chunk_remaining);
+			if (wsi->chunk_remaining)
+				break;
+			lwsl_info("final chunk\n");
+			goto completed;
+
+		case ELCP_CONTENT:
+			break;
+
+		case ELCP_POST_CR:
+			if ((*buf)[0] != '\x0d')
+				return -1;
+
+			wsi->chunk_parser = ELCP_POST_LF;
+			break;
+
+		case ELCP_POST_LF:
+			if ((*buf)[0] != '\x0a')
+				return -1;
+
+			wsi->chunk_parser = ELCP_HEX;
+			wsi->chunk_remaining = 0;
+			break;
+		}
+		(*buf)++;
+		(*len)--;
+	}
+
+	if (wsi->chunked && !wsi->chunk_remaining)
+		return 0;
+
+	if (wsi->u.http.content_remain &&
+	    wsi->u.http.content_remain < *len)
+		n = wsi->u.http.content_remain;
+	else
+		n = *len;
+
+	if (wsi->chunked && wsi->chunk_remaining &&
+	    wsi->chunk_remaining < n)
+		n = wsi->chunk_remaining;
+
+	/* hubbub */
+	if (wsi->perform_rewrite)
+		lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n);
+	else
+
+		if (user_callback_handle_rxflow(wsi->protocol->callback,
+				wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
+				wsi->user_space, *buf, n))
+			return -1;
+
+	if (wsi->chunked && wsi->chunk_remaining) {
+		(*buf) += n;
+		wsi->chunk_remaining -= n;
+		*len -= n;
+	}
+
+	if (wsi->chunked && !wsi->chunk_remaining)
+		wsi->chunk_parser = ELCP_POST_CR;
+
+	if (wsi->chunked && *len) {
+		goto spin_chunks;
+	}
+
+	if (wsi->chunked)
+		return 0;
+
+	wsi->u.http.content_remain -= n;
+	if (wsi->u.http.content_remain || !wsi->u.http.content_length)
+		return 0;
+
+completed:
+	if (user_callback_handle_rxflow(wsi->protocol->callback,
+			wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
+			wsi->user_space, NULL, 0))
+		return -1;
+
+	if (lws_http_transaction_completed(wsi))
+		return -1;
+
+	return 0;
+}
+
 /**
  * lws_service_fd() - Service polled socket with something waiting
  * @context:	Websocket context
@@ -716,163 +844,70 @@ lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int t
 
 		if (!(pollfd->revents & pollfd->events & LWS_POLLIN))
 			break;
-read:
 
+read:
 		/* all the union members start with hdr, so even in ws mode
 		 * we can deal with the ah via u.hdr
 		 */
 		if (wsi->u.hdr.ah) {
-			lwsl_err("%s: %p: using inherited ah rx\n", __func__, wsi);
+			lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi);
 			eff_buf.token_len = wsi->u.hdr.ah->rxlen -
 					    wsi->u.hdr.ah->rxpos;
 			eff_buf.token = (char *)wsi->u.hdr.ah->rx +
 					wsi->u.hdr.ah->rxpos;
 		} else {
-
-			eff_buf.token_len = lws_ssl_capable_read(wsi, pt->serv_buf,
-					     pending ? pending : LWS_MAX_SOCKET_IO_BUF);
-			switch (eff_buf.token_len) {
-			case 0:
-				lwsl_info("service_fd: closing due to 0 length read\n");
-				goto close_and_handled;
-			case LWS_SSL_CAPABLE_MORE_SERVICE:
-				lwsl_info("SSL Capable more service\n");
-				n = 0;
-				goto handled;
-			case LWS_SSL_CAPABLE_ERROR:
-				lwsl_info("Closing when error\n");
-				goto close_and_handled;
-			}
-
-			eff_buf.token = (char *)pt->serv_buf;
-		}
-
-		/*
-		 * give any active extensions a chance to munge the buffer
-		 * before parse.  We pass in a pointer to an lws_tokens struct
-		 * prepared with the default buffer and content length that's in
-		 * there.  Rather than rewrite the default buffer, extensions
-		 * that expect to grow the buffer can adapt .token to
-		 * point to their own per-connection buffer in the extension
-		 * user allocation.  By default with no extensions or no
-		 * extension callback handling, just the normal input buffer is
-		 * used then so it is efficient.
-		 */
-drain:
-		if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) {
-			/*
-			 * server may insist on transfer-encoding: chunked,
-			 * so http client must deal with it
-			 */
-spin_chunks:
-			while (wsi->chunked &&
-			       (wsi->chunk_parser != ELCP_CONTENT) &&
-			       eff_buf.token_len) {
-				switch (wsi->chunk_parser) {
-				case ELCP_HEX:
-					if (eff_buf.token[0] == '\x0d') {
-						wsi->chunk_parser = ELCP_CR;
-						break;
-					}
-					n = char_to_hex(eff_buf.token[0]);
-					if (n < 0)
-						goto close_and_handled;
-					wsi->chunk_remaining <<= 4;
-					wsi->chunk_remaining |= n;
-					break;
-				case ELCP_CR:
-					if (eff_buf.token[0] != '\x0a')
-						goto close_and_handled;
-					wsi->chunk_parser = ELCP_CONTENT;
-					lwsl_info("chunk %d\n",
-						  wsi->chunk_remaining);
-					if (wsi->chunk_remaining)
-						break;
-					lwsl_info("final chunk\n");
-					if (user_callback_handle_rxflow(
-						  wsi->protocol->callback,
-						  wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
-						  wsi->user_space, NULL, 0))
-						goto close_and_handled;
-					if (lws_http_transaction_completed(wsi))
-						goto close_and_handled;
+			if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) {
+				eff_buf.token_len = lws_ssl_capable_read(wsi,
+					pt->serv_buf, pending ? pending :
+							LWS_MAX_SOCKET_IO_BUF);
+				switch (eff_buf.token_len) {
+				case 0:
+					lwsl_info("%s: zero length read\n", __func__);
+					goto close_and_handled;
+				case LWS_SSL_CAPABLE_MORE_SERVICE:
+					lwsl_info("SSL Capable more service\n");
 					n = 0;
 					goto handled;
-
-				case ELCP_CONTENT:
-					break;
-
-				case ELCP_POST_CR:
-					if (eff_buf.token[0] == '\x0d') {
-						wsi->chunk_parser = ELCP_POST_LF;
-						break;
-					}
-					goto close_and_handled;
-
-				case ELCP_POST_LF:
-					if (eff_buf.token[0] == '\x0a') {
-						wsi->chunk_parser = ELCP_HEX;
-						wsi->chunk_remaining = 0;
-						break;
-					}
+				case LWS_SSL_CAPABLE_ERROR:
+					lwsl_info("Closing when error\n");
 					goto close_and_handled;
 				}
-				eff_buf.token++;
-				eff_buf.token_len--;
-			}
 
-			if (wsi->chunked && !wsi->chunk_remaining) {
-				n = 0;
-				goto handled;
+				eff_buf.token = (char *)pt->serv_buf;
 			}
+		}
 
-			if (wsi->u.http.content_remain &&
-			    wsi->u.http.content_remain < eff_buf.token_len)
-				n = wsi->u.http.content_remain;
-			else
-				n = eff_buf.token_len;
+drain:
+		if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) {
 
-			if (wsi->chunked && wsi->chunk_remaining &&
-			    wsi->chunk_remaining < n)
-				n = wsi->chunk_remaining;
+			/*
+			 * simply mark ourselves as having readable data
+			 * and turn off our POLLIN
+			 */
+			wsi->client_rx_avail = 1;
+			lws_change_pollfd(wsi, LWS_POLLIN, 0);
 
+			/* let user code know, he'll usually ask for writeable
+			 * callback and drain / reenable it there
+			 */
 			if (user_callback_handle_rxflow(wsi->protocol->callback,
 					wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
-					wsi->user_space, (void *)eff_buf.token,
-					n))
-				goto close_and_handled;
-
-			if (wsi->chunked && wsi->chunk_remaining) {
-				eff_buf.token += n;
-				wsi->chunk_remaining -= n;
-				eff_buf.token_len -= n;
-			}
-
-			if (wsi->chunked && !wsi->chunk_remaining)
-				wsi->chunk_parser = ELCP_POST_CR;
-
-			if (wsi->chunked && eff_buf.token_len) {
-				goto spin_chunks;
-			}
-
-			if (wsi->chunked) {
-				n = 0;
-				goto handled;
-			}
-
-			wsi->u.http.content_remain -= n;
-			if (wsi->u.http.content_remain)
-				goto handled;
-
-			if (user_callback_handle_rxflow(wsi->protocol->callback,
-					wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
 					wsi->user_space, NULL, 0))
 				goto close_and_handled;
 
-			if (wsi->u.http.connection_type == HTTP_CONNECTION_CLOSE)
-				goto close_and_handled;
-			goto handled;
+
 		}
+		/*
+		 * give any active extensions a chance to munge the buffer
+		 * before parse.  We pass in a pointer to an lws_tokens struct
+		 * prepared with the default buffer and content length that's in
+		 * there.  Rather than rewrite the default buffer, extensions
+		 * that expect to grow the buffer can adapt .token to
+		 * point to their own per-connection buffer in the extension
+		 * user allocation.  By default with no extensions or no
+		 * extension callback handling, just the normal input buffer is
+		 * used then so it is efficient.
+		 */
 		do {
 			more = 0;
 
diff --git a/lws_config.h.in b/lws_config.h.in
index a647a4b51e114d6e75f7215fa797fb01f4d31264..341515f0310d696cef5f5244ea02957233562b70 100644
--- a/lws_config.h.in
+++ b/lws_config.h.in
@@ -80,6 +80,9 @@
 /* whether the Openssl is recent enough, and / or built with, ecdh */
 #cmakedefine LWS_HAVE_OPENSSL_ECDH_H
 
+/* HTTP Proxy support */
+#cmakedefine LWS_WITH_HTTP_PROXY
+
 /* Maximum supported service threads */
 #define LWS_MAX_SMP ${LWS_MAX_SMP}
 
diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c
index 594c8dff3d759628d2caa29537f48a395d9b8759..1535e437ae46ce47d56afc6b675b03fa3736d473 100644
--- a/test-server/test-server-http.c
+++ b/test-server/test-server-http.c
@@ -126,6 +126,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 	unsigned char *end;
 	struct timeval tv;
 	unsigned char *p;
+	struct lws *wsi1;
 	char buf[256];
 	char b64[64];
 	int n, m;
@@ -161,15 +162,11 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 			goto try_to_reuse;
 		}
 
-		/* this example server has no concept of directories */
-		if (strchr((const char *)in + 1, '/')) {
-			lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
-			goto try_to_reuse;
-		}
-
 #ifndef LWS_NO_CLIENT
-		if (!strcmp(in, "/proxytest")) {
+		if (!strncmp(in, "/proxytest", 10)) {
 			struct lws_client_connect_info i;
+			char *rootpath = "/";
+			const char *p = (const char *)in;
 
 			if (lws_get_child(wsi))
 				break;
@@ -177,22 +174,36 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 			pss->client_finished = 0;
 			memset(&i,0, sizeof(i));
 			i.context = lws_get_context(wsi);
-			i.address = "example.com";
+			i.address = "git.libwebsockets.org";
 			i.port = 80;
 			i.ssl_connection = 0;
-			i.path = "/";
-			i.host = "example.com";
+			if (p[10])
+				i.path = in + 10;
+			else
+				i.path = rootpath;
+			i.host = "git.libwebsockets.org";
 			i.origin = NULL;
 			i.method = "GET";
 			i.parent_wsi = wsi;
+			i.uri_replace_from = "git.libwebsockets.org/";
+			i.uri_replace_to = "/proxytest/";
 			if (!lws_client_connect_via_info(&i)) {
 				lwsl_err("proxy connect fail\n");
 				break;
 			}
+
+
+
 			break;
 		}
 #endif
 
+		/* this example server has no concept of directories */
+		if (strchr((const char *)in + 1, '/')) {
+			lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
+			goto try_to_reuse;
+		}
+
 #ifdef LWS_WITH_CGI
 		if (!strcmp(in, "/cgitest")) {
 			static char *cmd[] = {
@@ -396,11 +407,33 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
 		if (pss->fd == LWS_INVALID_FILE)
 			goto try_to_reuse;
 #ifdef LWS_WITH_CGI
-		if (pss->reason_bf) {
+		if (pss->reason_bf & 1) {
 			if (lws_cgi_write_split_stdout_headers(wsi) < 0)
 				goto bail;
 
-			pss->reason_bf = 0;
+			pss->reason_bf &= ~1;
+			break;
+		}
+#endif
+#ifndef LWS_NO_CLIENT
+		if (pss->reason_bf & 2) {
+			char *px = buf + LWS_PRE;
+			int lenx = sizeof(buf) - LWS_PRE;
+			/*
+			 * our sink is writeable and our source has something
+			 * to read.  So read a lump of source material of
+			 * suitable size to send or what's available, whichever
+			 * is the smaller.
+			 */
+			pss->reason_bf &= ~2;
+			wsi1 = lws_get_child(wsi);
+			if (!wsi1)
+				break;
+			if (lws_http_client_read(wsi1, &px, &lenx) < 0)
+				goto bail;
+
+			if (pss->client_finished)
+				return -1;
 			break;
 		}
 #endif
@@ -470,7 +503,7 @@ bail:
 	 * callback for confirming to continue with client IP appear in
 	 * protocol 0 callback since no websocket protocol has been agreed
 	 * yet.  You can just ignore this if you won't filter on client IP
-	 * since the default uhandled callback return is 0 meaning let the
+	 * since the default unhandled callback return is 0 meaning let the
 	 * connection continue.
 	 */
 	case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
@@ -478,7 +511,9 @@ bail:
 		break;
 
 #ifndef LWS_WITH_CLIENT
-	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
+	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
+		char ctype[64], ctlen = 0;
+		lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n");
 		p = buffer + LWS_PRE;
 		end = p + sizeof(buffer) - LWS_PRE;
 		if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end))
@@ -488,14 +523,17 @@ bail:
 			    	(unsigned char *)"libwebsockets",
 				13, &p, end))
 			return 1;
-		if (lws_add_http_header_by_token(lws_get_parent(wsi),
+
+		ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE);
+		if (ctlen > 0) {
+			if (lws_add_http_header_by_token(lws_get_parent(wsi),
 				WSI_TOKEN_HTTP_CONTENT_TYPE,
-			    	(unsigned char *)"text/html", 9, &p, end))
-			return 1;
+				(unsigned char *)ctype, ctlen, &p, end))
+				return 1;
+		}
 #if 0
 		if (lws_add_http_header_content_length(lws_get_parent(wsi),
-						       file_len, &p,
-						       end))
+						       file_len, &p, end))
 			return 1;
 #endif
 		if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
@@ -510,20 +548,39 @@ bail:
 		if (n < 0)
 			return -1;
 
-		break;
+		break; }
 	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
+		//lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
 		return -1;
 		break;
 	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
-		m = lws_write(lws_get_parent(wsi), in, len, LWS_WRITE_HTTP);
+		//lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi);
+		assert(lws_get_parent(wsi));
+		if (!lws_get_parent(wsi))
+			break;
+		// lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d,  len %d\n",
+		//		wsi, lws_get_socket_fd(wsi),
+		//		lws_get_parent(wsi),
+		//		lws_get_socket_fd(lws_get_parent(wsi)), len);
+		pss1 = lws_wsi_user(lws_get_parent(wsi));
+		pss1->reason_bf |= 2;
+		lws_callback_on_writable(lws_get_parent(wsi));
+		break;
+	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
+		//lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len);
+		assert(lws_get_parent(wsi));
+		m = lws_write(lws_get_parent(wsi), (unsigned char *)in,
+				len, LWS_WRITE_HTTP);
 		if (m < 0)
-			return 1;
+			return -1;
 		break;
 	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
+		//lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n");
+		assert(lws_get_parent(wsi));
+		if (!lws_get_parent(wsi))
+			break;
 		pss1 = lws_wsi_user(lws_get_parent(wsi));
 		pss1->client_finished = 1;
-		lws_callback_on_writable(lws_get_parent(wsi));
-		return -1;
 		break;
 #endif
 
@@ -542,7 +599,7 @@ bail:
 			/* TBD stdin rx flow control */
 			break;
 		case LWS_STDOUT:
-			pss->reason_bf |= 1 << pss->args.ch;
+			pss->reason_bf |= 1;
 			/* when writing to MASTER would not block */
 			lws_callback_on_writable(wsi);
 			break;