diff --git a/changelog b/changelog
index fc87206a9e484d37f1fc86fdab0b0933e96ed8c2..ef5a406f9f178de525d2bdfcca300f478e6a4e6d 100644
--- a/changelog
+++ b/changelog
@@ -79,7 +79,7 @@ just deferred until an ah becomes available.
 8) The test client pays attention to if you give it an http:/ or https://
 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.
+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,
diff --git a/lib/client.c b/lib/client.c
index fdf1eff2eb1e863fab2ef8ccababa85bd2ebf31b..37779be4e7ae0994784a9c2eb4b32cbf39c13552 100644
--- a/lib/client.c
+++ b/lib/client.c
@@ -628,6 +628,17 @@ lws_client_interpret_server_handshake(struct lws *wsi)
 			goto bail2;
 		}
 
+		/* he may choose to send us stuff in chunked transfer-coding */
+		wsi->chunked = 0;
+		wsi->chunk_remaining = 0; /* ie, next thing is chunk size */
+		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) {
+			wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi,
+					       WSI_TOKEN_HTTP_TRANSFER_ENCODING),
+					"chunked");
+			/* first thing is hex, after payload there is crlf */
+			wsi->chunk_parser = ELCP_HEX;
+		}
+
 		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
 			wsi->u.http.content_length =
 					atoi(lws_hdr_simple_ptr(wsi,
@@ -635,8 +646,9 @@ lws_client_interpret_server_handshake(struct lws *wsi)
 			lwsl_notice("%s: incoming content length %d\n", __func__,
 					wsi->u.http.content_length);
 			wsi->u.http.content_remain = wsi->u.http.content_length;
-		} else /* can't do 1.1 without a content length */
-			wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
+		} else /* can't do 1.1 without a content length or chunked */
+			if (!wsi->chunked)
+				wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
 
 		/*
 		 * we seem to be good to go, give client last chance to check
diff --git a/lib/parsers.c b/lib/parsers.c
index 96be75dc9e91c7decf635b99a063a3f1d1278863..879db995281c07e6bbb4e62271d3451366160be4 100644
--- a/lib/parsers.c
+++ b/lib/parsers.c
@@ -496,7 +496,7 @@ lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s)
 	return 0;
 }
 
-static signed char char_to_hex(const char c)
+signed char char_to_hex(const char c)
 {
 	if (c >= '0' && c <= '9')
 		return c - '0';
diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h
index 5f118ad765abe818ee4d941ca908ea9cbb03bfab..4d69bd56c2dcf86a6d91000f38a92bec3b05458e 100644
--- a/lib/private-libwebsockets.h
+++ b/lib/private-libwebsockets.h
@@ -1054,7 +1054,18 @@ struct lws_cgi {
 
 	unsigned int being_closed:1;
 };
+#endif
+
+signed char char_to_hex(const char c);
 
+#ifndef LWS_NO_CLIENT
+enum lws_chunk_parser {
+	ELCP_HEX,
+	ELCP_CR,
+	ELCP_CONTENT,
+	ELCP_POST_CR,
+	ELCP_POST_LF,
+};
 #endif
 
 struct lws {
@@ -1121,6 +1132,9 @@ struct lws {
 	unsigned int trunc_alloc_len; /* size of malloc */
 	unsigned int trunc_offset; /* where we are in terms of spilling */
 	unsigned int trunc_len; /* how much is buffered */
+#ifndef LWS_NO_CLIENT
+	int chunk_remaining;
+#endif
 
 	unsigned int hdr_parsing_completed:1;
 	unsigned int user_space_externally_allocated:1;
@@ -1129,6 +1143,7 @@ struct lws {
 	unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */
 #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 */
 #endif
 #ifndef LWS_NO_EXTENSIONS
 	unsigned int extension_data_pending:1;
@@ -1161,6 +1176,9 @@ struct lws {
 	char cgi_channel; /* which of stdin/out/err */
 	char hdr_state;
 #endif
+#ifndef LWS_NO_CLIENT
+	char chunk_parser; /* enum lws_chunk_parser */
+#endif
 };
 
 LWS_EXTERN int log_level;
diff --git a/lib/service.c b/lib/service.c
index ee872feacb9d71aaf94e665cdd857d183253f19d..9acdcb11650005b8a4ddc8074c7ed3887b8da642 100644
--- a/lib/service.c
+++ b/lib/service.c
@@ -760,24 +760,110 @@ read:
 		 */
 drain:
 		if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) {
-			lwsl_notice("%s: calling LWS_CALLBACK_RECEIVE_CLIENT_HTTP, "
-				    "rem %d len %d\n", __func__,
-				    wsi->u.http.content_remain, eff_buf.token_len);
-			if ((int)wsi->u.http.content_remain < eff_buf.token_len)
+			/*
+			 * 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;
+					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;
+					}
+					goto close_and_handled;
+				}
+				eff_buf.token++;
+				eff_buf.token_len--;
+			}
+
+			if (wsi->chunked && !wsi->chunk_remaining) {
+				n = 0;
+				goto handled;
+			}
+
+			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;
+
+			if (wsi->chunked && wsi->chunk_remaining &&
+			    wsi->chunk_remaining < n)
+				n = wsi->chunk_remaining;
+
 			if (user_callback_handle_rxflow(wsi->protocol->callback,
 					wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP,
 					wsi->user_space, (void *)eff_buf.token,
-					eff_buf.token_len))
+					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;
 
-			lwsl_notice("%s: client http receved all content\n",
-				    __func__);
 			if (user_callback_handle_rxflow(wsi->protocol->callback,
 					wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
 					wsi->user_space, NULL, 0))
@@ -821,7 +907,7 @@ drain:
 		} while (more);
 
 		if (wsi->u.hdr.ah) {
-			lwsl_err("%s: %p: detaching inherited used ah\n",
+			lwsl_info("%s: %p: detaching inherited used ah\n",
 				 __func__, wsi);
 			/* show we used all the pending rx up */
 			wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;