diff --git a/docs/overview/build-options/disabling-flags.md b/docs/overview/build-options/disabling-flags.md
index 961cd071208f71bb7b68fa34530d2be16fdfda6e..ac5efee3a67d069867592fcb42db5f1186855d5e 100644
--- a/docs/overview/build-options/disabling-flags.md
+++ b/docs/overview/build-options/disabling-flags.md
@@ -2,7 +2,6 @@
 title: Disabling flags
 ---
 
-- `MG_DISABLE_HTTP_WEBSOCKET` disable HTTP + WebSocket protocol support
 - `MG_DISABLE_HTTP_DIGEST_AUTH` disable HTTP Digest (MD5) authorisation support
 - `MG_DISABLE_SHA1` disable SHA1 support (used by WebSocket)
 - `MG_DISABLE_MD5` disable MD5 support (used by HTTP auth)
diff --git a/docs/overview/build-options/enabling-flags.md b/docs/overview/build-options/enabling-flags.md
index a1858a285cdb55975f7eb8920271f5098dc08a89..f3cca7c00701e6f1764d78a1e4c24bbfe6342a11 100644
--- a/docs/overview/build-options/enabling-flags.md
+++ b/docs/overview/build-options/enabling-flags.md
@@ -2,13 +2,16 @@
 title: Enabling flags
 ---
 
-- `MG_ENABLE_CGI` Enable CGI support
-- `MG_ENABLE_SSL` Enable SSL/TLS support (OpenSSL API)
+- `MG_ENABLE_SSL` Enable [SSL/TLS support](https://docs.cesanta.com/mongoose/master/#/http/ssl.md/) (OpenSSL API)
 - `MG_ENABLE_IPV6` Enable IPV6 support
-- `MG_ENABLE_MQTT` enable MQTT client
-- `MG_ENABLE_MQTT_BROKER` enable MQTT broker
+- `MG_ENABLE_MQTT` enable [MQTT client](https://docs.cesanta.com/mongoose/master/#/mqtt/client_example.md/)
+- `MG_ENABLE_MQTT_BROKER` enable [MQTT broker](https://docs.cesanta.com/mongoose/master/#/mqtt/server_example.md/)
 - `MG_ENABLE_DNS_SERVER` enable DNS server
 - `MG_ENABLE_COAP` enable CoAP protocol
+- `MG_ENABLE_HTTP` Enable HTTP protocol support (on by default, set to 0 to disable)
+- `MG_ENABLE_HTTP_CGI` Enable [CGI](https://docs.cesanta.com/mongoose/master/#/http/cgi.md/) support
+- `MG_ENABLE_HTTP_SSI` Enable [Server SIde Includes](https://docs.cesanta.com/mongoose/master/#/http/ssi.md/) support
 - `MG_ENABLE_HTTP_WEBDAV` enable WebDAV extensions to HTTP
+- `MG_ENABLE_HTTP_WEBSOCKET` enable WebSocket extension to HTTP (on by default, =0 to disable)
 - `MG_ENABLE_GETADDRINFO` enable `getaddrinfo()` in `mg_resolve2()`
 - `MG_ENABLE_THREADS` enable `mg_start_thread()` API
diff --git a/examples/mqtt_broker/Makefile b/examples/mqtt_broker/Makefile
index 0028d1af4986b06de2d1fe464474166491aa693a..f0c17a0e0daaed2a74d8425a185f4953a76e6f12 100644
--- a/examples/mqtt_broker/Makefile
+++ b/examples/mqtt_broker/Makefile
@@ -1,4 +1,4 @@
 PROG = mqtt_broker
-MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER
+MODULE_CFLAGS = -DMG_ENABLE_MQTT_BROKER -DMG_ENABLE_HTTP=0
 SSL_LIB=openssl
 include ../examples.mk
diff --git a/examples/mqtt_client/Makefile b/examples/mqtt_client/Makefile
index 900fb580445844808ed93d42b0de7cde52f78d6d..a06c0ad4afb5753081cf145913e5a3d44e4ccb46 100644
--- a/examples/mqtt_client/Makefile
+++ b/examples/mqtt_client/Makefile
@@ -1,4 +1,4 @@
 PROG = mqtt_client
-MODULE_CFLAGS = -DMG_ENABLE_MQTT
+MODULE_CFLAGS = -DMG_ENABLE_MQTT -DMG_ENABLE_HTTP=0
 SSL_LIB=openssl
 include ../examples.mk
diff --git a/examples/restful_server/Makefile b/examples/restful_server/Makefile
index f481ef3048280654559432ce20159614ca3a3186..6961bbd0128cb9f99571b6960b056f3e695424ea 100644
--- a/examples/restful_server/Makefile
+++ b/examples/restful_server/Makefile
@@ -1,3 +1,3 @@
 PROG = restful_server
-MODULE_CFLAGS=-DMG_ENABLE_THREADS -DMG_DISABLE_HTTP_WEBSOCKET
+MODULE_CFLAGS=-DMG_ENABLE_THREADS -DMG_ENABLE_HTTP_WEBSOCKET=0
 include ../examples.mk
diff --git a/examples/restful_server/restful_server.c b/examples/restful_server/restful_server.c
index 9419755a8e9025b31db3c47287a2bf36f73baf66..264989e4ba138dfc72168bad17e40a04c1b4f452 100644
--- a/examples/restful_server/restful_server.c
+++ b/examples/restful_server/restful_server.c
@@ -86,7 +86,7 @@ int main(int argc, char *argv[]) {
       s_http_server_opts.per_directory_auth_file = argv[++i];
     } else if (strcmp(argv[i], "-r") == 0 && i + 1 < argc) {
       s_http_server_opts.url_rewrites = argv[++i];
-#if !MG_DISABLE_CGI
+#if MG_ENABLE_HTTP_CGI
     } else if (strcmp(argv[i], "-i") == 0 && i + 1 < argc) {
       s_http_server_opts.cgi_interpreter = argv[++i];
 #endif
diff --git a/mongoose.c b/mongoose.c
index 10ff08dc4496641fd25a0bf31bb5f6b208023ec0..74e9b9feaf5dd2a818d1b61239b6f9604b34aaf4 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -71,34 +71,11 @@ MG_INTERNAL void mg_remove_conn(struct mg_connection *c);
 MG_INTERNAL struct mg_connection *mg_create_connection(
     struct mg_mgr *mgr, mg_event_handler_t callback,
     struct mg_add_sock_opts opts);
-#if MG_ENABLE_FILESYSTEM
-MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
-                                     const struct mg_serve_http_opts *opts,
-                                     char **local_path,
-                                     struct mg_str *remainder);
-MG_INTERNAL time_t mg_parse_date_string(const char *datetime);
-MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st);
-#endif
 #ifdef _WIN32
 /* Retur value is the same as for MultiByteToWideChar. */
 int to_wchar(const char *path, wchar_t *wbuf, size_t wbuf_len);
 #endif
 
-/*
- * Reassemble the content of the buffer (buf, blen) which should be
- * in the HTTP chunked encoding, by collapsing data chunks to the
- * beginning of the buffer.
- *
- * If chunks get reassembled, modify hm->body to point to the reassembled
- * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK
- * in nc->flags, delete reassembled body from the mbuf.
- *
- * Return reassembled body size.
- */
-MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
-                                     struct http_message *hm, char *buf,
-                                     size_t blen);
-
 struct ctl_msg {
   mg_event_handler_t callback;
   char message[MG_CTL_MSG_MESSAGE_SIZE];
@@ -117,7 +94,31 @@ extern void *(*test_calloc)(size_t count, size_t size);
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 #endif
 
-#if !MG_DISABLE_HTTP && MG_ENABLE_CGI
+#if MG_ENABLE_HTTP
+/*
+ * Reassemble the content of the buffer (buf, blen) which should be
+ * in the HTTP chunked encoding, by collapsing data chunks to the
+ * beginning of the buffer.
+ *
+ * If chunks get reassembled, modify hm->body to point to the reassembled
+ * body and fire MG_EV_HTTP_CHUNK event. If handler sets MG_F_DELETE_CHUNK
+ * in nc->flags, delete reassembled body from the mbuf.
+ *
+ * Return reassembled body size.
+ */
+MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
+                                     struct http_message *hm, char *buf,
+                                     size_t blen);
+
+#if MG_ENABLE_FILESYSTEM
+MG_INTERNAL int mg_uri_to_local_path(struct http_message *hm,
+                                     const struct mg_serve_http_opts *opts,
+                                     char **local_path,
+                                     struct mg_str *remainder);
+MG_INTERNAL time_t mg_parse_date_string(const char *datetime);
+MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st);
+#endif
+#if MG_ENABLE_HTTP_CGI
 MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
                                const struct mg_str *path_info,
                                const struct http_message *hm,
@@ -125,7 +126,13 @@ MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog,
 struct mg_http_proto_data_cgi;
 MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d);
 #endif
-#if !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV
+#if MG_ENABLE_HTTP_SSI
+MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc,
+                                       struct http_message *hm,
+                                       const char *path,
+                                       const struct mg_serve_http_opts *opts);
+#endif
+#if MG_ENABLE_HTTP_WEBDAV
 MG_INTERNAL int mg_is_dav_request(const struct mg_str *s);
 MG_INTERNAL void mg_handle_propfind(struct mg_connection *nc, const char *path,
                                     cs_stat_t *stp, struct http_message *hm,
@@ -142,6 +149,12 @@ MG_INTERNAL void mg_handle_delete(struct mg_connection *nc,
 MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,
                                struct http_message *hm);
 #endif
+#if MG_ENABLE_HTTP_WEBSOCKET
+MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev, void *ev_data);
+MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,
+                                 const struct mg_str *key);
+#endif
+#endif /* MG_ENABLE_HTTP */
 
 MG_INTERNAL int mg_get_errno();
 
@@ -3818,17 +3831,13 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) {
  * All rights reserved
  */
 
-#if !MG_DISABLE_HTTP
+#if MG_ENABLE_HTTP
 
 /* Amalgamated: #include "mongoose/src/internal.h" */
 /* Amalgamated: #include "mongoose/src/util.h" */
 /* Amalgamated: #include "common/sha1.h" */
 /* Amalgamated: #include "common/md5.h" */
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
-#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1)
-#endif
-
 static const char *mg_version_header = "Mongoose/" MG_VERSION;
 
 enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT };
@@ -3841,7 +3850,7 @@ struct mg_http_proto_data_file {
   enum mg_http_proto_data_type type;
 };
 
-#if MG_ENABLE_CGI
+#if MG_ENABLE_HTTP_CGI
 struct mg_http_proto_data_cgi {
   struct mg_connection *cgi_nc;
 };
@@ -3883,7 +3892,7 @@ struct mg_http_proto_data {
 #if MG_ENABLE_FILESYSTEM
   struct mg_http_proto_data_file file;
 #endif
-#if MG_ENABLE_CGI
+#if MG_ENABLE_HTTP_CGI
   struct mg_http_proto_data_cgi cgi;
 #endif
 #if MG_ENABLE_HTTP_STREAMING_MULTIPART
@@ -3945,7 +3954,7 @@ static void mg_http_conn_destructor(void *proto_data) {
 #if MG_ENABLE_FILESYSTEM
   mg_http_free_proto_data_file(&pd->file);
 #endif
-#if MG_ENABLE_CGI
+#if MG_ENABLE_HTTP_CGI
   mg_http_free_proto_data_cgi(&pd->cgi);
 #endif
 #if MG_ENABLE_HTTP_STREAMING_MULTIPART
@@ -4185,877 +4194,580 @@ struct mg_str *mg_get_http_header(struct http_message *hm, const char *name) {
   return NULL;
 }
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
+#if MG_ENABLE_FILESYSTEM
+static void mg_http_transfer_file_data(struct mg_connection *nc) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+  char buf[MG_MAX_HTTP_SEND_MBUF];
+  size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent);
 
-static int mg_is_ws_fragment(unsigned char flags) {
-  return (flags & 0x80) == 0 || (flags & 0x0f) == 0;
-}
+  if (pd->file.type == DATA_FILE) {
+    struct mbuf *io = &nc->send_mbuf;
+    if (io->len < sizeof(buf)) {
+      to_read = sizeof(buf) - io->len;
+    }
 
-static int mg_is_ws_first_fragment(unsigned char flags) {
-  return (flags & 0x80) == 0 && (flags & 0x0f) != 0;
-}
+    if (left > 0 && to_read > left) {
+      to_read = left;
+    }
 
-static void mg_handle_incoming_websocket_frame(struct mg_connection *nc,
-                                               struct websocket_message *wsm) {
-  if (wsm->flags & 0x8) {
-    mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);
-  } else {
-    mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm);
+    if (to_read == 0) {
+      /* Rate limiting. send_mbuf is too full, wait until it's drained. */
+    } else if (pd->file.sent < pd->file.cl &&
+               (n = fread(buf, 1, to_read, pd->file.fp)) > 0) {
+      mg_send(nc, buf, n);
+      pd->file.sent += n;
+    } else {
+      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
+      mg_http_free_proto_data_file(&pd->file);
+    }
+  } else if (pd->file.type == DATA_PUT) {
+    struct mbuf *io = &nc->recv_mbuf;
+    size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len;
+    size_t n = fwrite(io->buf, 1, to_write, pd->file.fp);
+    if (n > 0) {
+      mbuf_remove(io, n);
+      pd->file.sent += n;
+    }
+    if (n == 0 || pd->file.sent >= pd->file.cl) {
+      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
+      mg_http_free_proto_data_file(&pd->file);
+    }
+  }
+#if MG_ENABLE_HTTP_CGI
+  else if (pd->cgi.cgi_nc != NULL) {
+    /* This is POST data that needs to be forwarded to the CGI process */
+    if (pd->cgi.cgi_nc != NULL) {
+      mg_forward(nc, pd->cgi.cgi_nc);
+    } else {
+      nc->flags |= MG_F_SEND_AND_CLOSE;
+    }
   }
+#endif
 }
+#endif /* MG_ENABLE_FILESYSTEM */
 
-static int mg_deliver_websocket_data(struct mg_connection *nc) {
-  /* Using unsigned char *, cause of integer arithmetic below */
-  uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len,
-              mask_len = 0, header_len = 0;
-  unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p,
-                *e = p + buf_len;
-  unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */
-  int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) &&
-                  !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);
+/*
+ * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or
+ * if it's incomplete. If the chunk is fully buffered, return total number of
+ * bytes in a chunk, and store data in `data`, `data_len`.
+ */
+static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data,
+                                  size_t *chunk_len) {
+  unsigned char *s = (unsigned char *) buf;
+  size_t n = 0; /* scanned chunk length */
+  size_t i = 0; /* index in s */
 
-  /* If that's a continuation frame that must be reassembled, handle it */
-  if (reass && !mg_is_ws_first_fragment(p[0]) &&
-      buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) {
-    buf += 1 + sizeof(*sizep) + *sizep;
-    buf_len -= 1 + sizeof(*sizep) + *sizep;
+  /* Scan chunk length. That should be a hexadecimal number. */
+  while (i < len && isxdigit(s[i])) {
+    n *= 16;
+    n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10;
+    i++;
   }
 
-  if (buf_len >= 2) {
-    len = buf[1] & 127;
-    mask_len = buf[1] & 128 ? 4 : 0;
-    if (len < 126 && buf_len >= mask_len) {
-      data_len = len;
-      header_len = 2 + mask_len;
-    } else if (len == 126 && buf_len >= 4 + mask_len) {
-      header_len = 4 + mask_len;
-      data_len = ntohs(*(uint16_t *) &buf[2]);
-    } else if (buf_len >= 10 + mask_len) {
-      header_len = 10 + mask_len;
-      data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) +
-                 ntohl(*(uint32_t *) &buf[6]);
-    }
+  /* Skip new line */
+  if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
+    return 0;
   }
+  i += 2;
 
-  frame_len = header_len + data_len;
-  ok = frame_len > 0 && frame_len <= buf_len;
+  /* Record where the data is */
+  *chunk_data = (char *) s + i;
+  *chunk_len = n;
 
-  if (ok) {
-    struct websocket_message wsm;
+  /* Skip data */
+  i += n;
 
-    wsm.size = (size_t) data_len;
-    wsm.data = buf + header_len;
-    wsm.flags = buf[0];
+  /* Skip new line */
+  if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
+    return 0;
+  }
+  return i + 2;
+}
 
-    /* Apply mask if necessary */
-    if (mask_len > 0) {
-      for (i = 0; i < data_len; i++) {
-        buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
-      }
+MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
+                                     struct http_message *hm, char *buf,
+                                     size_t blen) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+  char *data;
+  size_t i, n, data_len, body_len, zero_chunk_received = 0;
+  /* Find out piece of received data that is not yet reassembled */
+  body_len = (size_t) pd->chunk.body_len;
+  assert(blen >= body_len);
+
+  /* Traverse all fully buffered chunks */
+  for (i = body_len;
+       (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0;
+       i += n) {
+    /* Collapse chunk data to the rest of HTTP body */
+    memmove(buf + body_len, data, data_len);
+    body_len += data_len;
+    hm->body.len = body_len;
+
+    if (data_len == 0) {
+      zero_chunk_received = 1;
+      i += n;
+      break;
     }
+  }
 
-    if (reass) {
-      /* On first fragmented frame, nullify size */
-      if (mg_is_ws_first_fragment(wsm.flags)) {
-        mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep));
-        p[0] &= ~0x0f; /* Next frames will be treated as continuation */
-        buf = p + 1 + sizeof(*sizep);
-        *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */
-      }
+  if (i > body_len) {
+    /* Shift unparsed content to the parsed body */
+    assert(i <= blen);
+    memmove(buf + body_len, buf + i, blen - i);
+    memset(buf + body_len + blen - i, 0, i - body_len);
+    nc->recv_mbuf.len -= i - body_len;
+    pd->chunk.body_len = body_len;
 
-      /* Append this frame to the reassembled buffer */
-      memmove(buf, wsm.data, e - wsm.data);
-      (*sizep) += wsm.size;
-      nc->recv_mbuf.len -= wsm.data - buf;
+    /* Send MG_EV_HTTP_CHUNK event */
+    nc->flags &= ~MG_F_DELETE_CHUNK;
+    mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm);
 
-      /* On last fragmented frame - call user handler and remove data */
-      if (wsm.flags & 0x80) {
-        wsm.data = p + 1 + sizeof(*sizep);
-        wsm.size = *sizep;
-        mg_handle_incoming_websocket_frame(nc, &wsm);
-        mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep);
-      }
-    } else {
-      /* TODO(lsm): properly handle OOB control frames during defragmentation */
-      mg_handle_incoming_websocket_frame(nc, &wsm);
-      mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */
+    /* Delete processed data if user set MG_F_DELETE_CHUNK flag */
+    if (nc->flags & MG_F_DELETE_CHUNK) {
+      memset(buf, 0, body_len);
+      memmove(buf, buf + body_len, blen - i);
+      nc->recv_mbuf.len -= body_len;
+      hm->body.len = 0;
+      pd->chunk.body_len = 0;
     }
 
-    /* If client closes, close too */
-    if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
-      nc->flags |= MG_F_SEND_AND_CLOSE;
+    if (zero_chunk_received) {
+      hm->message.len = (size_t) pd->chunk.body_len + blen - i;
     }
   }
 
-  return ok;
+  return body_len;
 }
 
-struct ws_mask_ctx {
-  size_t pos; /* zero means unmasked */
-  uint32_t mask;
-};
-
-static uint32_t mg_ws_random_mask(void) {
-  uint32_t mask;
-/*
- * The spec requires WS client to generate hard to
- * guess mask keys. From RFC6455, Section 5.3:
- *
- * The unpredictability of the masking key is essential to prevent
- * authors of malicious applications from selecting the bytes that appear on
- * the wire.
- *
- * Hence this feature is essential when the actual end user of this API
- * is untrusted code that wouldn't have access to a lower level net API
- * anyway (e.g. web browsers). Hence this feature is low prio for most
- * mongoose use cases and thus can be disabled, e.g. when porting to a platform
- * that lacks rand().
- */
-#if MG_DISABLE_WS_RANDOM_MASK
-  mask = 0xefbeadde; /* generated with a random number generator, I swear */
-#else
-  if (sizeof(long) >= 4) {
-    mask = (uint32_t) rand();
-  } else if (sizeof(long) == 2) {
-    mask = (uint32_t) rand() << 16 | (uint32_t) rand();
+static mg_event_handler_t mg_http_get_endpoint_handler(
+    struct mg_connection *nc, struct mg_str *uri_path) {
+  struct mg_http_proto_data *pd;
+  mg_event_handler_t ret = NULL;
+  int matched, matched_max = 0;
+  struct mg_http_endpoint *ep;
+
+  if (nc == NULL) {
+    return NULL;
   }
-#endif
-  return mask;
-}
 
-static void mg_send_ws_header(struct mg_connection *nc, int op, size_t len,
-                              struct ws_mask_ctx *ctx) {
-  int header_len;
-  unsigned char header[10];
+  pd = mg_http_get_proto_data(nc);
 
-  header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f);
-  if (len < 126) {
-    header[1] = (unsigned char) len;
-    header_len = 2;
-  } else if (len < 65535) {
-    uint16_t tmp = htons((uint16_t) len);
-    header[1] = 126;
-    memcpy(&header[2], &tmp, sizeof(tmp));
-    header_len = 4;
-  } else {
-    uint32_t tmp;
-    header[1] = 127;
-    tmp = htonl((uint32_t)((uint64_t) len >> 32));
-    memcpy(&header[2], &tmp, sizeof(tmp));
-    tmp = htonl((uint32_t)(len & 0xffffffff));
-    memcpy(&header[6], &tmp, sizeof(tmp));
-    header_len = 10;
-  }
+  ep = pd->endpoints;
+  while (ep != NULL) {
+    const struct mg_str name_s = {ep->name, ep->name_len};
+    if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) {
+      if (matched > matched_max) {
+        /* Looking for the longest suitable handler */
+        ret = ep->handler;
+        matched_max = matched;
+      }
+    }
 
-  /* client connections enable masking */
-  if (nc->listener == NULL) {
-    header[1] |= 1 << 7; /* set masking flag */
-    mg_send(nc, header, header_len);
-    ctx->mask = mg_ws_random_mask();
-    mg_send(nc, &ctx->mask, sizeof(ctx->mask));
-    ctx->pos = nc->send_mbuf.len;
-  } else {
-    mg_send(nc, header, header_len);
-    ctx->pos = 0;
+    ep = ep->next;
   }
-}
 
-static void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) {
-  size_t i;
-  if (ctx->pos == 0) return;
-  for (i = 0; i < (mbuf->len - ctx->pos); i++) {
-    mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4];
-  }
+  return ret;
 }
 
-void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,
-                             size_t len) {
-  struct ws_mask_ctx ctx;
-  DBG(("%p %d %d", nc, op, (int) len));
-  mg_send_ws_header(nc, op, len, &ctx);
-  mg_send(nc, data, len);
-
-  mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
+                                          struct http_message *hm) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
 
-  if (op == WEBSOCKET_OP_CLOSE) {
-    nc->flags |= MG_F_SEND_AND_CLOSE;
+  if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) {
+    pd->endpoint_handler =
+        ev == MG_EV_HTTP_REQUEST
+            ? mg_http_get_endpoint_handler(nc->listener, &hm->uri)
+            : NULL;
   }
+  mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev,
+          hm);
 }
 
-void mg_send_websocket_framev(struct mg_connection *nc, int op,
-                              const struct mg_str *strv, int strvcnt) {
-  struct ws_mask_ctx ctx;
-  int i;
-  int len = 0;
-  for (i = 0; i < strvcnt; i++) {
-    len += strv[i].len;
-  }
+#if MG_ENABLE_HTTP_STREAMING_MULTIPART
+static void mg_http_multipart_continue(struct mg_connection *nc);
 
-  mg_send_ws_header(nc, op, len, &ctx);
+static void mg_http_multipart_begin(struct mg_connection *nc,
+                                    struct http_message *hm, int req_len);
 
-  for (i = 0; i < strvcnt; i++) {
-    mg_send(nc, strv[i].p, strv[i].len);
-  }
+#endif
 
-  mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+/*
+ * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here)
+ * If a big structure is declared in a big function, lx106 gcc will make it
+ * even bigger (round up to 4k, from 700 bytes of actual size).
+ */
+#ifdef __xtensa__
+static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
+                             struct http_message *hm) __attribute__((noinline));
 
-  if (op == WEBSOCKET_OP_CLOSE) {
-    nc->flags |= MG_F_SEND_AND_CLOSE;
-  }
+void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
+  struct http_message hm;
+  mg_http_handler2(nc, ev, ev_data, &hm);
 }
 
-void mg_printf_websocket_frame(struct mg_connection *nc, int op,
-                               const char *fmt, ...) {
-  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
-  va_list ap;
-  int len;
+static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
+                             struct http_message *hm) {
+#else  /* !__XTENSA__ */
+void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
+  struct http_message shm;
+  struct http_message *hm = &shm;
+#endif /* __XTENSA__ */
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+  struct mbuf *io = &nc->recv_mbuf;
+  int req_len;
+  const int is_req = (nc->listener != NULL);
+#if MG_ENABLE_HTTP_WEBSOCKET
+  struct mg_str *vec;
+#endif
+  if (ev == MG_EV_CLOSE) {
+#if MG_ENABLE_HTTP_STREAMING_MULTIPART
+    if (pd->mp_stream.boundary != NULL) {
+      /*
+       * Multipart message is in progress, but we get close
+       * MG_EV_HTTP_PART_END with error flag
+       */
+      struct mg_http_multipart_part mp;
+      memset(&mp, 0, sizeof(mp));
 
-  va_start(ap, fmt);
-  if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
-    mg_send_websocket_frame(nc, op, buf, len);
+      mp.status = -1;
+      mp.var_name = pd->mp_stream.var_name;
+      mp.file_name = pd->mp_stream.file_name;
+      mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
+              MG_EV_HTTP_PART_END, &mp);
+    } else
+#endif
+        if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) {
+      /*
+      * For HTTP messages without Content-Length, always send HTTP message
+      * before MG_EV_CLOSE message.
+      */
+      int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
+      hm->message.len = io->len;
+      hm->body.len = io->buf + io->len - hm->body.p;
+      mg_http_call_endpoint_handler(nc, ev2, hm);
+    }
   }
-  va_end(ap);
 
-  if (buf != mem && buf != NULL) {
-    MG_FREE(buf);
+#if MG_ENABLE_FILESYSTEM
+  if (pd->file.fp != NULL) {
+    mg_http_transfer_file_data(nc);
   }
-}
+#endif
 
-static void mg_websocket_handler(struct mg_connection *nc, int ev,
-                                 void *ev_data) {
   mg_call(nc, nc->handler, ev, ev_data);
 
-  switch (ev) {
-    case MG_EV_RECV:
-      do {
-      } while (mg_deliver_websocket_data(nc));
-      break;
-    case MG_EV_POLL:
-      /* Ping idle websocket connections */
-      {
-        time_t now = *(time_t *) ev_data;
-        if (nc->flags & MG_F_IS_WEBSOCKET &&
-            now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) {
-          mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "", 0);
-        }
-      }
-      break;
-    default:
-      break;
-  }
-}
+  if (ev == MG_EV_RECV) {
+    struct mg_str *s;
 
-#ifndef MG_EXT_SHA1
-static void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
-                           const size_t *msg_lens, uint8_t *digest) {
-  size_t i;
-  cs_sha1_ctx sha_ctx;
-  cs_sha1_init(&sha_ctx);
-  for (i = 0; i < num_msgs; i++) {
-    cs_sha1_update(&sha_ctx, msgs[i], msg_lens[i]);
-  }
-  cs_sha1_final(digest, &sha_ctx);
-}
-#else
-extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
-                           const size_t *msg_lens, uint8_t *digest);
-#endif
+#if MG_ENABLE_HTTP_STREAMING_MULTIPART
+    if (pd->mp_stream.boundary != NULL) {
+      mg_http_multipart_continue(nc);
+      return;
+    }
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
 
-static void mg_ws_handshake(struct mg_connection *nc,
-                            const struct mg_str *key) {
-  static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
-  const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic};
-  const size_t msg_lens[2] = {key->len, 36};
-  unsigned char sha[20];
-  char b64_sha[30];
+    req_len = mg_parse_http(io->buf, io->len, hm, is_req);
 
-  mg_hash_sha1_v(2, msgs, msg_lens, sha);
-  mg_base64_encode(sha, sizeof(sha), b64_sha);
-  mg_printf(nc, "%s%s%s",
-            "HTTP/1.1 101 Switching Protocols\r\n"
-            "Upgrade: websocket\r\n"
-            "Connection: Upgrade\r\n"
-            "Sec-WebSocket-Accept: ",
-            b64_sha, "\r\n\r\n");
-  DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha));
-}
+    if (req_len > 0 &&
+        (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL &&
+        mg_vcasecmp(s, "chunked") == 0) {
+      mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len);
+    }
 
-#endif /* MG_DISABLE_HTTP_WEBSOCKET */
-
-#if MG_ENABLE_FILESYSTEM
-static void mg_http_transfer_file_data(struct mg_connection *nc) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
-  char buf[MG_MAX_HTTP_SEND_MBUF];
-  size_t n = 0, to_read = 0, left = (size_t)(pd->file.cl - pd->file.sent);
-
-  if (pd->file.type == DATA_FILE) {
-    struct mbuf *io = &nc->send_mbuf;
-    if (io->len < sizeof(buf)) {
-      to_read = sizeof(buf) - io->len;
+#if MG_ENABLE_HTTP_STREAMING_MULTIPART
+    if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL &&
+        s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) {
+      mg_http_multipart_begin(nc, hm, req_len);
+      mg_http_multipart_continue(nc);
+      return;
     }
+#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
 
-    if (left > 0 && to_read > left) {
-      to_read = left;
+    /* TODO(alashkin): refactor this ifelseifelseifelseifelse */
+    if ((req_len < 0 ||
+         (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) {
+      DBG(("invalid request"));
+      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+    } else if (req_len == 0) {
+      /* Do nothing, request is not yet fully buffered */
     }
+#if MG_ENABLE_HTTP_WEBSOCKET
+    else if (nc->listener == NULL &&
+             mg_get_http_header(hm, "Sec-WebSocket-Accept")) {
+      /* We're websocket client, got handshake response from server. */
+      /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */
+      mbuf_remove(io, req_len);
+      nc->proto_handler = mg_ws_handler;
+      nc->flags |= MG_F_IS_WEBSOCKET;
+      mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+      mg_ws_handler(nc, MG_EV_RECV, ev_data);
+    } else if (nc->listener != NULL &&
+               (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
+      /* This is a websocket request. Switch protocol handlers. */
+      mbuf_remove(io, req_len);
+      nc->proto_handler = mg_ws_handler;
+      nc->flags |= MG_F_IS_WEBSOCKET;
 
-    if (to_read == 0) {
-      /* Rate limiting. send_mbuf is too full, wait until it's drained. */
-    } else if (pd->file.sent < pd->file.cl &&
-               (n = fread(buf, 1, to_read, pd->file.fp)) > 0) {
-      mg_send(nc, buf, n);
-      pd->file.sent += n;
-    } else {
-      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
-      mg_http_free_proto_data_file(&pd->file);
-    }
-  } else if (pd->file.type == DATA_PUT) {
-    struct mbuf *io = &nc->recv_mbuf;
-    size_t to_write = left <= 0 ? 0 : left < io->len ? (size_t) left : io->len;
-    size_t n = fwrite(io->buf, 1, to_write, pd->file.fp);
-    if (n > 0) {
-      mbuf_remove(io, n);
-      pd->file.sent += n;
-    }
-    if (n == 0 || pd->file.sent >= pd->file.cl) {
-      if (!pd->file.keepalive) nc->flags |= MG_F_SEND_AND_CLOSE;
-      mg_http_free_proto_data_file(&pd->file);
-    }
-  }
-#if MG_ENABLE_CGI
-  else if (pd->cgi.cgi_nc != NULL) {
-    /* This is POST data that needs to be forwarded to the CGI process */
-    if (pd->cgi.cgi_nc != NULL) {
-      mg_forward(nc, pd->cgi.cgi_nc);
-    } else {
-      nc->flags |= MG_F_SEND_AND_CLOSE;
+      /* Send handshake */
+      mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm);
+      if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
+        if (nc->send_mbuf.len == 0) {
+          mg_ws_handshake(nc, vec);
+        }
+        mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
+        mg_ws_handler(nc, MG_EV_RECV, ev_data);
+      }
     }
-  }
-#endif
-}
-#endif /* MG_ENABLE_FILESYSTEM */
-
-/*
- * Parse chunked-encoded buffer. Return 0 if the buffer is not encoded, or
- * if it's incomplete. If the chunk is fully buffered, return total number of
- * bytes in a chunk, and store data in `data`, `data_len`.
- */
-static size_t mg_http_parse_chunk(char *buf, size_t len, char **chunk_data,
-                                  size_t *chunk_len) {
-  unsigned char *s = (unsigned char *) buf;
-  size_t n = 0; /* scanned chunk length */
-  size_t i = 0; /* index in s */
-
-  /* Scan chunk length. That should be a hexadecimal number. */
-  while (i < len && isxdigit(s[i])) {
-    n *= 16;
-    n += (s[i] >= '0' && s[i] <= '9') ? s[i] - '0' : tolower(s[i]) - 'a' + 10;
-    i++;
-  }
+#endif /* MG_ENABLE_HTTP_WEBSOCKET */
+    else if (hm->message.len <= io->len) {
+      int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
 
-  /* Skip new line */
-  if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
-    return 0;
-  }
-  i += 2;
+/* Whole HTTP message is fully buffered, call event handler */
 
-  /* Record where the data is */
-  *chunk_data = (char *) s + i;
-  *chunk_len = n;
+#if MG_ENABLE_JAVASCRIPT
+      v7_val_t v1, v2, headers, req, args, res;
+      struct v7 *v7 = nc->mgr->v7;
+      const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv";
+      int i, js_callback_handled_request = 0;
 
-  /* Skip data */
-  i += n;
+      if (v7 != NULL) {
+        /* Lookup JS callback */
+        v1 = v7_get(v7, v7_get_global(v7), "Http", ~0);
+        v2 = v7_get(v7, v1, ev_name, ~0);
 
-  /* Skip new line */
-  if (i == 0 || i + 2 > len || s[i] != '\r' || s[i + 1] != '\n') {
-    return 0;
-  }
-  return i + 2;
-}
+        /* Create callback params. TODO(lsm): own/disown those */
+        args = v7_mk_array(v7);
+        req = v7_mk_object(v7);
+        headers = v7_mk_object(v7);
 
-MG_INTERNAL size_t mg_handle_chunked(struct mg_connection *nc,
-                                     struct http_message *hm, char *buf,
-                                     size_t blen) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
-  char *data;
-  size_t i, n, data_len, body_len, zero_chunk_received = 0;
-  /* Find out piece of received data that is not yet reassembled */
-  body_len = (size_t) pd->chunk.body_len;
-  assert(blen >= body_len);
+        /* Populate request object */
+        v7_set(v7, req, "method", ~0,
+               v7_mk_string(v7, hm->method.p, hm->method.len, 1));
+        v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1));
+        v7_set(v7, req, "body", ~0,
+               v7_mk_string(v7, hm->body.p, hm->body.len, 1));
+        v7_set(v7, req, "headers", ~0, headers);
+        for (i = 0; hm->header_names[i].len > 0; i++) {
+          const struct mg_str *name = &hm->header_names[i];
+          const struct mg_str *value = &hm->header_values[i];
+          v7_set(v7, headers, name->p, name->len,
+                 v7_mk_string(v7, value->p, value->len, 1));
+        }
 
-  /* Traverse all fully buffered chunks */
-  for (i = body_len;
-       (n = mg_http_parse_chunk(buf + i, blen - i, &data, &data_len)) > 0;
-       i += n) {
-    /* Collapse chunk data to the rest of HTTP body */
-    memmove(buf + body_len, data, data_len);
-    body_len += data_len;
-    hm->body.len = body_len;
+        /* Invoke callback. TODO(lsm): report errors */
+        v7_array_push(v7, args, v7_mk_foreign(v7, nc));
+        v7_array_push(v7, args, req);
+        if (v7_apply(v7, v2, V7_UNDEFINED, args, &res) == V7_OK &&
+            v7_is_truthy(v7, res)) {
+          js_callback_handled_request++;
+        }
+      }
 
-    if (data_len == 0) {
-      zero_chunk_received = 1;
-      i += n;
-      break;
+      /* If JS callback returns true, stop request processing */
+      if (js_callback_handled_request) {
+        nc->flags |= MG_F_SEND_AND_CLOSE;
+      } else {
+        mg_http_call_endpoint_handler(nc, trigger_ev, hm);
+      }
+#else
+      mg_http_call_endpoint_handler(nc, trigger_ev, hm);
+#endif
+      mbuf_remove(io, hm->message.len);
     }
   }
+  (void) pd;
+}
 
-  if (i > body_len) {
-    /* Shift unparsed content to the parsed body */
-    assert(i <= blen);
-    memmove(buf + body_len, buf + i, blen - i);
-    memset(buf + body_len + blen - i, 0, i - body_len);
-    nc->recv_mbuf.len -= i - body_len;
-    pd->chunk.body_len = body_len;
+static size_t mg_get_line_len(const char *buf, size_t buf_len) {
+  size_t len = 0;
+  while (len < buf_len && buf[len] != '\n') len++;
+  return len == buf_len ? 0 : len + 1;
+}
 
-    /* Send MG_EV_HTTP_CHUNK event */
-    nc->flags &= ~MG_F_DELETE_CHUNK;
-    mg_call(nc, nc->handler, MG_EV_HTTP_CHUNK, hm);
+#if MG_ENABLE_HTTP_STREAMING_MULTIPART
+static void mg_http_multipart_begin(struct mg_connection *nc,
+                                    struct http_message *hm, int req_len) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+  struct mg_str *ct;
+  struct mbuf *io = &nc->recv_mbuf;
 
-    /* Delete processed data if user set MG_F_DELETE_CHUNK flag */
-    if (nc->flags & MG_F_DELETE_CHUNK) {
-      memset(buf, 0, body_len);
-      memmove(buf, buf + body_len, blen - i);
-      nc->recv_mbuf.len -= body_len;
-      hm->body.len = 0;
-      pd->chunk.body_len = 0;
-    }
+  char boundary[100];
+  int boundary_len;
 
-    if (zero_chunk_received) {
-      hm->message.len = (size_t) pd->chunk.body_len + blen - i;
-    }
+  if (nc->listener == NULL) {
+    /* No streaming for replies now */
+    goto exit_mp;
   }
 
-  return body_len;
-}
+  ct = mg_get_http_header(hm, "Content-Type");
+  if (ct == NULL) {
+    /* We need more data - or it isn't multipart mesage */
+    goto exit_mp;
+  }
 
-static mg_event_handler_t mg_http_get_endpoint_handler(
-    struct mg_connection *nc, struct mg_str *uri_path) {
-  struct mg_http_proto_data *pd;
-  mg_event_handler_t ret = NULL;
-  int matched, matched_max = 0;
-  struct mg_http_endpoint *ep;
+  /* Content-type should start with "multipart" */
+  if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) {
+    goto exit_mp;
+  }
 
-  if (nc == NULL) {
-    return NULL;
+  boundary_len =
+      mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
+  if (boundary_len == 0) {
+    /*
+     * Content type is multipart, but there is no boundary,
+     * probably malformed request
+     */
+    nc->flags = MG_F_CLOSE_IMMEDIATELY;
+    DBG(("invalid request"));
+    goto exit_mp;
   }
 
-  pd = mg_http_get_proto_data(nc);
+  /* If we reach this place - that is multipart request */
 
-  ep = pd->endpoints;
-  while (ep != NULL) {
-    const struct mg_str name_s = {ep->name, ep->name_len};
-    if ((matched = mg_match_prefix_n(name_s, *uri_path)) != -1) {
-      if (matched > matched_max) {
-        /* Looking for the longest suitable handler */
-        ret = ep->handler;
-        matched_max = matched;
-      }
+  if (pd->mp_stream.boundary != NULL) {
+    /*
+     * Another streaming request was in progress,
+     * looks like protocol error
+     */
+    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
+  } else {
+    pd->mp_stream.state = MPS_BEGIN;
+    pd->mp_stream.boundary = strdup(boundary);
+    pd->mp_stream.boundary_len = strlen(boundary);
+    pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;
+
+    pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
+    if (pd->endpoint_handler == NULL) {
+      pd->endpoint_handler = nc->handler;
     }
 
-    ep = ep->next;
-  }
+    mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm);
 
-  return ret;
+    mbuf_remove(io, req_len);
+  }
+exit_mp:
+  ;
 }
 
-static void mg_http_call_endpoint_handler(struct mg_connection *nc, int ev,
-                                          struct http_message *hm) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+#define CONTENT_DISPOSITION "Content-Disposition: "
 
-  if (pd->endpoint_handler == NULL || ev == MG_EV_HTTP_REQUEST) {
-    pd->endpoint_handler =
-        ev == MG_EV_HTTP_REQUEST
-            ? mg_http_get_endpoint_handler(nc->listener, &hm->uri)
-            : NULL;
-  }
-  mg_call(nc, pd->endpoint_handler ? pd->endpoint_handler : nc->handler, ev,
-          hm);
+static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
+                                           const char *data, size_t data_len) {
+  struct mg_http_multipart_part mp;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  memset(&mp, 0, sizeof(mp));
+
+  mp.var_name = pd->mp_stream.var_name;
+  mp.file_name = pd->mp_stream.file_name;
+  mp.user_data = pd->mp_stream.user_data;
+  mp.data.p = data;
+  mp.data.len = data_len;
+  mg_call(c, pd->endpoint_handler, ev, &mp);
+  pd->mp_stream.user_data = mp.user_data;
 }
 
-#if MG_ENABLE_HTTP_STREAMING_MULTIPART
-static void mg_http_multipart_continue(struct mg_connection *nc);
+static int mg_http_multipart_got_chunk(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  struct mbuf *io = &c->recv_mbuf;
 
-static void mg_http_multipart_begin(struct mg_connection *nc,
-                                    struct http_message *hm, int req_len);
+  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf,
+                                 pd->mp_stream.prev_io_len);
+  mbuf_remove(io, pd->mp_stream.prev_io_len);
+  pd->mp_stream.prev_io_len = 0;
+  pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
 
-#endif
+  return 0;
+}
 
-/*
- * lx106 compiler has a bug (TODO(mkm) report and insert tracking bug here)
- * If a big structure is declared in a big function, lx106 gcc will make it
- * even bigger (round up to 4k, from 700 bytes of actual size).
- */
-#ifdef __xtensa__
-static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
-                             struct http_message *hm) __attribute__((noinline));
+static int mg_http_multipart_finalize(struct mg_connection *c) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
 
-void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
-  struct http_message hm;
-  mg_http_handler2(nc, ev, ev_data, &hm);
+  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
+  mg_http_free_proto_data_mp_stream(&pd->mp_stream);
+  pd->mp_stream.state = MPS_FINISHED;
+
+  return 1;
 }
 
-static void mg_http_handler2(struct mg_connection *nc, int ev, void *ev_data,
-                             struct http_message *hm) {
-#else  /* !__XTENSA__ */
-void mg_http_handler(struct mg_connection *nc, int ev, void *ev_data) {
-  struct http_message shm;
-  struct http_message *hm = &shm;
-#endif /* __XTENSA__ */
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
-  struct mbuf *io = &nc->recv_mbuf;
-  int req_len;
-  const int is_req = (nc->listener != NULL);
-#if !MG_DISABLE_HTTP_WEBSOCKET
-  struct mg_str *vec;
-#endif
-  if (ev == MG_EV_CLOSE) {
-#if MG_ENABLE_HTTP_STREAMING_MULTIPART
-    if (pd->mp_stream.boundary != NULL) {
-      /*
-       * Multipart message is in progress, but we get close
-       * MG_EV_HTTP_PART_END with error flag
-       */
-      struct mg_http_multipart_part mp;
-      memset(&mp, 0, sizeof(mp));
+static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
+  const char *boundary;
+  struct mbuf *io = &c->recv_mbuf;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
 
-      mp.status = -1;
-      mp.var_name = pd->mp_stream.var_name;
-      mp.file_name = pd->mp_stream.file_name;
-      mg_call(nc, (pd->endpoint_handler ? pd->endpoint_handler : nc->handler),
-              MG_EV_HTTP_PART_END, &mp);
-    } else
-#endif
-        if (io->len > 0 && mg_parse_http(io->buf, io->len, hm, is_req) > 0) {
-      /*
-      * For HTTP messages without Content-Length, always send HTTP message
-      * before MG_EV_CLOSE message.
-      */
-      int ev2 = is_req ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
-      hm->message.len = io->len;
-      hm->body.len = io->buf + io->len - hm->body.p;
-      mg_http_call_endpoint_handler(nc, ev2, hm);
-    }
+  if ((int) io->len < pd->mp_stream.boundary_len + 2) {
+    return 0;
   }
 
-#if MG_ENABLE_FILESYSTEM
-  if (pd->file.fp != NULL) {
-    mg_http_transfer_file_data(nc);
+  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+  if (boundary != NULL) {
+    if (io->len - (boundary - io->buf) < 4) {
+      return 0;
+    }
+    if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) {
+      pd->mp_stream.state = MPS_FINALIZE;
+    } else {
+      pd->mp_stream.state = MPS_GOT_BOUNDARY;
+    }
+  } else {
+    return 0;
   }
-#endif
-
-  mg_call(nc, nc->handler, ev, ev_data);
-
-  if (ev == MG_EV_RECV) {
-    struct mg_str *s;
 
-#if MG_ENABLE_HTTP_STREAMING_MULTIPART
-    if (pd->mp_stream.boundary != NULL) {
-      mg_http_multipart_continue(nc);
-      return;
-    }
-#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
+  return 1;
+}
 
-    req_len = mg_parse_http(io->buf, io->len, hm, is_req);
+static int mg_http_multipart_process_boundary(struct mg_connection *c) {
+  int data_size;
+  const char *boundary, *block_begin;
+  struct mbuf *io = &c->recv_mbuf;
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
+  char file_name[100], var_name[100];
+  int line_len;
+  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
+  block_begin = boundary + pd->mp_stream.boundary_len + 2;
+  data_size = io->len - (block_begin - io->buf);
 
-    if (req_len > 0 &&
-        (s = mg_get_http_header(hm, "Transfer-Encoding")) != NULL &&
-        mg_vcasecmp(s, "chunked") == 0) {
-      mg_handle_chunked(nc, hm, io->buf + req_len, io->len - req_len);
-    }
+  while (data_size > 0 &&
+         (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
+    if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
+        mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
+                    sizeof(CONTENT_DISPOSITION) - 1) == 0) {
+      struct mg_str header;
 
-#if MG_ENABLE_HTTP_STREAMING_MULTIPART
-    if (req_len > 0 && (s = mg_get_http_header(hm, "Content-Type")) != NULL &&
-        s->len >= 9 && strncmp(s->p, "multipart", 9) == 0) {
-      mg_http_multipart_begin(nc, hm, req_len);
-      mg_http_multipart_continue(nc);
-      return;
+      header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
+      header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
+      mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
+      mg_http_parse_header(&header, "filename", file_name,
+                           sizeof(file_name) - 2);
+      block_begin += line_len;
+      data_size -= line_len;
+      continue;
     }
-#endif /* MG_ENABLE_HTTP_STREAMING_MULTIPART */
 
-    /* TODO(alashkin): refactor this ifelseifelseifelseifelse */
-    if ((req_len < 0 ||
-         (req_len == 0 && io->len >= MG_MAX_HTTP_REQUEST_SIZE))) {
-      DBG(("invalid request"));
-      nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-    } else if (req_len == 0) {
-      /* Do nothing, request is not yet fully buffered */
-    }
-#if !MG_DISABLE_HTTP_WEBSOCKET
-    else if (nc->listener == NULL &&
-             mg_get_http_header(hm, "Sec-WebSocket-Accept")) {
-      /* We're websocket client, got handshake response from server. */
-      /* TODO(lsm): check the validity of accept Sec-WebSocket-Accept */
-      mbuf_remove(io, req_len);
-      nc->proto_handler = mg_websocket_handler;
-      nc->flags |= MG_F_IS_WEBSOCKET;
-      mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
-      mg_websocket_handler(nc, MG_EV_RECV, ev_data);
-    } else if (nc->listener != NULL &&
-               (vec = mg_get_http_header(hm, "Sec-WebSocket-Key")) != NULL) {
-      /* This is a websocket request. Switch protocol handlers. */
-      mbuf_remove(io, req_len);
-      nc->proto_handler = mg_websocket_handler;
-      nc->flags |= MG_F_IS_WEBSOCKET;
+    if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
+      mbuf_remove(io, block_begin - io->buf + 2);
 
-      /* Send handshake */
-      mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, hm);
-      if (!(nc->flags & MG_F_CLOSE_IMMEDIATELY)) {
-        if (nc->send_mbuf.len == 0) {
-          mg_ws_handshake(nc, vec);
-        }
-        mg_call(nc, nc->handler, MG_EV_WEBSOCKET_HANDSHAKE_DONE, NULL);
-        mg_websocket_handler(nc, MG_EV_RECV, ev_data);
+      if (pd->mp_stream.processing_part != 0) {
+        mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
       }
+
+      free((void *) pd->mp_stream.file_name);
+      pd->mp_stream.file_name = strdup(file_name);
+      free((void *) pd->mp_stream.var_name);
+      pd->mp_stream.var_name = strdup(var_name);
+
+      mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
+      pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
+      pd->mp_stream.processing_part++;
+      return 1;
     }
-#endif /* MG_DISABLE_HTTP_WEBSOCKET */
-    else if (hm->message.len <= io->len) {
-      int trigger_ev = nc->listener ? MG_EV_HTTP_REQUEST : MG_EV_HTTP_REPLY;
 
-/* Whole HTTP message is fully buffered, call event handler */
+    block_begin += line_len;
+  }
 
-#if MG_ENABLE_JAVASCRIPT
-      v7_val_t v1, v2, headers, req, args, res;
-      struct v7 *v7 = nc->mgr->v7;
-      const char *ev_name = trigger_ev == MG_EV_HTTP_REPLY ? "onsnd" : "onrcv";
-      int i, js_callback_handled_request = 0;
+  pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
 
-      if (v7 != NULL) {
-        /* Lookup JS callback */
-        v1 = v7_get(v7, v7_get_global(v7), "Http", ~0);
-        v2 = v7_get(v7, v1, ev_name, ~0);
-
-        /* Create callback params. TODO(lsm): own/disown those */
-        args = v7_mk_array(v7);
-        req = v7_mk_object(v7);
-        headers = v7_mk_object(v7);
-
-        /* Populate request object */
-        v7_set(v7, req, "method", ~0,
-               v7_mk_string(v7, hm->method.p, hm->method.len, 1));
-        v7_set(v7, req, "uri", ~0, v7_mk_string(v7, hm->uri.p, hm->uri.len, 1));
-        v7_set(v7, req, "body", ~0,
-               v7_mk_string(v7, hm->body.p, hm->body.len, 1));
-        v7_set(v7, req, "headers", ~0, headers);
-        for (i = 0; hm->header_names[i].len > 0; i++) {
-          const struct mg_str *name = &hm->header_names[i];
-          const struct mg_str *value = &hm->header_values[i];
-          v7_set(v7, headers, name->p, name->len,
-                 v7_mk_string(v7, value->p, value->len, 1));
-        }
-
-        /* Invoke callback. TODO(lsm): report errors */
-        v7_array_push(v7, args, v7_mk_foreign(v7, nc));
-        v7_array_push(v7, args, req);
-        if (v7_apply(v7, v2, V7_UNDEFINED, args, &res) == V7_OK &&
-            v7_is_truthy(v7, res)) {
-          js_callback_handled_request++;
-        }
-      }
-
-      /* If JS callback returns true, stop request processing */
-      if (js_callback_handled_request) {
-        nc->flags |= MG_F_SEND_AND_CLOSE;
-      } else {
-        mg_http_call_endpoint_handler(nc, trigger_ev, hm);
-      }
-#else
-      mg_http_call_endpoint_handler(nc, trigger_ev, hm);
-#endif
-      mbuf_remove(io, hm->message.len);
-    }
-  }
-  (void) pd;
-}
-
-static size_t mg_get_line_len(const char *buf, size_t buf_len) {
-  size_t len = 0;
-  while (len < buf_len && buf[len] != '\n') len++;
-  return len == buf_len ? 0 : len + 1;
-}
-
-#if MG_ENABLE_HTTP_STREAMING_MULTIPART
-static void mg_http_multipart_begin(struct mg_connection *nc,
-                                    struct http_message *hm, int req_len) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
-  struct mg_str *ct;
-  struct mbuf *io = &nc->recv_mbuf;
-
-  char boundary[100];
-  int boundary_len;
-
-  if (nc->listener == NULL) {
-    /* No streaming for replies now */
-    goto exit_mp;
-  }
-
-  ct = mg_get_http_header(hm, "Content-Type");
-  if (ct == NULL) {
-    /* We need more data - or it isn't multipart mesage */
-    goto exit_mp;
-  }
-
-  /* Content-type should start with "multipart" */
-  if (ct->len < 9 || strncmp(ct->p, "multipart", 9) != 0) {
-    goto exit_mp;
-  }
-
-  boundary_len =
-      mg_http_parse_header(ct, "boundary", boundary, sizeof(boundary));
-  if (boundary_len == 0) {
-    /*
-     * Content type is multipart, but there is no boundary,
-     * probably malformed request
-     */
-    nc->flags = MG_F_CLOSE_IMMEDIATELY;
-    DBG(("invalid request"));
-    goto exit_mp;
-  }
-
-  /* If we reach this place - that is multipart request */
-
-  if (pd->mp_stream.boundary != NULL) {
-    /*
-     * Another streaming request was in progress,
-     * looks like protocol error
-     */
-    nc->flags |= MG_F_CLOSE_IMMEDIATELY;
-  } else {
-    pd->mp_stream.state = MPS_BEGIN;
-    pd->mp_stream.boundary = strdup(boundary);
-    pd->mp_stream.boundary_len = strlen(boundary);
-    pd->mp_stream.var_name = pd->mp_stream.file_name = NULL;
-
-    pd->endpoint_handler = mg_http_get_endpoint_handler(nc->listener, &hm->uri);
-    if (pd->endpoint_handler == NULL) {
-      pd->endpoint_handler = nc->handler;
-    }
-
-    mg_call(nc, pd->endpoint_handler, MG_EV_HTTP_MULTIPART_REQUEST, hm);
-
-    mbuf_remove(io, req_len);
-  }
-exit_mp:
-  ;
-}
-
-#define CONTENT_DISPOSITION "Content-Disposition: "
-
-static void mg_http_multipart_call_handler(struct mg_connection *c, int ev,
-                                           const char *data, size_t data_len) {
-  struct mg_http_multipart_part mp;
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  memset(&mp, 0, sizeof(mp));
-
-  mp.var_name = pd->mp_stream.var_name;
-  mp.file_name = pd->mp_stream.file_name;
-  mp.user_data = pd->mp_stream.user_data;
-  mp.data.p = data;
-  mp.data.len = data_len;
-  mg_call(c, pd->endpoint_handler, ev, &mp);
-  pd->mp_stream.user_data = mp.user_data;
-}
-
-static int mg_http_multipart_got_chunk(struct mg_connection *c) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  struct mbuf *io = &c->recv_mbuf;
-
-  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_DATA, io->buf,
-                                 pd->mp_stream.prev_io_len);
-  mbuf_remove(io, pd->mp_stream.prev_io_len);
-  pd->mp_stream.prev_io_len = 0;
-  pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
-
-  return 0;
-}
-
-static int mg_http_multipart_finalize(struct mg_connection *c) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-
-  mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
-  mg_http_free_proto_data_mp_stream(&pd->mp_stream);
-  pd->mp_stream.state = MPS_FINISHED;
-
-  return 1;
-}
-
-static int mg_http_multipart_wait_for_boundary(struct mg_connection *c) {
-  const char *boundary;
-  struct mbuf *io = &c->recv_mbuf;
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-
-  if ((int) io->len < pd->mp_stream.boundary_len + 2) {
-    return 0;
-  }
-
-  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
-  if (boundary != NULL) {
-    if (io->len - (boundary - io->buf) < 4) {
-      return 0;
-    }
-    if (memcmp(boundary + pd->mp_stream.boundary_len, "--", 2) == 0) {
-      pd->mp_stream.state = MPS_FINALIZE;
-    } else {
-      pd->mp_stream.state = MPS_GOT_BOUNDARY;
-    }
-  } else {
-    return 0;
-  }
-
-  return 1;
-}
-
-static int mg_http_multipart_process_boundary(struct mg_connection *c) {
-  int data_size;
-  const char *boundary, *block_begin;
-  struct mbuf *io = &c->recv_mbuf;
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
-  char file_name[100], var_name[100];
-  int line_len;
-  boundary = c_strnstr(io->buf, pd->mp_stream.boundary, io->len);
-  block_begin = boundary + pd->mp_stream.boundary_len + 2;
-  data_size = io->len - (block_begin - io->buf);
-
-  while (data_size > 0 &&
-         (line_len = mg_get_line_len(block_begin, data_size)) != 0) {
-    if (line_len > (int) sizeof(CONTENT_DISPOSITION) &&
-        mg_ncasecmp(block_begin, CONTENT_DISPOSITION,
-                    sizeof(CONTENT_DISPOSITION) - 1) == 0) {
-      struct mg_str header;
-
-      header.p = block_begin + sizeof(CONTENT_DISPOSITION) - 1;
-      header.len = line_len - sizeof(CONTENT_DISPOSITION) - 1;
-      mg_http_parse_header(&header, "name", var_name, sizeof(var_name) - 2);
-      mg_http_parse_header(&header, "filename", file_name,
-                           sizeof(file_name) - 2);
-      block_begin += line_len;
-      data_size -= line_len;
-      continue;
-    }
-
-    if (line_len == 2 && mg_ncasecmp(block_begin, "\r\n", 2) == 0) {
-      mbuf_remove(io, block_begin - io->buf + 2);
-
-      if (pd->mp_stream.processing_part != 0) {
-        mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_END, NULL, 0);
-      }
-
-      free((void *) pd->mp_stream.file_name);
-      pd->mp_stream.file_name = strdup(file_name);
-      free((void *) pd->mp_stream.var_name);
-      pd->mp_stream.var_name = strdup(var_name);
-
-      mg_http_multipart_call_handler(c, MG_EV_HTTP_PART_BEGIN, NULL, 0);
-      pd->mp_stream.state = MPS_WAITING_FOR_CHUNK;
-      pd->mp_stream.processing_part++;
-      return 1;
-    }
-
-    block_begin += line_len;
-  }
-
-  pd->mp_stream.state = MPS_WAITING_FOR_BOUNDARY;
-
-  return 0;
-}
+  return 0;
+}
 
 static int mg_http_multipart_continue_wait_for_chunk(struct mg_connection *c) {
   struct mg_http_proto_data *pd = mg_http_get_proto_data(c);
@@ -5261,48 +4973,6 @@ void mg_set_protocol_http_websocket(struct mg_connection *nc) {
   nc->proto_handler = mg_http_handler;
 }
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
-
-void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,
-                                  const char *host, const char *protocol,
-                                  const char *extra_headers) {
-  char key[25];
-  uint32_t nonce[4];
-  nonce[0] = mg_ws_random_mask();
-  nonce[1] = mg_ws_random_mask();
-  nonce[2] = mg_ws_random_mask();
-  nonce[3] = mg_ws_random_mask();
-  mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key);
-
-  mg_printf(nc,
-            "GET %s HTTP/1.1\r\n"
-            "Upgrade: websocket\r\n"
-            "Connection: Upgrade\r\n"
-            "Sec-WebSocket-Version: 13\r\n"
-            "Sec-WebSocket-Key: %s\r\n",
-            path, key);
-
-  /* TODO(mkm): take default hostname from http proto data if host == NULL */
-  if (host != MG_WS_NO_HOST_HEADER_MAGIC) {
-    mg_printf(nc, "Host: %s\r\n", host);
-  }
-  if (protocol != NULL) {
-    mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol);
-  }
-  if (extra_headers != NULL) {
-    mg_printf(nc, "%s", extra_headers);
-  }
-  mg_printf(nc, "\r\n");
-}
-
-void mg_send_websocket_handshake(struct mg_connection *nc, const char *path,
-                                 const char *extra_headers) {
-  mg_send_websocket_handshake2(nc, path, MG_WS_NO_HOST_HEADER_MAGIC, NULL,
-                               extra_headers);
-}
-
-#endif /* MG_DISABLE_HTTP_WEBSOCKET */
-
 void mg_send_response_line_s(struct mg_connection *nc, int status_code,
                              const struct mg_str extra_headers) {
   const char *status_message = "OK";
@@ -5390,200 +5060,6 @@ static void mg_http_send_error(struct mg_connection *nc, int code,
   mg_send(nc, reason, strlen(reason));
   nc->flags |= MG_F_SEND_AND_CLOSE;
 }
-#if !MG_DISABLE_SSI
-static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
-                             const char *path, FILE *fp, int include_level,
-                             const struct mg_serve_http_opts *opts);
-
-static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
-  char buf[BUFSIZ];
-  size_t n;
-  while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
-    mg_send(nc, buf, n);
-  }
-}
-
-static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm,
-                              const char *ssi, char *tag, int include_level,
-                              const struct mg_serve_http_opts *opts) {
-  char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p;
-  FILE *fp;
-
-  /*
-   * sscanf() is safe here, since send_ssi_file() also uses buffer
-   * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
-   */
-  if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
-    /* File name is relative to the webserver root */
-    snprintf(path, sizeof(path), "%s/%s", opts->document_root, file_name);
-  } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
-    /*
-     * File name is relative to the webserver working directory
-     * or it is absolute system path
-     */
-    snprintf(path, sizeof(path), "%s", file_name);
-  } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
-             sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
-    /* File name is relative to the currect document */
-    snprintf(path, sizeof(path), "%s", ssi);
-    if ((p = strrchr(path, DIRSEP)) != NULL) {
-      p[1] = '\0';
-    }
-    snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name);
-  } else {
-    mg_printf(nc, "Bad SSI #include: [%s]", tag);
-    return;
-  }
-
-  if ((fp = fopen(path, "rb")) == NULL) {
-    mg_printf(nc, "SSI include error: fopen(%s): %s", path,
-              strerror(mg_get_errno()));
-  } else {
-    mg_set_close_on_exec(fileno(fp));
-    if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) >
-        0) {
-      mg_send_ssi_file(nc, hm, path, fp, include_level + 1, opts);
-    } else {
-      mg_send_file_data(nc, fp);
-    }
-    fclose(fp);
-  }
-}
-
-#if !MG_DISABLE_POPEN
-static void do_ssi_exec(struct mg_connection *nc, char *tag) {
-  char cmd[BUFSIZ];
-  FILE *fp;
-
-  if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
-    mg_printf(nc, "Bad SSI #exec: [%s]", tag);
-  } else if ((fp = popen(cmd, "r")) == NULL) {
-    mg_printf(nc, "Cannot SSI #exec: [%s]: %s", cmd, strerror(mg_get_errno()));
-  } else {
-    mg_send_file_data(nc, fp);
-    pclose(fp);
-  }
-}
-#endif /* !MG_DISABLE_POPEN */
-
-/*
- * SSI directive has the following format:
- * <!--#directive parameter=value parameter=value -->
- */
-static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
-                             const char *path, FILE *fp, int include_level,
-                             const struct mg_serve_http_opts *opts) {
-  static const struct mg_str btag = MG_MK_STR("<!--#");
-  static const struct mg_str d_include = MG_MK_STR("include");
-  static const struct mg_str d_call = MG_MK_STR("call");
-#if !MG_DISABLE_POPEN
-  static const struct mg_str d_exec = MG_MK_STR("exec");
-#endif
-  char buf[BUFSIZ], *p = buf + btag.len; /* p points to SSI directive */
-  int ch, len, in_ssi_tag;
-
-  if (include_level > 10) {
-    mg_printf(nc, "SSI #include level is too deep (%s)", path);
-    return;
-  }
-
-  in_ssi_tag = len = 0;
-  while ((ch = fgetc(fp)) != EOF) {
-    if (in_ssi_tag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
-      size_t i = len - 2;
-      in_ssi_tag = 0;
-
-      /* Trim closing --> */
-      buf[i--] = '\0';
-      while (i > 0 && buf[i] == ' ') {
-        buf[i--] = '\0';
-      }
-
-      /* Handle known SSI directives */
-      if (memcmp(p, d_include.p, d_include.len) == 0) {
-        mg_do_ssi_include(nc, hm, path, p + d_include.len + 1, include_level,
-                          opts);
-      } else if (memcmp(p, d_call.p, d_call.len) == 0) {
-        struct mg_ssi_call_ctx cctx;
-        memset(&cctx, 0, sizeof(cctx));
-        cctx.req = hm;
-        cctx.file = mg_mk_str(path);
-        cctx.arg = mg_mk_str(p + d_call.len + 1);
-        mg_call(nc, NULL, MG_EV_SSI_CALL,
-                (void *) cctx.arg.p); /* NUL added above */
-        mg_call(nc, NULL, MG_EV_SSI_CALL_CTX, &cctx);
-#if !MG_DISABLE_POPEN
-      } else if (memcmp(p, d_exec.p, d_exec.len) == 0) {
-        do_ssi_exec(nc, p + d_exec.len + 1);
-#endif
-      } else {
-        /* Silently ignore unknown SSI directive. */
-      }
-      len = 0;
-    } else if (ch == '<') {
-      in_ssi_tag = 1;
-      if (len > 0) {
-        mg_send(nc, buf, (size_t) len);
-      }
-      len = 0;
-      buf[len++] = ch & 0xff;
-    } else if (in_ssi_tag) {
-      if (len == (int) btag.len && memcmp(buf, btag.p, btag.len) != 0) {
-        /* Not an SSI tag */
-        in_ssi_tag = 0;
-      } else if (len == (int) sizeof(buf) - 2) {
-        mg_printf(nc, "%s: SSI tag is too large", path);
-        len = 0;
-      }
-      buf[len++] = ch & 0xff;
-    } else {
-      buf[len++] = ch & 0xff;
-      if (len == (int) sizeof(buf)) {
-        mg_send(nc, buf, (size_t) len);
-        len = 0;
-      }
-    }
-  }
-
-  /* Send the rest of buffered data */
-  if (len > 0) {
-    mg_send(nc, buf, (size_t) len);
-  }
-}
-
-static void mg_handle_ssi_request(struct mg_connection *nc,
-                                  struct http_message *hm, const char *path,
-                                  const struct mg_serve_http_opts *opts) {
-  FILE *fp;
-  struct mg_str mime_type;
-  DBG(("%p %s", nc, path));
-
-  if ((fp = fopen(path, "rb")) == NULL) {
-    mg_http_send_error(nc, 404, NULL);
-  } else {
-    mg_set_close_on_exec(fileno(fp));
-
-    mime_type = mg_get_mime_type(path, "text/plain", opts);
-    mg_send_response_line(nc, 200, opts->extra_headers);
-    mg_printf(nc,
-              "Content-Type: %.*s\r\n"
-              "Connection: close\r\n\r\n",
-              (int) mime_type.len, mime_type.p);
-    mg_send_ssi_file(nc, hm, path, fp, 0, opts);
-    fclose(fp);
-    nc->flags |= MG_F_SEND_AND_CLOSE;
-  }
-}
-#else
-static void mg_handle_ssi_request(struct mg_connection *nc,
-                                  struct http_message *hm, const char *path,
-                                  const struct mg_serve_http_opts *opts) {
-  (void) path;
-  (void) hm;
-  (void) opts;
-  mg_http_send_error(nc, 500, "SSI disabled");
-}
-#endif /* MG_DISABLE_SSI */
 
 static void mg_http_construct_etag(char *buf, size_t buf_len,
                                    const cs_stat_t *st) {
@@ -5710,10 +5186,12 @@ void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,
 static void mg_http_serve_file2(struct mg_connection *nc, const char *path,
                                 struct http_message *hm,
                                 struct mg_serve_http_opts *opts) {
+#if MG_ENABLE_HTTP_SSI
   if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) > 0) {
     mg_handle_ssi_request(nc, hm, path, opts);
     return;
   }
+#endif
   mg_http_serve_file(nc, hm, path, mg_get_mime_type(path, "text/plain", opts),
                      mg_mk_str(opts->extra_headers));
 }
@@ -6538,11 +6016,11 @@ MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path,
                                opts->per_directory_auth_file, 0)) {
     mg_http_send_digest_auth_request(nc, opts->auth_domain);
   } else if (is_cgi) {
-#if MG_ENABLE_CGI
+#if MG_ENABLE_HTTP_CGI
     mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts);
 #else
     mg_http_send_error(nc, 501, NULL);
-#endif /* MG_DISABLE_CGI */
+#endif /* MG_ENABLE_HTTP_CGI */
   } else if ((!exists ||
               mg_is_file_hidden(path, opts, 0 /* specials are ok */)) &&
              !mg_is_creation_request(hm)) {
@@ -6781,35 +6259,6 @@ struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
                              post_data);
 }
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
-struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
-                                        mg_event_handler_t ev_handler,
-                                        struct mg_connect_opts opts,
-                                        const char *url, const char *protocol,
-                                        const char *extra_headers) {
-  char *addr = NULL;
-  const char *path = NULL;
-  struct mg_connection *nc = mg_connect_http_base(
-      mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr);
-
-  if (nc != NULL) {
-    mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers);
-  }
-
-  MG_FREE(addr);
-  return nc;
-}
-
-struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
-                                    mg_event_handler_t ev_handler,
-                                    const char *url, const char *protocol,
-                                    const char *extra_headers) {
-  struct mg_connect_opts opts;
-  memset(&opts, 0, sizeof(opts));
-  return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers);
-}
-#endif /* MG_DISABLE_HTTP_WEBSOCKET */
-
 size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name,
                           size_t var_name_len, char *file_name,
                           size_t file_name_len, const char **data,
@@ -6865,7 +6314,7 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
   pd->endpoints = new_ep;
 }
 
-#endif /* MG_DISABLE_HTTP */
+#endif /* MG_ENABLE_HTTP */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/http_cgi.c"
 #endif
@@ -6874,7 +6323,15 @@ void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path,
  * All rights reserved
  */
 
-#if !MG_DISABLE_HTTP && MG_ENABLE_CGI
+#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI
+
+#ifndef MG_MAX_CGI_ENVIR_VARS
+#define MG_MAX_CGI_ENVIR_VARS 64
+#endif
+
+#ifndef MG_ENV_EXPORT_TO_CGI
+#define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
+#endif
 
 /*
  * This structure helps to create an environment for the spawned CGI program.
@@ -7350,37 +6807,233 @@ MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) {
   }
 }
 
-#endif /* MG_ENABLE_HTTP && MG_ENABLE_CGI */
+#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_CGI */
 #ifdef MG_MODULE_LINES
-#line 1 "mongoose/src/http_webdav.c"
+#line 1 "mongoose/src/http_ssi.c"
 #endif
 /*
  * Copyright (c) 2014-2016 Cesanta Software Limited
  * All rights reserved
  */
 
-#if !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV
+#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_SSI && MG_ENABLE_FILESYSTEM
 
-MG_INTERNAL int mg_is_dav_request(const struct mg_str *s) {
-  static const char *methods[] = {
-    "PUT",
-    "DELETE",
-    "MKCOL",
-    "PROPFIND",
-    "MOVE"
-#if MG_ENABLE_FAKE_DAVLOCK
-    ,
-    "LOCK",
-    "UNLOCK"
-#endif
-  };
-  size_t i;
+static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
+                             const char *path, FILE *fp, int include_level,
+                             const struct mg_serve_http_opts *opts);
 
-  for (i = 0; i < ARRAY_SIZE(methods); i++) {
-    if (mg_vcmp(s, methods[i]) == 0) {
-      return 1;
-    }
-  }
+static void mg_send_file_data(struct mg_connection *nc, FILE *fp) {
+  char buf[BUFSIZ];
+  size_t n;
+  while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
+    mg_send(nc, buf, n);
+  }
+}
+
+static void mg_do_ssi_include(struct mg_connection *nc, struct http_message *hm,
+                              const char *ssi, char *tag, int include_level,
+                              const struct mg_serve_http_opts *opts) {
+  char file_name[BUFSIZ], path[MAX_PATH_SIZE], *p;
+  FILE *fp;
+
+  /*
+   * sscanf() is safe here, since send_ssi_file() also uses buffer
+   * of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
+   */
+  if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
+    /* File name is relative to the webserver root */
+    snprintf(path, sizeof(path), "%s/%s", opts->document_root, file_name);
+  } else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
+    /*
+     * File name is relative to the webserver working directory
+     * or it is absolute system path
+     */
+    snprintf(path, sizeof(path), "%s", file_name);
+  } else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
+             sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
+    /* File name is relative to the currect document */
+    snprintf(path, sizeof(path), "%s", ssi);
+    if ((p = strrchr(path, DIRSEP)) != NULL) {
+      p[1] = '\0';
+    }
+    snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s", file_name);
+  } else {
+    mg_printf(nc, "Bad SSI #include: [%s]", tag);
+    return;
+  }
+
+  if ((fp = fopen(path, "rb")) == NULL) {
+    mg_printf(nc, "SSI include error: fopen(%s): %s", path,
+              strerror(mg_get_errno()));
+  } else {
+    mg_set_close_on_exec(fileno(fp));
+    if (mg_match_prefix(opts->ssi_pattern, strlen(opts->ssi_pattern), path) >
+        0) {
+      mg_send_ssi_file(nc, hm, path, fp, include_level + 1, opts);
+    } else {
+      mg_send_file_data(nc, fp);
+    }
+    fclose(fp);
+  }
+}
+
+#if !MG_DISABLE_POPEN
+static void do_ssi_exec(struct mg_connection *nc, char *tag) {
+  char cmd[BUFSIZ];
+  FILE *fp;
+
+  if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
+    mg_printf(nc, "Bad SSI #exec: [%s]", tag);
+  } else if ((fp = popen(cmd, "r")) == NULL) {
+    mg_printf(nc, "Cannot SSI #exec: [%s]: %s", cmd, strerror(mg_get_errno()));
+  } else {
+    mg_send_file_data(nc, fp);
+    pclose(fp);
+  }
+}
+#endif /* !MG_DISABLE_POPEN */
+
+/*
+ * SSI directive has the following format:
+ * <!--#directive parameter=value parameter=value -->
+ */
+static void mg_send_ssi_file(struct mg_connection *nc, struct http_message *hm,
+                             const char *path, FILE *fp, int include_level,
+                             const struct mg_serve_http_opts *opts) {
+  static const struct mg_str btag = MG_MK_STR("<!--#");
+  static const struct mg_str d_include = MG_MK_STR("include");
+  static const struct mg_str d_call = MG_MK_STR("call");
+#if !MG_DISABLE_POPEN
+  static const struct mg_str d_exec = MG_MK_STR("exec");
+#endif
+  char buf[BUFSIZ], *p = buf + btag.len; /* p points to SSI directive */
+  int ch, len, in_ssi_tag;
+
+  if (include_level > 10) {
+    mg_printf(nc, "SSI #include level is too deep (%s)", path);
+    return;
+  }
+
+  in_ssi_tag = len = 0;
+  while ((ch = fgetc(fp)) != EOF) {
+    if (in_ssi_tag && ch == '>' && buf[len - 1] == '-' && buf[len - 2] == '-') {
+      size_t i = len - 2;
+      in_ssi_tag = 0;
+
+      /* Trim closing --> */
+      buf[i--] = '\0';
+      while (i > 0 && buf[i] == ' ') {
+        buf[i--] = '\0';
+      }
+
+      /* Handle known SSI directives */
+      if (memcmp(p, d_include.p, d_include.len) == 0) {
+        mg_do_ssi_include(nc, hm, path, p + d_include.len + 1, include_level,
+                          opts);
+      } else if (memcmp(p, d_call.p, d_call.len) == 0) {
+        struct mg_ssi_call_ctx cctx;
+        memset(&cctx, 0, sizeof(cctx));
+        cctx.req = hm;
+        cctx.file = mg_mk_str(path);
+        cctx.arg = mg_mk_str(p + d_call.len + 1);
+        mg_call(nc, NULL, MG_EV_SSI_CALL,
+                (void *) cctx.arg.p); /* NUL added above */
+        mg_call(nc, NULL, MG_EV_SSI_CALL_CTX, &cctx);
+#if !MG_DISABLE_POPEN
+      } else if (memcmp(p, d_exec.p, d_exec.len) == 0) {
+        do_ssi_exec(nc, p + d_exec.len + 1);
+#endif
+      } else {
+        /* Silently ignore unknown SSI directive. */
+      }
+      len = 0;
+    } else if (ch == '<') {
+      in_ssi_tag = 1;
+      if (len > 0) {
+        mg_send(nc, buf, (size_t) len);
+      }
+      len = 0;
+      buf[len++] = ch & 0xff;
+    } else if (in_ssi_tag) {
+      if (len == (int) btag.len && memcmp(buf, btag.p, btag.len) != 0) {
+        /* Not an SSI tag */
+        in_ssi_tag = 0;
+      } else if (len == (int) sizeof(buf) - 2) {
+        mg_printf(nc, "%s: SSI tag is too large", path);
+        len = 0;
+      }
+      buf[len++] = ch & 0xff;
+    } else {
+      buf[len++] = ch & 0xff;
+      if (len == (int) sizeof(buf)) {
+        mg_send(nc, buf, (size_t) len);
+        len = 0;
+      }
+    }
+  }
+
+  /* Send the rest of buffered data */
+  if (len > 0) {
+    mg_send(nc, buf, (size_t) len);
+  }
+}
+
+MG_INTERNAL void mg_handle_ssi_request(struct mg_connection *nc,
+                                       struct http_message *hm,
+                                       const char *path,
+                                       const struct mg_serve_http_opts *opts) {
+  FILE *fp;
+  struct mg_str mime_type;
+  DBG(("%p %s", nc, path));
+
+  if ((fp = fopen(path, "rb")) == NULL) {
+    mg_http_send_error(nc, 404, NULL);
+  } else {
+    mg_set_close_on_exec(fileno(fp));
+
+    mime_type = mg_get_mime_type(path, "text/plain", opts);
+    mg_send_response_line(nc, 200, opts->extra_headers);
+    mg_printf(nc,
+              "Content-Type: %.*s\r\n"
+              "Connection: close\r\n\r\n",
+              (int) mime_type.len, mime_type.p);
+    mg_send_ssi_file(nc, hm, path, fp, 0, opts);
+    fclose(fp);
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  }
+}
+
+#endif /* MG_ENABLE_HTTP_SSI && MG_ENABLE_HTTP && MG_ENABLE_FILESYSTEM */
+#ifdef MG_MODULE_LINES
+#line 1 "mongoose/src/http_webdav.c"
+#endif
+/*
+ * Copyright (c) 2014-2016 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV
+
+MG_INTERNAL int mg_is_dav_request(const struct mg_str *s) {
+  static const char *methods[] = {
+    "PUT",
+    "DELETE",
+    "MKCOL",
+    "PROPFIND",
+    "MOVE"
+#if MG_ENABLE_FAKE_DAVLOCK
+    ,
+    "LOCK",
+    "UNLOCK"
+#endif
+  };
+  size_t i;
+
+  for (i = 0; i < ARRAY_SIZE(methods); i++) {
+    if (mg_vcmp(s, methods[i]) == 0) {
+      return 1;
+    }
+  }
 
   return 0;
 }
@@ -7480,148 +7133,522 @@ MG_INTERNAL void mg_handle_lock(struct mg_connection *nc, const char *path) {
 }
 #endif
 
-MG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path,
-                                 struct http_message *hm) {
-  int status_code = 500;
-  if (hm->body.len != (size_t) ~0 && hm->body.len > 0) {
-    status_code = 415;
-  } else if (!mg_mkdir(path, 0755)) {
-    status_code = 201;
-  } else if (errno == EEXIST) {
-    status_code = 405;
-  } else if (errno == EACCES) {
-    status_code = 403;
-  } else if (errno == ENOENT) {
-    status_code = 409;
-  } else {
-    status_code = 500;
+MG_INTERNAL void mg_handle_mkcol(struct mg_connection *nc, const char *path,
+                                 struct http_message *hm) {
+  int status_code = 500;
+  if (hm->body.len != (size_t) ~0 && hm->body.len > 0) {
+    status_code = 415;
+  } else if (!mg_mkdir(path, 0755)) {
+    status_code = 201;
+  } else if (errno == EEXIST) {
+    status_code = 405;
+  } else if (errno == EACCES) {
+    status_code = 403;
+  } else if (errno == ENOENT) {
+    status_code = 409;
+  } else {
+    status_code = 500;
+  }
+  mg_http_send_error(nc, status_code, NULL);
+}
+
+static int mg_remove_directory(const struct mg_serve_http_opts *opts,
+                               const char *dir) {
+  char path[MAX_PATH_SIZE];
+  struct dirent *dp;
+  cs_stat_t st;
+  DIR *dirp;
+
+  if ((dirp = opendir(dir)) == NULL) return 0;
+
+  while ((dp = readdir(dirp)) != NULL) {
+    if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {
+      continue;
+    }
+    snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
+    mg_stat(path, &st);
+    if (S_ISDIR(st.st_mode)) {
+      mg_remove_directory(opts, path);
+    } else {
+      remove(path);
+    }
+  }
+  closedir(dirp);
+  rmdir(dir);
+
+  return 1;
+}
+
+MG_INTERNAL void mg_handle_move(struct mg_connection *c,
+                                const struct mg_serve_http_opts *opts,
+                                const char *path, struct http_message *hm) {
+  const struct mg_str *dest = mg_get_http_header(hm, "Destination");
+  if (dest == NULL) {
+    mg_http_send_error(c, 411, NULL);
+  } else {
+    const char *p = (char *) memchr(dest->p, '/', dest->len);
+    if (p != NULL && p[1] == '/' &&
+        (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) {
+      char buf[MAX_PATH_SIZE];
+      snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root,
+               (int) (dest->p + dest->len - p), p);
+      if (rename(path, buf) == 0) {
+        mg_http_send_error(c, 200, NULL);
+      } else {
+        mg_http_send_error(c, 418, NULL);
+      }
+    } else {
+      mg_http_send_error(c, 500, NULL);
+    }
+  }
+}
+
+MG_INTERNAL void mg_handle_delete(struct mg_connection *nc,
+                                  const struct mg_serve_http_opts *opts,
+                                  const char *path) {
+  cs_stat_t st;
+  if (mg_stat(path, &st) != 0) {
+    mg_http_send_error(nc, 404, NULL);
+  } else if (S_ISDIR(st.st_mode)) {
+    mg_remove_directory(opts, path);
+    mg_http_send_error(nc, 204, NULL);
+  } else if (remove(path) == 0) {
+    mg_http_send_error(nc, 204, NULL);
+  } else {
+    mg_http_send_error(nc, 423, NULL);
+  }
+}
+
+/* Return -1 on error, 1 on success. */
+static int mg_create_itermediate_directories(const char *path) {
+  const char *s;
+
+  /* Create intermediate directories if they do not exist */
+  for (s = path + 1; *s != '\0'; s++) {
+    if (*s == '/') {
+      char buf[MAX_PATH_SIZE];
+      cs_stat_t st;
+      snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path);
+      buf[sizeof(buf) - 1] = '\0';
+      if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) {
+        return -1;
+      }
+    }
+  }
+
+  return 1;
+}
+
+MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,
+                               struct http_message *hm) {
+  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
+  cs_stat_t st;
+  const struct mg_str *cl_hdr = mg_get_http_header(hm, "Content-Length");
+  int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+
+  mg_http_free_proto_data_file(&pd->file);
+  if ((rc = mg_create_itermediate_directories(path)) == 0) {
+    mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
+  } else if (rc == -1) {
+    mg_http_send_error(nc, 500, NULL);
+  } else if (cl_hdr == NULL) {
+    mg_http_send_error(nc, 411, NULL);
+  } else if ((pd->file.fp = fopen(path, "w+b")) == NULL) {
+    mg_http_send_error(nc, 500, NULL);
+  } else {
+    const struct mg_str *range_hdr = mg_get_http_header(hm, "Content-Range");
+    int64_t r1 = 0, r2 = 0;
+    pd->file.type = DATA_PUT;
+    mg_set_close_on_exec(fileno(pd->file.fp));
+    pd->file.cl = to64(cl_hdr->p);
+    if (range_hdr != NULL &&
+        mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) {
+      status_code = 206;
+      fseeko(pd->file.fp, r1, SEEK_SET);
+      pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1;
+    }
+    mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
+    /* Remove HTTP request from the mbuf, leave only payload */
+    mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len);
+    mg_http_transfer_file_data(nc);
+  }
+}
+
+#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */
+#ifdef MG_MODULE_LINES
+#line 1 "mongoose/src/http_websocket.c"
+#endif
+/*
+ * Copyright (c) 2014 Cesanta Software Limited
+ * All rights reserved
+ */
+
+#if MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET
+
+#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS
+#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5
+#endif
+
+#define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1)
+
+static int mg_is_ws_fragment(unsigned char flags) {
+  return (flags & 0x80) == 0 || (flags & 0x0f) == 0;
+}
+
+static int mg_is_ws_first_fragment(unsigned char flags) {
+  return (flags & 0x80) == 0 && (flags & 0x0f) != 0;
+}
+
+static void mg_handle_incoming_websocket_frame(struct mg_connection *nc,
+                                               struct websocket_message *wsm) {
+  if (wsm->flags & 0x8) {
+    mg_call(nc, nc->handler, MG_EV_WEBSOCKET_CONTROL_FRAME, wsm);
+  } else {
+    mg_call(nc, nc->handler, MG_EV_WEBSOCKET_FRAME, wsm);
+  }
+}
+
+static int mg_deliver_websocket_data(struct mg_connection *nc) {
+  /* Using unsigned char *, cause of integer arithmetic below */
+  uint64_t i, data_len = 0, frame_len = 0, buf_len = nc->recv_mbuf.len, len,
+              mask_len = 0, header_len = 0;
+  unsigned char *p = (unsigned char *) nc->recv_mbuf.buf, *buf = p,
+                *e = p + buf_len;
+  unsigned *sizep = (unsigned *) &p[1]; /* Size ptr for defragmented frames */
+  int ok, reass = buf_len > 0 && mg_is_ws_fragment(p[0]) &&
+                  !(nc->flags & MG_F_WEBSOCKET_NO_DEFRAG);
+
+  /* If that's a continuation frame that must be reassembled, handle it */
+  if (reass && !mg_is_ws_first_fragment(p[0]) &&
+      buf_len >= 1 + sizeof(*sizep) && buf_len >= 1 + sizeof(*sizep) + *sizep) {
+    buf += 1 + sizeof(*sizep) + *sizep;
+    buf_len -= 1 + sizeof(*sizep) + *sizep;
+  }
+
+  if (buf_len >= 2) {
+    len = buf[1] & 127;
+    mask_len = buf[1] & 128 ? 4 : 0;
+    if (len < 126 && buf_len >= mask_len) {
+      data_len = len;
+      header_len = 2 + mask_len;
+    } else if (len == 126 && buf_len >= 4 + mask_len) {
+      header_len = 4 + mask_len;
+      data_len = ntohs(*(uint16_t *) &buf[2]);
+    } else if (buf_len >= 10 + mask_len) {
+      header_len = 10 + mask_len;
+      data_len = (((uint64_t) ntohl(*(uint32_t *) &buf[2])) << 32) +
+                 ntohl(*(uint32_t *) &buf[6]);
+    }
+  }
+
+  frame_len = header_len + data_len;
+  ok = frame_len > 0 && frame_len <= buf_len;
+
+  if (ok) {
+    struct websocket_message wsm;
+
+    wsm.size = (size_t) data_len;
+    wsm.data = buf + header_len;
+    wsm.flags = buf[0];
+
+    /* Apply mask if necessary */
+    if (mask_len > 0) {
+      for (i = 0; i < data_len; i++) {
+        buf[i + header_len] ^= (buf + header_len - mask_len)[i % 4];
+      }
+    }
+
+    if (reass) {
+      /* On first fragmented frame, nullify size */
+      if (mg_is_ws_first_fragment(wsm.flags)) {
+        mbuf_resize(&nc->recv_mbuf, nc->recv_mbuf.size + sizeof(*sizep));
+        p[0] &= ~0x0f; /* Next frames will be treated as continuation */
+        buf = p + 1 + sizeof(*sizep);
+        *sizep = 0; /* TODO(lsm): fix. this can stomp over frame data */
+      }
+
+      /* Append this frame to the reassembled buffer */
+      memmove(buf, wsm.data, e - wsm.data);
+      (*sizep) += wsm.size;
+      nc->recv_mbuf.len -= wsm.data - buf;
+
+      /* On last fragmented frame - call user handler and remove data */
+      if (wsm.flags & 0x80) {
+        wsm.data = p + 1 + sizeof(*sizep);
+        wsm.size = *sizep;
+        mg_handle_incoming_websocket_frame(nc, &wsm);
+        mbuf_remove(&nc->recv_mbuf, 1 + sizeof(*sizep) + *sizep);
+      }
+    } else {
+      /* TODO(lsm): properly handle OOB control frames during defragmentation */
+      mg_handle_incoming_websocket_frame(nc, &wsm);
+      mbuf_remove(&nc->recv_mbuf, (size_t) frame_len); /* Cleanup frame */
+    }
+
+    /* If client closes, close too */
+    if ((buf[0] & 0x0f) == WEBSOCKET_OP_CLOSE) {
+      nc->flags |= MG_F_SEND_AND_CLOSE;
+    }
+  }
+
+  return ok;
+}
+
+struct ws_mask_ctx {
+  size_t pos; /* zero means unmasked */
+  uint32_t mask;
+};
+
+static uint32_t mg_ws_random_mask(void) {
+  uint32_t mask;
+/*
+ * The spec requires WS client to generate hard to
+ * guess mask keys. From RFC6455, Section 5.3:
+ *
+ * The unpredictability of the masking key is essential to prevent
+ * authors of malicious applications from selecting the bytes that appear on
+ * the wire.
+ *
+ * Hence this feature is essential when the actual end user of this API
+ * is untrusted code that wouldn't have access to a lower level net API
+ * anyway (e.g. web browsers). Hence this feature is low prio for most
+ * mongoose use cases and thus can be disabled, e.g. when porting to a platform
+ * that lacks rand().
+ */
+#if MG_DISABLE_WS_RANDOM_MASK
+  mask = 0xefbeadde; /* generated with a random number generator, I swear */
+#else
+  if (sizeof(long) >= 4) {
+    mask = (uint32_t) rand();
+  } else if (sizeof(long) == 2) {
+    mask = (uint32_t) rand() << 16 | (uint32_t) rand();
+  }
+#endif
+  return mask;
+}
+
+static void mg_send_ws_header(struct mg_connection *nc, int op, size_t len,
+                              struct ws_mask_ctx *ctx) {
+  int header_len;
+  unsigned char header[10];
+
+  header[0] = (op & WEBSOCKET_DONT_FIN ? 0x0 : 0x80) + (op & 0x0f);
+  if (len < 126) {
+    header[1] = (unsigned char) len;
+    header_len = 2;
+  } else if (len < 65535) {
+    uint16_t tmp = htons((uint16_t) len);
+    header[1] = 126;
+    memcpy(&header[2], &tmp, sizeof(tmp));
+    header_len = 4;
+  } else {
+    uint32_t tmp;
+    header[1] = 127;
+    tmp = htonl((uint32_t)((uint64_t) len >> 32));
+    memcpy(&header[2], &tmp, sizeof(tmp));
+    tmp = htonl((uint32_t)(len & 0xffffffff));
+    memcpy(&header[6], &tmp, sizeof(tmp));
+    header_len = 10;
+  }
+
+  /* client connections enable masking */
+  if (nc->listener == NULL) {
+    header[1] |= 1 << 7; /* set masking flag */
+    mg_send(nc, header, header_len);
+    ctx->mask = mg_ws_random_mask();
+    mg_send(nc, &ctx->mask, sizeof(ctx->mask));
+    ctx->pos = nc->send_mbuf.len;
+  } else {
+    mg_send(nc, header, header_len);
+    ctx->pos = 0;
+  }
+}
+
+static void mg_ws_mask_frame(struct mbuf *mbuf, struct ws_mask_ctx *ctx) {
+  size_t i;
+  if (ctx->pos == 0) return;
+  for (i = 0; i < (mbuf->len - ctx->pos); i++) {
+    mbuf->buf[ctx->pos + i] ^= ((char *) &ctx->mask)[i % 4];
+  }
+}
+
+void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,
+                             size_t len) {
+  struct ws_mask_ctx ctx;
+  DBG(("%p %d %d", nc, op, (int) len));
+  mg_send_ws_header(nc, op, len, &ctx);
+  mg_send(nc, data, len);
+
+  mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+
+  if (op == WEBSOCKET_OP_CLOSE) {
+    nc->flags |= MG_F_SEND_AND_CLOSE;
   }
-  mg_http_send_error(nc, status_code, NULL);
 }
 
-static int mg_remove_directory(const struct mg_serve_http_opts *opts,
-                               const char *dir) {
-  char path[MAX_PATH_SIZE];
-  struct dirent *dp;
-  cs_stat_t st;
-  DIR *dirp;
+void mg_send_websocket_framev(struct mg_connection *nc, int op,
+                              const struct mg_str *strv, int strvcnt) {
+  struct ws_mask_ctx ctx;
+  int i;
+  int len = 0;
+  for (i = 0; i < strvcnt; i++) {
+    len += strv[i].len;
+  }
 
-  if ((dirp = opendir(dir)) == NULL) return 0;
+  mg_send_ws_header(nc, op, len, &ctx);
 
-  while ((dp = readdir(dirp)) != NULL) {
-    if (mg_is_file_hidden((const char *) dp->d_name, opts, 1)) {
-      continue;
-    }
-    snprintf(path, sizeof(path), "%s%c%s", dir, '/', dp->d_name);
-    mg_stat(path, &st);
-    if (S_ISDIR(st.st_mode)) {
-      mg_remove_directory(opts, path);
-    } else {
-      remove(path);
-    }
+  for (i = 0; i < strvcnt; i++) {
+    mg_send(nc, strv[i].p, strv[i].len);
   }
-  closedir(dirp);
-  rmdir(dir);
 
-  return 1;
+  mg_ws_mask_frame(&nc->send_mbuf, &ctx);
+
+  if (op == WEBSOCKET_OP_CLOSE) {
+    nc->flags |= MG_F_SEND_AND_CLOSE;
+  }
 }
 
-MG_INTERNAL void mg_handle_move(struct mg_connection *c,
-                                const struct mg_serve_http_opts *opts,
-                                const char *path, struct http_message *hm) {
-  const struct mg_str *dest = mg_get_http_header(hm, "Destination");
-  if (dest == NULL) {
-    mg_http_send_error(c, 411, NULL);
-  } else {
-    const char *p = (char *) memchr(dest->p, '/', dest->len);
-    if (p != NULL && p[1] == '/' &&
-        (p = (char *) memchr(p + 2, '/', dest->p + dest->len - p)) != NULL) {
-      char buf[MAX_PATH_SIZE];
-      snprintf(buf, sizeof(buf), "%s%.*s", opts->dav_document_root,
-               (int) (dest->p + dest->len - p), p);
-      if (rename(path, buf) == 0) {
-        mg_http_send_error(c, 200, NULL);
-      } else {
-        mg_http_send_error(c, 418, NULL);
+void mg_printf_websocket_frame(struct mg_connection *nc, int op,
+                               const char *fmt, ...) {
+  char mem[MG_VPRINTF_BUFFER_SIZE], *buf = mem;
+  va_list ap;
+  int len;
+
+  va_start(ap, fmt);
+  if ((len = mg_avprintf(&buf, sizeof(mem), fmt, ap)) > 0) {
+    mg_send_websocket_frame(nc, op, buf, len);
+  }
+  va_end(ap);
+
+  if (buf != mem && buf != NULL) {
+    MG_FREE(buf);
+  }
+}
+
+MG_INTERNAL void mg_ws_handler(struct mg_connection *nc, int ev,
+                               void *ev_data) {
+  mg_call(nc, nc->handler, ev, ev_data);
+
+  switch (ev) {
+    case MG_EV_RECV:
+      do {
+      } while (mg_deliver_websocket_data(nc));
+      break;
+    case MG_EV_POLL:
+      /* Ping idle websocket connections */
+      {
+        time_t now = *(time_t *) ev_data;
+        if (nc->flags & MG_F_IS_WEBSOCKET &&
+            now > nc->last_io_time + MG_WEBSOCKET_PING_INTERVAL_SECONDS) {
+          mg_send_websocket_frame(nc, WEBSOCKET_OP_PING, "", 0);
+        }
       }
-    } else {
-      mg_http_send_error(c, 500, NULL);
-    }
+      break;
+    default:
+      break;
   }
 }
 
-MG_INTERNAL void mg_handle_delete(struct mg_connection *nc,
-                                  const struct mg_serve_http_opts *opts,
-                                  const char *path) {
-  cs_stat_t st;
-  if (mg_stat(path, &st) != 0) {
-    mg_http_send_error(nc, 404, NULL);
-  } else if (S_ISDIR(st.st_mode)) {
-    mg_remove_directory(opts, path);
-    mg_http_send_error(nc, 204, NULL);
-  } else if (remove(path) == 0) {
-    mg_http_send_error(nc, 204, NULL);
-  } else {
-    mg_http_send_error(nc, 423, NULL);
+#ifndef MG_EXT_SHA1
+static void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
+                           const size_t *msg_lens, uint8_t *digest) {
+  size_t i;
+  cs_sha1_ctx sha_ctx;
+  cs_sha1_init(&sha_ctx);
+  for (i = 0; i < num_msgs; i++) {
+    cs_sha1_update(&sha_ctx, msgs[i], msg_lens[i]);
   }
+  cs_sha1_final(digest, &sha_ctx);
 }
+#else
+extern void mg_hash_sha1_v(size_t num_msgs, const uint8_t *msgs[],
+                           const size_t *msg_lens, uint8_t *digest);
+#endif
 
-/* Return -1 on error, 1 on success. */
-static int mg_create_itermediate_directories(const char *path) {
-  const char *s;
+MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc,
+                                 const struct mg_str *key) {
+  static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+  const uint8_t *msgs[2] = {(const uint8_t *) key->p, (const uint8_t *) magic};
+  const size_t msg_lens[2] = {key->len, 36};
+  unsigned char sha[20];
+  char b64_sha[30];
 
-  /* Create intermediate directories if they do not exist */
-  for (s = path + 1; *s != '\0'; s++) {
-    if (*s == '/') {
-      char buf[MAX_PATH_SIZE];
-      cs_stat_t st;
-      snprintf(buf, sizeof(buf), "%.*s", (int) (s - path), path);
-      buf[sizeof(buf) - 1] = '\0';
-      if (mg_stat(buf, &st) != 0 && mg_mkdir(buf, 0755) != 0) {
-        return -1;
-      }
-    }
+  mg_hash_sha1_v(2, msgs, msg_lens, sha);
+  mg_base64_encode(sha, sizeof(sha), b64_sha);
+  mg_printf(nc, "%s%s%s",
+            "HTTP/1.1 101 Switching Protocols\r\n"
+            "Upgrade: websocket\r\n"
+            "Connection: Upgrade\r\n"
+            "Sec-WebSocket-Accept: ",
+            b64_sha, "\r\n\r\n");
+  DBG(("%p %.*s %s", nc, (int) key->len, key->p, b64_sha));
+}
+
+void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,
+                                  const char *host, const char *protocol,
+                                  const char *extra_headers) {
+  char key[25];
+  uint32_t nonce[4];
+  nonce[0] = mg_ws_random_mask();
+  nonce[1] = mg_ws_random_mask();
+  nonce[2] = mg_ws_random_mask();
+  nonce[3] = mg_ws_random_mask();
+  mg_base64_encode((unsigned char *) &nonce, sizeof(nonce), key);
+
+  mg_printf(nc,
+            "GET %s HTTP/1.1\r\n"
+            "Upgrade: websocket\r\n"
+            "Connection: Upgrade\r\n"
+            "Sec-WebSocket-Version: 13\r\n"
+            "Sec-WebSocket-Key: %s\r\n",
+            path, key);
+
+  /* TODO(mkm): take default hostname from http proto data if host == NULL */
+  if (host != MG_WS_NO_HOST_HEADER_MAGIC) {
+    mg_printf(nc, "Host: %s\r\n", host);
+  }
+  if (protocol != NULL) {
+    mg_printf(nc, "Sec-WebSocket-Protocol: %s\r\n", protocol);
+  }
+  if (extra_headers != NULL) {
+    mg_printf(nc, "%s", extra_headers);
   }
+  mg_printf(nc, "\r\n");
+}
 
-  return 1;
+void mg_send_websocket_handshake(struct mg_connection *nc, const char *path,
+                                 const char *extra_headers) {
+  mg_send_websocket_handshake2(nc, path, MG_WS_NO_HOST_HEADER_MAGIC, NULL,
+                               extra_headers);
 }
 
-MG_INTERNAL void mg_handle_put(struct mg_connection *nc, const char *path,
-                               struct http_message *hm) {
-  struct mg_http_proto_data *pd = mg_http_get_proto_data(nc);
-  cs_stat_t st;
-  const struct mg_str *cl_hdr = mg_get_http_header(hm, "Content-Length");
-  int rc, status_code = mg_stat(path, &st) == 0 ? 200 : 201;
+struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr,
+                                        mg_event_handler_t ev_handler,
+                                        struct mg_connect_opts opts,
+                                        const char *url, const char *protocol,
+                                        const char *extra_headers) {
+  char *addr = NULL;
+  const char *path = NULL;
+  struct mg_connection *nc = mg_connect_http_base(
+      mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr);
 
-  mg_http_free_proto_data_file(&pd->file);
-  if ((rc = mg_create_itermediate_directories(path)) == 0) {
-    mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
-  } else if (rc == -1) {
-    mg_http_send_error(nc, 500, NULL);
-  } else if (cl_hdr == NULL) {
-    mg_http_send_error(nc, 411, NULL);
-  } else if ((pd->file.fp = fopen(path, "w+b")) == NULL) {
-    mg_http_send_error(nc, 500, NULL);
-  } else {
-    const struct mg_str *range_hdr = mg_get_http_header(hm, "Content-Range");
-    int64_t r1 = 0, r2 = 0;
-    pd->file.type = DATA_PUT;
-    mg_set_close_on_exec(fileno(pd->file.fp));
-    pd->file.cl = to64(cl_hdr->p);
-    if (range_hdr != NULL &&
-        mg_http_parse_range_header(range_hdr, &r1, &r2) > 0) {
-      status_code = 206;
-      fseeko(pd->file.fp, r1, SEEK_SET);
-      pd->file.cl = r2 > r1 ? r2 - r1 + 1 : pd->file.cl - r1;
-    }
-    mg_printf(nc, "HTTP/1.1 %d OK\r\nContent-Length: 0\r\n\r\n", status_code);
-    /* Remove HTTP request from the mbuf, leave only payload */
-    mbuf_remove(&nc->recv_mbuf, hm->message.len - hm->body.len);
-    mg_http_transfer_file_data(nc);
+  if (nc != NULL) {
+    mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers);
   }
+
+  MG_FREE(addr);
+  return nc;
 }
 
-#endif /* !MG_DISABLE_HTTP && MG_ENABLE_HTTP_WEBDAV */
+struct mg_connection *mg_connect_ws(struct mg_mgr *mgr,
+                                    mg_event_handler_t ev_handler,
+                                    const char *url, const char *protocol,
+                                    const char *extra_headers) {
+  struct mg_connect_opts opts;
+  memset(&opts, 0, sizeof(opts));
+  return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers);
+}
+#endif /* MG_ENABLE_HTTP && MG_ENABLE_HTTP_WEBSOCKET */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/util.c"
 #endif
diff --git a/mongoose.h b/mongoose.h
index d38e522487906f7564e4a953c81aaee8d4de3218..ffc574a6a95168f4f83381bd15c5484e19913f41 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -1024,7 +1024,7 @@ const char *strerror();
  * WinCE lacks a lot of used in CGI API functions
  * TODO(alaskin): look for wce_xxxx alternatives
  */
-#define MG_ENABLE_CGI 0
+#define MG_ENABLE_HTTP_CGI 0
 
 #endif /* CS_PLATFORM == CS_P_WINCE */
 #endif /* CS_COMMON_PLATFORMS_PLATFORM_WINCE_H_ */
@@ -1381,18 +1381,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
 #define MG_DISABLE_HTTP_DIGEST_AUTH 0
 #endif
 
-#ifndef MG_DISABLE_HTTP
-#define MG_DISABLE_HTTP 0
-#endif
-
 #ifndef MG_DISABLE_HTTP_KEEP_ALIVE
 #define MG_DISABLE_HTTP_KEEP_ALIVE 0
 #endif
 
-#ifndef MG_DISABLE_HTTP_WEBSOCKET
-#define MG_DISABLE_HTTP_WEBSOCKET 0
-#endif
-
 #ifndef MG_DISABLE_PFS
 #define MG_DISABLE_PFS 0
 #endif
@@ -1413,10 +1405,6 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
 #define MG_DISABLE_SOCKETPAIR 0
 #endif
 
-#ifndef MG_DISABLE_SSI
-#define MG_DISABLE_SSI 0
-#endif
-
 #ifndef MG_DISABLE_SYNC_RESOLVER
 #define MG_DISABLE_SYNC_RESOLVER 0
 #endif
@@ -1425,8 +1413,13 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
 #define MG_DISABLE_WS_RANDOM_MASK 0
 #endif
 
-#ifndef MG_ENABLE_CGI
-#define MG_ENABLE_CGI (CS_PLATFORM == CS_P_UNIX || CS_PLATFORM == CS_P_WINDOWS)
+#ifndef MG_ENABLE_HTTP
+#define MG_ENABLE_HTTP 1
+#endif
+
+#ifndef MG_ENABLE_HTTP_CGI
+#define MG_ENABLE_HTTP_CGI \
+  (CS_PLATFORM == CS_P_UNIX || CS_PLATFORM == CS_P_WINDOWS)
 #endif
 
 #ifndef MG_ENABLE_COAP
@@ -1461,6 +1454,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
 #define MG_ENABLE_HEXDUMP CS_ENABLE_STDIO
 #endif
 
+#ifndef MG_ENABLE_HTTP_SSI
+#define MG_ENABLE_HTTP_SSI MG_ENABLE_FILESYSTEM
+#endif
+
 #ifndef MG_ENABLE_HTTP_STREAMING_MULTIPART
 #define MG_ENABLE_HTTP_STREAMING_MULTIPART 0
 #endif
@@ -1469,6 +1466,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen);
 #define MG_ENABLE_HTTP_WEBDAV 0
 #endif
 
+#ifndef MG_ENABLE_HTTP_WEBSOCKET
+#define MG_ENABLE_HTTP_WEBSOCKET 1
+#endif
+
 #ifndef MG_ENABLE_IPV6
 #define MG_ENABLE_IPV6 0
 #endif
@@ -2452,6 +2453,8 @@ int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
 #ifndef CS_MONGOOSE_SRC_HTTP_H_
 #define CS_MONGOOSE_SRC_HTTP_H_
 
+#if MG_ENABLE_HTTP
+
 /* Amalgamated: #include "mongoose/src/net.h" */
 
 #ifdef __cplusplus
@@ -2478,22 +2481,10 @@ extern "C" {
 #define MG_MAX_HTTP_SEND_MBUF 1024
 #endif
 
-#ifndef MG_WEBSOCKET_PING_INTERVAL_SECONDS
-#define MG_WEBSOCKET_PING_INTERVAL_SECONDS 5
-#endif
-
 #ifndef MG_CGI_ENVIRONMENT_SIZE
 #define MG_CGI_ENVIRONMENT_SIZE 8192
 #endif
 
-#ifndef MG_MAX_CGI_ENVIR_VARS
-#define MG_MAX_CGI_ENVIR_VARS 64
-#endif
-
-#ifndef MG_ENV_EXPORT_TO_CGI
-#define MG_ENV_EXPORT_TO_CGI "MONGOOSE_CGI"
-#endif
-
 /* HTTP message */
 struct http_message {
   struct mg_str message; /* Whole message: request line + headers + body */
@@ -2525,12 +2516,14 @@ struct http_message {
   struct mg_str body; /* Zero-length for requests with no body */
 };
 
+#if MG_ENABLE_HTTP_WEBSOCKET
 /* WebSocket message */
 struct websocket_message {
   unsigned char *data;
   size_t size;
   unsigned char flags;
 };
+#endif
 
 /* HTTP multipart part */
 struct mg_http_multipart_part {
@@ -2555,7 +2548,7 @@ struct mg_ssi_call_ctx {
 #define MG_EV_SSI_CALL 105     /* char * */
 #define MG_EV_SSI_CALL_CTX 106 /* struct mg_ssi_call_ctx * */
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
+#if MG_ENABLE_HTTP_WEBSOCKET
 #define MG_EV_WEBSOCKET_HANDSHAKE_REQUEST 111 /* NULL */
 #define MG_EV_WEBSOCKET_HANDSHAKE_DONE 112    /* NULL */
 #define MG_EV_WEBSOCKET_FRAME 113             /* struct websocket_message * */
@@ -2615,7 +2608,7 @@ struct mg_ssi_call_ctx {
  */
 void mg_set_protocol_http_websocket(struct mg_connection *nc);
 
-#if !MG_DISABLE_HTTP_WEBSOCKET
+#if MG_ENABLE_HTTP_WEBSOCKET
 /*
  * Send websocket handshake to the server.
  *
@@ -2721,7 +2714,6 @@ void mg_send_websocket_framev(struct mg_connection *nc, int op_and_flags,
  */
 void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,
                                const char *fmt, ...);
-#endif /* MG_DISABLE_HTTP_WEBSOCKET */
 
 /* Websocket opcodes, from http://tools.ietf.org/html/rfc6455 */
 #define WEBSOCKET_OP_CONTINUE 0
@@ -2745,6 +2737,8 @@ void mg_printf_websocket_frame(struct mg_connection *nc, int op_and_flags,
  */
 #define WEBSOCKET_DONT_FIN 0x100
 
+#endif /* MG_ENABLE_HTTP_WEBSOCKET */
+
 /*
  * Decodes a URL-encoded string.
  *
@@ -2761,6 +2755,9 @@ int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
+
+#endif /* MG_ENABLE_HTTP */
+
 #endif /* CS_MONGOOSE_SRC_HTTP_H_ */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/http_server.h"
@@ -2772,6 +2769,8 @@ int mg_url_decode(const char *src, int src_len, char *dst, int dst_len,
 #ifndef CS_MONGOOSE_SRC_HTTP_SERVER_H_
 #define CS_MONGOOSE_SRC_HTTP_SERVER_H_
 
+#if MG_ENABLE_HTTP
+
 #ifdef __cplusplus
 extern "C" {
 #endif /* __cplusplus */
@@ -3229,6 +3228,9 @@ void mg_printf_html_escape(struct mg_connection *nc, const char *fmt, ...);
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
+
+#endif /* MG_ENABLE_HTTP */
+
 #endif /* CS_MONGOOSE_SRC_HTTP_SERVER_H_ */
 #ifdef MG_MODULE_LINES
 #line 1 "mongoose/src/http_client.h"