From ecbf79135fd78c5e6554590f2981140e120e5664 Mon Sep 17 00:00:00 2001
From: Sergey Lyubka <valenok@gmail.com>
Date: Sat, 28 Sep 2013 11:00:54 +0100
Subject: [PATCH] Refactored API, returned back to event-based handlers. Upload
 and Websocket API simplified

---
 build/main.c         |  12 +-
 examples/chat.c      |  19 +--
 examples/hello.c     |  54 ++++----
 examples/post.c      |  60 ++++----
 examples/upload.c    |  73 +++++-----
 examples/websocket.c |  56 +++++---
 mongoose.c           | 323 ++++++++++++++++++++-----------------------
 mongoose.h           |  75 +++++-----
 test/unit_test.c     | 228 +++++++++++-------------------
 9 files changed, 410 insertions(+), 490 deletions(-)

diff --git a/build/main.c b/build/main.c
index 2e8bc5ba4..e0a29ba8f 100644
--- a/build/main.c
+++ b/build/main.c
@@ -271,9 +271,10 @@ static void init_server_name(void) {
            mg_version());
 }
 
-static int log_message(const struct mg_connection *conn, const char *message) {
-  (void) conn;
-  printf("%s\n", message);
+static int event_handler(struct mg_event *event) {
+  if (event->type == MG_EVENT_LOG) {
+    printf("%s\n", (const char *) event->event_param);
+  }
   return 0;
 }
 
@@ -341,7 +342,6 @@ static void set_absolute_path(char *options[], const char *option_name,
 }
 
 static void start_mongoose(int argc, char *argv[]) {
-  struct mg_callbacks callbacks;
   char *options[MAX_OPTIONS];
   int i;
 
@@ -385,9 +385,7 @@ static void start_mongoose(int argc, char *argv[]) {
   signal(SIGINT, signal_handler);
 
   // Start Mongoose
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.log_message = &log_message;
-  ctx = mg_start(&callbacks, NULL, (const char **) options);
+  ctx = mg_start((const char **) options, event_handler, NULL);
   for (i = 0; options[i] != NULL; i++) {
     free(options[i]);
   }
diff --git a/examples/chat.c b/examples/chat.c
index e39e89139..0afb5c0da 100644
--- a/examples/chat.c
+++ b/examples/chat.c
@@ -326,9 +326,12 @@ static void redirect_to_ssl(struct mg_connection *conn,
   }
 }
 
-static int begin_request_handler(struct mg_connection *conn) {
-  const struct mg_request_info *request_info = mg_get_request_info(conn);
-  int processed = 1;
+static int event_handler(struct mg_event *event) {
+  struct mg_request_info *request_info = event->request_info;
+  struct mg_connection *conn = event->conn;
+  int result = 1;
+
+  if (event->type != MG_REQUEST_BEGIN) return 0;
 
   if (!request_info->is_ssl) {
     redirect_to_ssl(conn, request_info);
@@ -343,9 +346,10 @@ static int begin_request_handler(struct mg_connection *conn) {
   } else {
     // No suitable handler found, mark as not processed. Mongoose will
     // try to serve the request.
-    processed = 0;
+    result = 0;
   }
-  return processed;
+
+  return result;
 }
 
 static const char *options[] = {
@@ -357,7 +361,6 @@ static const char *options[] = {
 };
 
 int main(void) {
-  struct mg_callbacks callbacks;
   struct mg_context *ctx;
 
   // Initialize random number generator. It will be used later on for
@@ -365,9 +368,7 @@ int main(void) {
   srand((unsigned) time(0));
 
   // Setup and start Mongoose
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.begin_request = begin_request_handler;
-  if ((ctx = mg_start(&callbacks, NULL, options)) == NULL) {
+  if ((ctx = mg_start(options, event_handler, NULL)) == NULL) {
     printf("%s\n", "Cannot start chat server, fatal exit");
     exit(EXIT_FAILURE);
   }
diff --git a/examples/hello.c b/examples/hello.c
index 680a0122f..b516d7eb5 100644
--- a/examples/hello.c
+++ b/examples/hello.c
@@ -3,42 +3,42 @@
 #include "mongoose.h"
 
 // This function will be called by mongoose on every new request.
-static int begin_request_handler(struct mg_connection *conn) {
-  const struct mg_request_info *request_info = mg_get_request_info(conn);
-  char content[100];
-
-  // Prepare the message we're going to send
-  int content_length = snprintf(content, sizeof(content),
-                                "Hello from mongoose! Remote port: %d",
-                                request_info->remote_port);
-
-  // Send HTTP reply to the client
-  mg_printf(conn,
-            "HTTP/1.1 200 OK\r\n"
-            "Content-Type: text/plain\r\n"
-            "Content-Length: %d\r\n"        // Always set Content-Length
-            "\r\n"
-            "%s",
-            content_length, content);
-
-  // Returning non-zero tells mongoose that our function has replied to
-  // the client, and mongoose should not send client any more data.
-  return 1;
+static int event_handler(struct mg_event *event) {
+
+  if (event->type == MG_REQUEST_BEGIN) {
+    char content[100];
+
+    // Prepare the message we're going to send
+    int content_length = snprintf(content, sizeof(content),
+        "Hello from mongoose! Requested: [%s] [%s]",
+        event->request_info->request_method, event->request_info->uri);
+
+    // Send HTTP reply to the client
+    mg_printf(event->conn,
+        "HTTP/1.1 200 OK\r\n"
+        "Content-Type: text/plain\r\n"
+        "Content-Length: %d\r\n"        // Always set Content-Length
+        "\r\n"
+        "%s",
+        content_length, content);
+
+    // Returning non-zero tells mongoose that our function has replied to
+    // the client, and mongoose should not send client any more data.
+    return 1;
+  }
+
+  // We do not handle any other event
+  return 0;
 }
 
 int main(void) {
   struct mg_context *ctx;
-  struct mg_callbacks callbacks;
 
   // List of options. Last element must be NULL.
   const char *options[] = {"listening_ports", "8080", NULL};
 
-  // Prepare callbacks structure. We have only one callback, the rest are NULL.
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.begin_request = begin_request_handler;
-
   // Start the web server.
-  ctx = mg_start(&callbacks, NULL, options);
+  ctx = mg_start(options, &event_handler, NULL);
 
   // Wait until user hits "enter". Server is running in separate thread.
   // Navigating to http://localhost:8080 will invoke begin_request_handler().
diff --git a/examples/post.c b/examples/post.c
index 1c0a476c7..fe3418666 100644
--- a/examples/post.c
+++ b/examples/post.c
@@ -10,45 +10,47 @@ static const char *html_form =
   "<input type=\"submit\" />"
   "</form></body></html>";
 
-static int begin_request_handler(struct mg_connection *conn) {
-  const struct mg_request_info *ri = mg_get_request_info(conn);
+static int event_handler(struct mg_event *event) {
   char post_data[1024], input1[sizeof(post_data)], input2[sizeof(post_data)];
   int post_data_len;
 
-  if (!strcmp(ri->uri, "/handle_post_request")) {
-    // User has submitted a form, show submitted data and a variable value
-    post_data_len = mg_read(conn, post_data, sizeof(post_data));
-
-    // Parse form data. input1 and input2 are guaranteed to be NUL-terminated
-    mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
-    mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
-
-    // Send reply to the client, showing submitted form values.
-    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-              "Content-Type: text/plain\r\n\r\n"
-              "Submitted data: [%.*s]\n"
-              "Submitted data length: %d bytes\n"
-              "input_1: [%s]\n"
-              "input_2: [%s]\n",
-              post_data_len, post_data, post_data_len, input1, input2);
-  } else {
-    // Show HTML form.
-    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-              "Content-Length: %d\r\n"
-              "Content-Type: text/html\r\n\r\n%s",
-              (int) strlen(html_form), html_form);
+  if (event->type == MG_REQUEST_BEGIN) {
+    if (!strcmp(event->request_info->uri, "/handle_post_request")) {
+      // User has submitted a form, show submitted data and a variable value
+      post_data_len = mg_read(event->conn, post_data, sizeof(post_data));
+
+      // Parse form data. input1 and input2 are guaranteed to be NUL-terminated
+      mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1));
+      mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2));
+
+      // Send reply to the client, showing submitted form values.
+      mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
+                "Content-Type: text/plain\r\n\r\n"
+                "Submitted data: [%.*s]\n"
+                "Submitted data length: %d bytes\n"
+                "input_1: [%s]\n"
+                "input_2: [%s]\n",
+                post_data_len, post_data, post_data_len, input1, input2);
+    } else {
+      // Show HTML form.
+      mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
+                "Content-Length: %d\r\n"
+                "Content-Type: text/html\r\n\r\n%s",
+                (int) strlen(html_form), html_form);
+    }
+
+    return 1;  // Mark event as processed
   }
-  return 1;  // Mark request as processed
+
+  // All other events are left not processed
+  return 0;
 }
 
 int main(void) {
   struct mg_context *ctx;
   const char *options[] = {"listening_ports", "8080", NULL};
-  struct mg_callbacks callbacks;
 
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.begin_request = begin_request_handler;
-  ctx = mg_start(&callbacks, NULL, options);
+  ctx = mg_start(options, &event_handler, NULL);
   getchar();  // Wait until user hits "enter"
   mg_stop(ctx);
 
diff --git a/examples/upload.c b/examples/upload.c
index 599da50e1..0ce0fac7c 100644
--- a/examples/upload.c
+++ b/examples/upload.c
@@ -3,57 +3,48 @@
 
 #include <stdio.h>
 #include <string.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#ifdef _WIN32
-#include <windows.h>
-#include <io.h>
-#define strtoll strtol
-typedef __int64 int64_t;
-#else
-#include <inttypes.h>
-#include <unistd.h>
-#endif // !_WIN32
-
 #include "mongoose.h"
 
-static int begin_request_handler(struct mg_connection *conn) {
-  if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) {
-    mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n");
-    mg_upload(conn, "/tmp");
-  } else {
-    // Show HTML form. Make sure it has enctype="multipart/form-data" attr.
-    static const char *html_form =
-      "<html><body>Upload example."
-      "<form method=\"POST\" action=\"/handle_post_request\" "
-      "  enctype=\"multipart/form-data\">"
-      "<input type=\"file\" name=\"file\" /> <br/>"
-      "<input type=\"submit\" value=\"Upload\" />"
-      "</form></body></html>";
-
-    mg_printf(conn, "HTTP/1.0 200 OK\r\n"
-              "Content-Length: %d\r\n"
-              "Content-Type: text/html\r\n\r\n%s",
-              (int) strlen(html_form), html_form);
+static int event_handler(struct mg_event *event) {
+
+  if (event->type == MG_REQUEST_BEGIN) {
+    if (!strcmp(event->request_info->uri, "/handle_post_request")) {
+      char path[200];
+      FILE *fp = mg_upload(event->conn, "/tmp", path, sizeof(path));
+      if (fp != NULL) {
+        fclose(fp);
+        mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\nSaved: [%s]", path);
+      } else {
+        mg_printf(event->conn, "%s", "HTTP/1.0 200 OK\r\n\r\nNo files sent");
+      }
+    } else {
+      // Show HTML form. Make sure it has enctype="multipart/form-data" attr.
+      static const char *html_form =
+        "<html><body>Upload example."
+        "<form method=\"POST\" action=\"/handle_post_request\" "
+        "  enctype=\"multipart/form-data\">"
+        "<input type=\"file\" name=\"file\" /> <br/>"
+        "<input type=\"submit\" value=\"Upload\" />"
+        "</form></body></html>";
+
+      mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
+          "Content-Length: %d\r\n"
+          "Content-Type: text/html\r\n\r\n%s",
+          (int) strlen(html_form), html_form);
+    }
+
+    // Mark request as processed
+    return 1;
   }
 
-  // Mark request as processed
+  // All other events left unprocessed
   return 1;
 }
 
-static void upload_handler(struct mg_connection *conn, const char *path) {
-  mg_printf(conn, "Saved [%s]", path);
-}
-
 int main(void) {
   struct mg_context *ctx;
   const char *options[] = {"listening_ports", "8080", NULL};
-  struct mg_callbacks callbacks;
-
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.begin_request = begin_request_handler;
-  callbacks.upload = upload_handler;
-  ctx = mg_start(&callbacks, NULL, options);
+  ctx = mg_start(options, event_handler, NULL);
   getchar();  // Wait until user hits "enter"
   mg_stop(ctx);
 
diff --git a/examples/websocket.c b/examples/websocket.c
index 8a6083c1c..050c51e5d 100644
--- a/examples/websocket.c
+++ b/examples/websocket.c
@@ -5,38 +5,52 @@
 #include <string.h>
 #include "mongoose.h"
 
-static void websocket_ready_handler(struct mg_connection *conn) {
-  static const char *message = "server ready";
-  mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message));
-}
+static int event_handler(struct mg_event *event) {
+
+  if (event->type == MG_REQUEST_BEGIN) {
+    const char *version_header = mg_get_header(event->conn,
+                                               "Sec-WebSocket-Version");
+
+    if (version_header != NULL) {
+      // Websocket request, process it
+      if (strcmp(version_header, "13") != 0) {
+        mg_printf(event->conn, "%s", "HTTP/1.1 426 Upgrade Required\r\n\r\n");
+      } else {
+        static const char *server_ready_message = "server ready";
+        char *data;
+        int bits, len;
 
-// Arguments:
-//   flags: first byte of websocket frame, see websocket RFC,
-//          http://tools.ietf.org/html/rfc6455, section 5.2
-//   data, data_len: payload data. Mask, if any, is already applied.
-static int websocket_data_handler(struct mg_connection *conn, int flags,
-                                  char *data, size_t data_len) {
-  (void) flags; // Unused
-  mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len);
-
-  // Returning zero means stoping websocket conversation.
-  // Close the conversation if client has sent us "exit" string.
-  return memcmp(data, "exit", 4);
+        // Handshake, and send initial server message
+        mg_websocket_handshake(event->conn);
+        mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT,
+                           server_ready_message, strlen(server_ready_message));
+
+        while ((len = mg_websocket_read(event->conn, &bits, &data)) > 0) {
+          // Echo message back to the client
+          mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT, data, len);
+          if (memcmp(data, "exit", 4) == 0) {
+            mg_websocket_write(event->conn,
+                               WEBSOCKET_OPCODE_CONNECTION_CLOSE, "", 0);
+            break;
+          }
+        }
+      }
+      return 1;
+    }
+  }
+
+  return 0;
 }
 
 int main(void) {
   struct mg_context *ctx;
-  struct mg_callbacks callbacks;
   const char *options[] = {
     "listening_ports", "8080",
     "document_root", "websocket_html_root",
     NULL
   };
 
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.websocket_ready = websocket_ready_handler;
-  callbacks.websocket_data = websocket_data_handler;
-  ctx = mg_start(&callbacks, NULL, options);
+  ctx = mg_start(options, &event_handler, NULL);
   getchar();  // Wait until user hits "enter"
   mg_stop(ctx);
 
diff --git a/mongoose.c b/mongoose.c
index d9940ab59..7a97dd3fd 100644
--- a/mongoose.c
+++ b/mongoose.c
@@ -345,7 +345,7 @@ struct ssl_func {
 #define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \
         const char *, int)) ssl_sw[12].ptr)
 #define SSL_CTX_set_default_passwd_cb \
-  (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr)
+  (* (void (*)(SSL_CTX *, mg_event_handler_t)) ssl_sw[13].ptr)
 #define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr)
 #define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr)
 #define SSL_CTX_use_certificate_chain_file \
@@ -490,8 +490,7 @@ struct mg_context {
   volatile int stop_flag;         // Should we stop event loop
   SSL_CTX *ssl_ctx;               // SSL context
   char *config[NUM_OPTIONS];      // Mongoose configuration parameters
-  struct mg_callbacks callbacks;  // User-defined callback function
-  mg_callback_t user_callback;    // User-defined callback function
+  mg_event_handler_t event_handler;  // User-defined callback function
   void *user_data;                // User-defined data
 
   struct socket *listening_sockets;
@@ -510,6 +509,7 @@ struct mg_context {
 
 struct mg_connection {
   struct mg_request_info request_info;
+  struct mg_event event;
   struct mg_context *ctx;
   SSL *ssl;                   // SSL descriptor
   SSL_CTX *client_ssl_ctx;    // SSL context for client connections
@@ -517,7 +517,7 @@ struct mg_connection {
   time_t birth_time;          // Time when request was received
   int64_t num_bytes_sent;     // Total bytes sent to client
   int64_t content_len;        // Content-Length header value
-  int64_t consumed_content;   // How many bytes of content have been read
+  int64_t num_bytes_read;     // Bytes read from a remote socket
   char *buf;                  // Buffer for received data
   char *path_info;            // PATH_INFO part of the URL
   int must_close;             // 1 if connection must be closed
@@ -537,16 +537,25 @@ struct de {
   struct file file;
 };
 
+// Return number of bytes left to read for this connection
+static int64_t left_to_read(const struct mg_connection *conn) {
+  return conn->content_len + conn->request_len - conn->num_bytes_read;
+}
+
 const char **mg_get_valid_option_names(void) {
   return config_options;
 }
 
-static int call_user(enum mg_event ev, struct mg_connection *conn, void *p) {
+static int call_user(int type, struct mg_connection *conn, void *p) {
   if (conn != NULL && conn->ctx != NULL) {
-    conn->request_info.user_data = conn->ctx->user_data;
+    conn->event.user_data = conn->ctx->user_data;
+    conn->event.type = type;
+    conn->event.event_param = p;
+    conn->event.request_info = &conn->request_info;
+    conn->event.conn = conn;
   }
-  return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ?
-    0 : conn->ctx->user_callback(ev, conn, p);
+  return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
+    0 : conn->ctx->event_handler(&conn->event);
 }
 
 static FILE *mg_fopen(const char *path, const char *mode) {
@@ -614,8 +623,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
   // Do not lock when getting the callback value, here and below.
   // I suppose this is fine, since function cannot disappear in the
   // same way string option can.
-  if (conn->ctx->callbacks.log_message == NULL ||
-      conn->ctx->callbacks.log_message(conn, buf) == 0) {
+  if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
     fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
       fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
 
@@ -646,7 +654,7 @@ static struct mg_connection *fc(struct mg_context *ctx) {
   static struct mg_connection fake_connection;
   fake_connection.ctx = ctx;
   // See https://github.com/cesanta/mongoose/issues/236
-  fake_connection.request_info.user_data = ctx->user_data;
+  fake_connection.event.user_data = ctx->user_data;
   return &fake_connection;
 }
 
@@ -1497,6 +1505,7 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
 static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
   int nread;
 
+  if (len <= 0) return 0;
   if (fp != NULL) {
     // Use read() instead of fread(), because if we're reading from the CGI
     // pipe, fread() may block until IO buffer is filled up. We cannot afford
@@ -1509,6 +1518,9 @@ static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
   } else {
     nread = recv(conn->client.sock, buf, (size_t) len, 0);
   }
+  if (nread > 0) {
+    conn->num_bytes_read += nread;
+  }
 
   return conn->ctx->stop_flag ? -1 : nread;
 }
@@ -1524,7 +1536,6 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
     } else if (n == 0) {
       break;  // No more data to read
     } else {
-      conn->consumed_content += n;
       nread += n;
       len -= n;
     }
@@ -1533,46 +1544,48 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
   return nread;
 }
 
-int mg_read(struct mg_connection *conn, void *buf, size_t len) {
-  int n, buffered_len, nread;
-  const char *body;
+int mg_read(struct mg_connection *conn, void *buf, int len) {
+  int n, buffered_len, nread = 0;
+  int64_t left;
 
   // If Content-Length is not set, read until socket is closed
-  if (conn->consumed_content == 0 && conn->content_len == 0) {
+  if (conn->content_len <= 0) {
     conn->content_len = INT64_MAX;
     conn->must_close = 1;
   }
 
-  nread = 0;
-  if (conn->consumed_content < conn->content_len) {
-    // Adjust number of bytes to read.
-    int64_t to_read = conn->content_len - conn->consumed_content;
-    if (to_read < (int64_t) len) {
-      len = (size_t) to_read;
-    }
+  // conn->buf           body
+  //    |=================|==========|===============|
+  //    |<--request_len-->|                          |
+  //    |<-----------data_len------->|      conn->buf + conn->buf_size
 
-    // Return buffered data
-    body = conn->buf + conn->request_len + conn->consumed_content;
-    buffered_len = &conn->buf[conn->data_len] - body;
-    if (buffered_len > 0) {
-      if (len < (size_t) buffered_len) {
-        buffered_len = (int) len;
-      }
-      memcpy(buf, body, (size_t) buffered_len);
-      len -= buffered_len;
-      conn->consumed_content += buffered_len;
-      nread += buffered_len;
-      buf = (char *) buf + buffered_len;
-    }
+  // First, check for data buffered in conn->buf by read_request().
+  if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
+    char *body = conn->buf + conn->request_len;
+    if (buffered_len > len) buffered_len = len;
+    if (buffered_len > conn->content_len) buffered_len = conn->content_len;
 
-    // We have returned all buffered data. Read new data from the remote socket.
-    n = pull_all(NULL, conn, (char *) buf, (int) len);
+    memcpy(buf, body, (size_t) buffered_len);
+    memmove(body, body + buffered_len,
+            &conn->buf[conn->data_len] - &body[buffered_len]);
+    len -= buffered_len;
+    conn->data_len -= buffered_len;
+    nread += buffered_len;
+  }
+
+  // Read data from the socket.
+  if (len > 0 && (left = left_to_read(conn)) > 0) {
+    if (left < len) {
+      len = (int) left;
+    }
+    n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
     nread = n >= 0 ? nread + n : n;
   }
+
   return nread;
 }
 
-int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
+int mg_write(struct mg_connection *conn, const void *buf, int len) {
   time_t now;
   int64_t n, total, allowed;
 
@@ -1864,25 +1877,25 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
 //   -1  if request is malformed
 //    0  if request is not yet fully buffered
 //   >0  actual request length, including last \r\n\r\n
-static int get_request_len(const char *buf, int buflen) {
-  const char *s, *e;
-  int len = 0;
+static int get_request_len(const char *buf, int buf_len) {
+  int i;
 
-  for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
+  for (i = 0; i < buf_len; i++) {
     // Control characters are not allowed but >=128 is.
-    if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
-        *s != '\n' && * (const unsigned char *) s < 128) {
-      len = -1;
-      break;  // [i_a] abort scan as soon as one malformed character is found;
-              // don't let subsequent \r\n\r\n win us over anyhow
-    } else if (s[0] == '\n' && s[1] == '\n') {
-      len = (int) (s - buf) + 2;
-    } else if (s[0] == '\n' && &s[1] < e &&
-        s[1] == '\r' && s[2] == '\n') {
-      len = (int) (s - buf) + 3;
+    // Abort scan as soon as one malformed character is found;
+    // don't let subsequent \r\n\r\n win us over anyhow
+    if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
+        buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
+      return -1;
+    } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
+      return i + 2;
+    } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
+               buf[i + 2] == '\n') {
+      return i + 3;
     }
+  }
 
-  return len;
+  return 0;
 }
 
 // Convert month to the month number. Return -1 on error, or month number
@@ -2840,7 +2853,7 @@ static void handle_directory_request(struct mg_connection *conn,
 static void send_file_data(struct mg_connection *conn, FILE *fp,
                            int64_t offset, int64_t len) {
   char buf[MG_BUF_LEN];
-  int to_read, num_read, num_written;
+  int num_read, num_written, to_read;
 
   // If offset is beyond file boundaries, don't send anything
   if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
@@ -3054,7 +3067,8 @@ static int read_request(FILE *fp, struct mg_connection *conn,
 
   request_len = get_request_len(buf, *nread);
   while (conn->ctx->stop_flag == 0 &&
-         *nread < bufsiz && request_len == 0 &&
+         *nread < bufsiz &&
+         request_len == 0 &&
          (n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
     *nread += n;
     assert(*nread <= bufsiz);
@@ -3126,7 +3140,8 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
                              SOCKET sock, SSL *ssl) {
   const char *expect, *body;
   char buf[MG_BUF_LEN];
-  int to_read, nread, buffered_len, success = 0;
+  int nread, buffered_len, success = 0;
+  int64_t left;
 
   expect = mg_get_header(conn, "Expect");
   assert(fp != NULL);
@@ -3140,33 +3155,32 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp,
       (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
     }
 
-    body = conn->buf + conn->request_len + conn->consumed_content;
-    buffered_len = &conn->buf[conn->data_len] - body;
+    buffered_len = conn->data_len - conn->request_len;
+    body = conn->buf + conn->request_len;
     assert(buffered_len >= 0);
-    assert(conn->consumed_content == 0);
 
     if (buffered_len > 0) {
       if ((int64_t) buffered_len > conn->content_len) {
         buffered_len = (int) conn->content_len;
       }
       push(fp, sock, ssl, body, (int64_t) buffered_len);
-      conn->consumed_content += buffered_len;
+      memmove((char *) body, body + buffered_len, buffered_len);
+      conn->data_len -= buffered_len;
     }
 
     nread = 0;
-    while (conn->consumed_content < conn->content_len) {
-      to_read = sizeof(buf);
-      if ((int64_t) to_read > conn->content_len - conn->consumed_content) {
-        to_read = (int) (conn->content_len - conn->consumed_content);
+    while (conn->num_bytes_read < conn->content_len + conn->request_len) {
+      left = left_to_read(conn);
+      if (left > (int64_t) sizeof(buf)) {
+        left = sizeof(buf);
       }
-      nread = pull(NULL, conn, buf, to_read);
+      nread = pull(NULL, conn, buf, left);
       if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
         break;
       }
-      conn->consumed_content += nread;
     }
 
-    if (conn->consumed_content == conn->content_len) {
+    if (left_to_read(conn) == 0) {
       success = nread >= 0;
     }
 
@@ -3980,7 +3994,7 @@ static void base64_encode(const unsigned char *src, int src_len, char *dst) {
   dst[j++] = '\0';
 }
 
-static void send_websocket_handshake(struct mg_connection *conn) {
+void mg_websocket_handshake(struct mg_connection *conn) {
   static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
   char buf[100], sha[20], b64_sha[sizeof(sha) * 2];
   SHA1_CTX sha_ctx;
@@ -3998,17 +4012,14 @@ static void send_websocket_handshake(struct mg_connection *conn) {
             "Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n");
 }
 
-static void read_websocket(struct mg_connection *conn) {
+int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) {
   // Pointer to the beginning of the portion of the incoming websocket message
   // queue. The original websocket upgrade request is never removed,
   // so the queue begins after it.
   unsigned char *buf = (unsigned char *) conn->buf + conn->request_len;
-  int bits, n, stop = 0;
+  int n, stop = 0;
   size_t i, len, mask_len, data_len, header_len, body_len;
-  // data points to the place where the message is stored when passed to the
-  // websocket_data callback. This is either mem on the stack,
-  // or a dynamically allocated buffer if it is too large.
-  char mem[4 * 1024], mask[4], *data;
+  char mask[4];
 
   assert(conn->content_len == 0);
 
@@ -4046,28 +4057,28 @@ static void read_websocket(struct mg_connection *conn) {
 
     if (header_len > 0) {
       // Allocate space to hold websocket payload
-      data = mem;
-      if (data_len > sizeof(mem) && (data = malloc(data_len)) == NULL) {
+      if ((*data = malloc(data_len)) == NULL) {
         // Allocation failed, exit the loop and then close the connection
         // TODO: notify user about the failure
+        data_len = 0;
         break;
       }
 
       // Save mask and bits, otherwise it may be clobbered by memmove below
-      bits = buf[0];
+      *bits = buf[0];
       memcpy(mask, buf + header_len - mask_len, mask_len);
 
       // Read frame payload into the allocated buffer.
       assert(body_len >= header_len);
       if (data_len + header_len > body_len) {
         len = body_len - header_len;
-        memcpy(data, buf + header_len, len);
+        memcpy(*data, buf + header_len, len);
         // TODO: handle pull error
-        pull_all(NULL, conn, data + len, data_len - len);
+        pull_all(NULL, conn, *data + len, data_len - len);
         conn->data_len = conn->request_len;
       } else {
         len = data_len + header_len;
-        memcpy(data, buf + header_len, data_len);
+        memcpy(*data, buf + header_len, data_len);
         memmove(buf, buf + len, body_len - len);
         conn->data_len -= len;
       }
@@ -4075,21 +4086,17 @@ static void read_websocket(struct mg_connection *conn) {
       // Apply mask if necessary
       if (mask_len > 0) {
         for (i = 0; i < data_len; i++) {
-          data[i] ^= mask[i % 4];
+          (*data)[i] ^= mask[i % 4];
         }
       }
 
       // Exit the loop if callback signalled to exit,
       // or "connection close" opcode received.
-      if (((bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) ||
-          (conn->ctx->callbacks.websocket_data != NULL &&
-           !conn->ctx->callbacks.websocket_data(conn, bits, data, data_len))) {
+      if ((*bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) {
+        return data_len;
         stop = 1;
       }
 
-      if (data != mem) {
-        free(data);
-      }
       // Not breaking the loop, process next websocket frame.
     } else {
       // Buffering websocket request
@@ -4100,6 +4107,8 @@ static void read_websocket(struct mg_connection *conn) {
       conn->data_len += n;
     }
   }
+
+  return 0;
 }
 
 int mg_websocket_write(struct mg_connection* conn, int opcode,
@@ -4143,37 +4152,6 @@ int mg_websocket_write(struct mg_connection* conn, int opcode,
 
     return retval;
 }
-
-static void handle_websocket_request(struct mg_connection *conn) {
-  const char *version = mg_get_header(conn, "Sec-WebSocket-Version");
-  if (version == NULL || strcmp(version, "13") != 0) {
-    send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required");
-  } else if (conn->ctx->callbacks.websocket_connect != NULL &&
-             conn->ctx->callbacks.websocket_connect(conn) != 0) {
-    // Callback has returned non-zero, do not proceed with handshake
-  } else {
-    send_websocket_handshake(conn);
-    if (conn->ctx->callbacks.websocket_ready != NULL) {
-      conn->ctx->callbacks.websocket_ready(conn);
-    }
-    read_websocket(conn);
-  }
-}
-
-static int is_websocket_request(const struct mg_connection *conn) {
-  const char *host, *upgrade, *connection, *version, *key;
-
-  host = mg_get_header(conn, "Host");
-  upgrade = mg_get_header(conn, "Upgrade");
-  connection = mg_get_header(conn, "Connection");
-  key = mg_get_header(conn, "Sec-WebSocket-Key");
-  version = mg_get_header(conn, "Sec-WebSocket-Version");
-
-  return host != NULL && upgrade != NULL && connection != NULL &&
-    key != NULL && version != NULL &&
-    mg_strcasestr(upgrade, "websocket") != NULL &&
-    mg_strcasestr(connection, "Upgrade") != NULL;
-}
 #endif // !USE_WEBSOCKET
 
 static int isbyte(int n) {
@@ -4231,12 +4209,12 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) {
 #include "build/mod_lua.c"
 #endif // USE_LUA
 
-int mg_upload(struct mg_connection *conn, const char *destination_dir) {
+FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
+                char *path, int path_len) {
   const char *content_type_header, *boundary_start;
-  char buf[MG_BUF_LEN], path[PATH_MAX], fname[1024], boundary[100], *s;
+  char *buf, fname[1024], boundary[100], *s;
+  int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0;
   FILE *fp;
-  int bl, n, i, j, headers_len, boundary_len, eof,
-      len = 0, num_uploaded_files = 0;
 
   // Request looks like this:
   //
@@ -4260,15 +4238,31 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
       (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 &&
        sscanf(boundary_start, "boundary=%99s", boundary) == 0) ||
       boundary[0] == '\0') {
-    return num_uploaded_files;
+    return NULL;
   }
 
   boundary_len = strlen(boundary);
   bl = boundary_len + 4;  // \r\n--<boundary>
+
+  //                     buf
+  // conn->buf            |<--------- buf_len ------>|
+  //    |=================|==========|===============|
+  //    |<--request_len-->|<--len--->|               |
+  //    |<-----------data_len------->|      conn->buf + conn->buf_size
+
+  buf = conn->buf + conn->request_len;
+  buf_len = conn->buf_size - conn->request_len;
+  len = conn->data_len - conn->request_len;
+
   for (;;) {
     // Pull in headers
-    assert(len >= 0 && len <= (int) sizeof(buf));
-    while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) {
+    assert(len >= 0 && len <= buf_len);
+    to_read = buf_len - len;
+    if (to_read > left_to_read(conn)) {
+      to_read = left_to_read(conn);
+    }
+    while (len < buf_len &&
+           (n = pull(NULL, conn, buf + len, to_read)) > 0) {
       len += n;
     }
     if ((headers_len = get_request_len(buf, len)) <= 0) {
@@ -4297,10 +4291,12 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
     assert(len >= headers_len);
     memmove(buf, &buf[headers_len], len - headers_len);
     len -= headers_len;
+    conn->data_len = conn->request_len + len;
 
     // We open the file with exclusive lock held. This guarantee us
     // there is no other thread can save into the same file simultaneously.
     fp = NULL;
+
     // Construct destination file name. Do not allow paths to have slashes.
     if ((s = strrchr(fname, '/')) == NULL &&
         (s = strrchr(fname, '\\')) == NULL) {
@@ -4308,7 +4304,7 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
     }
 
     // Open file in binary mode. TODO: set an exclusive lock.
-    snprintf(path, sizeof(path), "%s/%s", destination_dir, s);
+    snprintf(path, path_len, "%s/%s", destination_dir, s);
     if ((fp = fopen(path, "wb")) == NULL) {
       break;
     }
@@ -4333,17 +4329,22 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) {
         memmove(buf, &buf[len - bl], bl);
         len = bl;
       }
-    } while (!eof && (n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0);
-    fclose(fp);
-    if (eof) {
-      num_uploaded_files++;
-      if (conn->ctx->callbacks.upload != NULL) {
-        conn->ctx->callbacks.upload(conn, path);
+      to_read = buf_len - len;
+      if (to_read > left_to_read(conn)) {
+        to_read = left_to_read(conn);
       }
+    } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0);
+    conn->data_len = conn->request_len + len;
+
+    if (eof) {
+      rewind(fp);
+      return fp;
+    } else {
+      fclose(fp);
     }
   }
 
-  return num_uploaded_files;
+  return NULL;
 }
 
 static int is_put_or_delete_request(const struct mg_connection *conn) {
@@ -4417,7 +4418,6 @@ static void handle_request(struct mg_connection *conn) {
   path[0] = '\0';
   convert_uri_to_file_name(conn, path, sizeof(path), &file);
 
-  DEBUG_TRACE(("%s", ri->uri));
   // Perform redirect and auth checks before calling begin_request() handler.
   // Otherwise, begin_request() would need to perform auth checks and redirects.
   if (!conn->client.is_ssl && conn->client.ssl_redir &&
@@ -4426,13 +4426,8 @@ static void handle_request(struct mg_connection *conn) {
   } else if (!is_put_or_delete_request(conn) &&
              !check_authorization(conn, path)) {
     send_authorization_request(conn);
-  } else if (conn->ctx->callbacks.begin_request != NULL &&
-      conn->ctx->callbacks.begin_request(conn)) {
+  } else if (call_user(MG_REQUEST_BEGIN, conn, (void *) ri->uri) == 1) {
     // Do nothing, callback has served the request
-#if defined(USE_WEBSOCKET)
-  } else if (is_websocket_request(conn)) {
-    handle_websocket_request(conn);
-#endif
   } else if (!strcmp(ri->request_method, "OPTIONS")) {
     handle_options_request(conn);
   } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
@@ -4767,8 +4762,9 @@ static int set_ssl_option(struct mg_context *ctx) {
 
   // If PEM file is not specified and the init_ssl callback
   // is not specified, skip SSL initialization.
-  if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL &&
-      ctx->callbacks.init_ssl == NULL) {
+  if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) {
+    //  MG_INIT_SSL
+    //  ctx->callbacks.init_ssl == NULL) {
     return 1;
   }
 
@@ -4790,10 +4786,9 @@ static int set_ssl_option(struct mg_context *ctx) {
 
   // If user callback returned non-NULL, that means that user callback has
   // set up certificate itself. In this case, skip sertificate setting.
-  if ((ctx->callbacks.init_ssl == NULL ||
-       !ctx->callbacks.init_ssl(ctx->ssl_ctx, ctx->user_data)) &&
-      (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
-       SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) {
+  // MG_INIT_SSL
+  if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 ||
+      SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) {
     cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
     return 0;
   }
@@ -4849,7 +4844,7 @@ static int set_acl_option(struct mg_context *ctx) {
 
 static void reset_per_request_attributes(struct mg_connection *conn) {
   conn->path_info = NULL;
-  conn->num_bytes_sent = conn->consumed_content = 0;
+  conn->num_bytes_sent = conn->num_bytes_read = 0;
   conn->status_code = -1;
   conn->must_close = conn->request_len = conn->throttle = 0;
 }
@@ -5039,9 +5034,7 @@ static void process_new_connection(struct mg_connection *conn) {
 
     if (ebuf[0] == '\0') {
       handle_request(conn);
-      if (conn->ctx->callbacks.end_request != NULL) {
-        conn->ctx->callbacks.end_request(conn, conn->status_code);
-      }
+      call_user(MG_REQUEST_END, conn, (void *) conn->status_code);
       log_access(conn);
     }
     if (ri->remote_user != NULL) {
@@ -5111,12 +5104,9 @@ static void *worker_thread(void *thread_func_param) {
     conn->buf_size = MAX_REQUEST_SIZE;
     conn->buf = (char *) (conn + 1);
     conn->ctx = ctx;
-    conn->request_info.user_data = ctx->user_data;
+    conn->event.user_data = ctx->user_data;
 
-    if (ctx->callbacks.thread_start != NULL) {
-      ctx->callbacks.thread_start(&conn->request_info.user_data,
-                                  &conn->request_info.conn_data);
-    }
+    call_user(MG_THREAD_BEGIN, conn, NULL);
 
     // Call consume_socket() even when ctx->stop_flag > 0, to let it signal
     // sq_empty condvar to wake up the master waiting in produce_socket()
@@ -5143,10 +5133,7 @@ static void *worker_thread(void *thread_func_param) {
 
       close_connection(conn);
     }
-    if (ctx->callbacks.thread_stop != NULL) {
-      ctx->callbacks.thread_stop(&conn->request_info.user_data,
-                                 &conn->request_info.conn_data);
-    }
+    call_user(MG_THREAD_END, conn, NULL);
     free(conn);
   }
 
@@ -5241,9 +5228,7 @@ static void *master_thread(void *thread_func_param) {
   pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param);
 #endif
 
-  if (ctx->callbacks.thread_start != NULL) {
-    ctx->callbacks.thread_start(&ctx->user_data, NULL);
-  }
+  call_user(MG_THREAD_BEGIN, fc(ctx), NULL);
 
   pfd = (struct pollfd *) calloc(ctx->num_listening_sockets, sizeof(pfd[0]));
   while (pfd != NULL && ctx->stop_flag == 0) {
@@ -5291,9 +5276,7 @@ static void *master_thread(void *thread_func_param) {
 #endif
   DEBUG_TRACE(("exiting"));
 
-  if (ctx->callbacks.thread_stop != NULL) {
-    ctx->callbacks.thread_stop(&ctx->user_data, NULL);
-  }
+  call_user(MG_THREAD_END, fc(ctx), NULL);
 
   // Signal mg_stop() that we're done.
   // WARNING: This must be the very last thing this
@@ -5340,9 +5323,9 @@ void mg_stop(struct mg_context *ctx) {
 #endif // _WIN32
 }
 
-struct mg_context *mg_start(const struct mg_callbacks *callbacks,
-                            void *user_data,
-                            const char **options) {
+struct mg_context *mg_start(const char **options,
+                            mg_event_handler_t func,
+                            void *user_data) {
   struct mg_context *ctx;
   const char *name, *value, *default_value;
   int i;
@@ -5357,7 +5340,7 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks,
   if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) {
     return NULL;
   }
-  ctx->callbacks = *callbacks;
+  ctx->event_handler = func;
   ctx->user_data = user_data;
 
   while (options && (name = *options++) != NULL) {
diff --git a/mongoose.h b/mongoose.h
index a79e6090a..36aba6a1d 100644
--- a/mongoose.h
+++ b/mongoose.h
@@ -25,8 +25,8 @@
 extern "C" {
 #endif // __cplusplus
 
-struct mg_context;     // Handle for the HTTP service itself
-struct mg_connection;  // Handle for the individual connection
+struct mg_context;     // Web server instance
+struct mg_connection;  // HTTP request descriptor
 
 
 // This structure contains information about the HTTP request.
@@ -39,8 +39,6 @@ struct mg_request_info {
   long remote_ip;             // Client's IP address
   int remote_port;            // Client's port
   int is_ssl;                 // 1 if SSL-ed, 0 if not
-  void *user_data;            // User data pointer passed to mg_start()
-  void *conn_data;            // Connection-specific, per-thread user data.
 
   int num_headers;            // Number of HTTP headers
   struct mg_header {
@@ -49,37 +47,32 @@ struct mg_request_info {
   } http_headers[64];         // Maximum 64 headers
 };
 
-enum mg_event {
-  MG_REQUEST_BEGIN,
-  MG_REQUEST_END,
-  MG_HTTP_ERROR,
-  MG_EVENT_LOG,
-  MG_THREAD_BEGIN,
-  MG_THREAD_END
-};
-typedef int (*mg_callback_t)(enum mg_event event,
-                             struct mg_connection *conn,
-                             void *data);
-
-struct mg_callbacks {
-  int  (*begin_request)(struct mg_connection *);
-  void (*end_request)(const struct mg_connection *, int reply_status_code);
-  int  (*log_message)(const struct mg_connection *, const char *message);
-  int  (*init_ssl)(void *ssl_context, void *user_data);
-  int (*websocket_connect)(const struct mg_connection *);
-  void (*websocket_ready)(struct mg_connection *);
-  int  (*websocket_data)(struct mg_connection *, int bits,
-                         char *data, size_t data_len);
-  void (*upload)(struct mg_connection *, const char *file_name);
-  void (*thread_start)(void *user_data, void **conn_data);
-  void (*thread_stop)(void *user_data, void **conn_data);
+struct mg_event {
+  int type;                   // Event type, possible types are defined below
+#define MG_REQUEST_BEGIN  1   // event_param: NULL
+#define MG_REQUEST_END    2   // event_param: NULL
+#define MG_HTTP_ERROR     3   // event_param: int status_code
+#define MG_EVENT_LOG      4   // event_param: const char *message
+#define MG_THREAD_BEGIN   5   // event_param: NULL
+#define MG_THREAD_END     6   // event_param: NULL
+
+  void *user_data;            // User data pointer passed to mg_start()
+  void *conn_data;            // Connection-specific, per-thread user data.
+  void *event_param;          // Event-specific parameter
+
+  struct mg_connection *conn;
+  struct mg_request_info *request_info;
 };
 
-struct mg_context *mg_start(const struct mg_callbacks *callbacks,
-                            void *user_data,
-                            const char **configuration_options);
+typedef int (*mg_event_handler_t)(struct mg_event *event);
+
+struct mg_context *mg_start(const char **configuration_options,
+                            mg_event_handler_t func, void *user_data);
 void mg_stop(struct mg_context *);
 
+void mg_websocket_handshake(struct mg_connection *);
+int mg_websocket_read(struct mg_connection *, int *bits, char **data);
+
 
 // Get the value of particular configuration parameter.
 // The value returned is read-only. Mongoose does not allow changing
@@ -114,17 +107,12 @@ int mg_modify_passwords_file(const char *passwords_file_name,
                              const char *user,
                              const char *password);
 
-
-// Return information associated with the request.
-struct mg_request_info *mg_get_request_info(struct mg_connection *);
-
-
 // Send data to the client.
 // Return:
 //  0   when the connection has been closed
 //  -1  on error
 //  >0  number of bytes written on success
-int mg_write(struct mg_connection *, const void *buf, size_t len);
+int mg_write(struct mg_connection *, const void *buf, int len);
 
 
 // Send data to a websocket client wrapped in a websocket frame.
@@ -184,7 +172,7 @@ void mg_send_file(struct mg_connection *conn, const char *path);
 //   0     connection has been closed by peer. No more data could be read.
 //   < 0   read error. No more data could be read from the connection.
 //   > 0   number of bytes read into the buffer.
-int mg_read(struct mg_connection *, void *buf, size_t len);
+int mg_read(struct mg_connection *, void *buf, int len);
 
 
 // Get the value of particular HTTP header.
@@ -258,10 +246,13 @@ struct mg_connection *mg_download(const char *host, int port, int use_ssl,
 void mg_close_connection(struct mg_connection *conn);
 
 
-// File upload functionality. Each uploaded file gets saved into a temporary
-// file and MG_UPLOAD event is sent.
-// Return number of uploaded files.
-int mg_upload(struct mg_connection *conn, const char *destination_dir);
+// Read multipart-form-data POST buffer, save uploaded files into
+// destination directory, and return path to the saved filed.
+// This function can be called multiple times for the same connection,
+// if more then one file is uploaded.
+// Return: path to the uploaded file, or NULL if there are no more files.
+FILE *mg_upload(struct mg_connection *conn, const char *destination_dir,
+                char *path, int path_len);
 
 
 // Convenience function -- create detached thread.
diff --git a/test/unit_test.c b/test/unit_test.c
index e5a77ee46..4da60b33b 100644
--- a/test/unit_test.c
+++ b/test/unit_test.c
@@ -1,24 +1,4 @@
-// Copyright (c) 2004-2013 Sergey Lyubka
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-// Unit test for the mongoose web server. Tests embedded API.
+// Unit test for the mongoose web server.
 
 #define USE_WEBSOCKET
 #define USE_LUA
@@ -65,6 +45,14 @@ static void test_parse_http_message() {
   char req8[] = " HTTP/1.1 200 OK \n\n";
   char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n";
 
+  ASSERT(get_request_len("\r\n", 3) == -1);
+  ASSERT(get_request_len("\r\n", 2) == 0);
+  ASSERT(get_request_len("GET", 3) == 0);
+  ASSERT(get_request_len("\n\n", 2) == 2);
+  ASSERT(get_request_len("\n\r\n", 3) == 3);
+  ASSERT(get_request_len("\xdd\xdd", 2) == 0);
+  ASSERT(get_request_len("\xdd\x03", 2) == -1);
+
   ASSERT(parse_http_message(req9, sizeof(req9), &ri) == sizeof(req9) - 1);
   ASSERT(ri.num_headers == 1);
 
@@ -72,15 +60,15 @@ static void test_parse_http_message() {
   ASSERT(strcmp(ri.http_version, "1.1") == 0);
   ASSERT(ri.num_headers == 0);
 
-  ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
-  ASSERT(parse_http_message(req3, sizeof(req3), &ri) == 0);
-  ASSERT(parse_http_message(req6, sizeof(req6), &ri) == 0);
-  ASSERT(parse_http_message(req7, sizeof(req7), &ri) == 0);
+  ASSERT(parse_http_message(req2, sizeof(req2) - 1, &ri) == -1);
+  ASSERT(parse_http_message(req3, sizeof(req3) - 1, &ri) == 0);
+  ASSERT(parse_http_message(req6, sizeof(req6) - 1, &ri) == 0);
+  ASSERT(parse_http_message(req7, sizeof(req7) - 1, &ri) == 0);
   ASSERT(parse_http_message("", 0, &ri) == 0);
-  ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1);
+  ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1);
 
   // TODO(lsm): Fix this. Header value may span multiple lines.
-  ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
+  ASSERT(parse_http_message(req4, sizeof(req4) - 1, &ri) == sizeof(req4) - 1);
   ASSERT(strcmp(ri.http_version, "1.1") == 0);
   ASSERT(ri.num_headers == 3);
   ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
@@ -90,7 +78,7 @@ static void test_parse_http_message() {
   ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
   ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
 
-  ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1);
+  ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1);
   ASSERT(strcmp(ri.request_method, "GET") == 0);
   ASSERT(strcmp(ri.http_version, "1.1") == 0);
 }
@@ -201,79 +189,55 @@ static char *read_file(const char *path, int *size) {
 }
 
 static const char *fetch_data = "hello world!\n";
-static const char *upload_filename = "upload_test.txt";
-static const char *upload_filename2 = "upload_test2.txt";
 static const char *upload_ok_message = "upload successful";
 
-static void upload_cb(struct mg_connection *conn, const char *path) {
-  const struct mg_request_info *ri = mg_get_request_info(conn);
-  char *p1, *p2;
+static void test_upload(struct mg_connection *conn, const char *orig_path,
+                        const char *uploaded_path) {
   int len1, len2;
+  char path[500], *p1, *p2;
+  FILE *fp;
 
-  if (atoi(ri->query_string) == 1) {
-    ASSERT(!strcmp(path, "./upload_test.txt"));
-    ASSERT((p1 = read_file("main.c", &len1)) != NULL);
-    ASSERT((p2 = read_file(path, &len2)) != NULL);
-    ASSERT(len1 == len2);
-    ASSERT(memcmp(p1, p2, len1) == 0);
-    free(p1), free(p2);
-    remove(upload_filename);
-  } else if (atoi(ri->query_string) == 2) {
-    if (!strcmp(path, "./upload_test.txt")) {
-      ASSERT((p1 = read_file("lua_5.2.1.h", &len1)) != NULL);
-      ASSERT((p2 = read_file(path, &len2)) != NULL);
-      ASSERT(len1 == len2);
-      ASSERT(memcmp(p1, p2, len1) == 0);
-      free(p1), free(p2);
-      remove(upload_filename);
-    } else if (!strcmp(path, "./upload_test2.txt")) {
-      ASSERT((p1 = read_file("mod_lua.c", &len1)) != NULL);
-      ASSERT((p2 = read_file(path, &len2)) != NULL);
-      ASSERT(len1 == len2);
-      ASSERT(memcmp(p1, p2, len1) == 0);
-      free(p1), free(p2);
-      remove(upload_filename);
-    } else {
-      ASSERT(0);
-    }
-  } else {
-    ASSERT(0);
-  }
-
-  mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s",
-            (int) strlen(upload_ok_message), upload_ok_message);
+  ASSERT((fp = mg_upload(conn, ".", path, sizeof(path))) != NULL);
+  fclose(fp);
+  ASSERT(!strcmp(path, uploaded_path));
+  ASSERT((p1 = read_file(orig_path, &len1)) != NULL);
+  ASSERT((p2 = read_file(path, &len2)) != NULL);
+  ASSERT(len1 == len2);
+  ASSERT(memcmp(p1, p2, len1) == 0);
+  free(p1), free(p2);
+  remove(path);
 }
 
-static int begin_request_handler_cb(struct mg_connection *conn) {
-  const struct mg_request_info *ri = mg_get_request_info(conn);
+static int event_handler(struct mg_event *event) {
+  struct mg_request_info *ri = event->request_info;
 
-  if (!strcmp(ri->uri, "/data")) {
-    mg_printf(conn, "HTTP/1.1 200 OK\r\n"
-              "Content-Type: text/plain\r\n\r\n"
-              "%s", fetch_data);
-    close_connection(conn);
-    return 1;
-  }
+  if (event->type == MG_REQUEST_BEGIN) {
+    if (!strcmp(ri->uri, "/data")) {
+      mg_printf(event->conn, "HTTP/1.1 200 OK\r\n"
+          "Content-Type: text/plain\r\n\r\n"
+          "%s", fetch_data);
+      close_connection(event->conn);
+      return 1;
+    }
 
-  if (!strcmp(ri->uri, "/upload")) {
-    ASSERT(ri->query_string != NULL);
-    ASSERT(mg_upload(conn, ".") == atoi(ri->query_string));
-  }
+    if (!strcmp(ri->uri, "/upload")) {
+      test_upload(event->conn, "lua_5.2.1.h", "./f1.txt");
+      test_upload(event->conn, "mod_lua.c", "./f2.txt");
+      ASSERT(mg_upload(event->conn, ".", NULL, 0) == NULL);
 
-  return 0;
-}
+      mg_printf(event->conn, "HTTP/1.0 200 OK\r\n"
+                "Content-Type: text/plain\r\n\r\n"
+                "%s", upload_ok_message);
+      close_connection(event->conn);
+      return 1;
+    }
+  } else if (event->type == MG_EVENT_LOG) {
+    printf("%s\n", (const char *) event->event_param);
+  }
 
-static int log_message_cb(const struct mg_connection *conn, const char *msg) {
-  (void) conn;
-  printf("%s\n", msg);
   return 0;
 }
 
-static const struct mg_callbacks CALLBACKS = {
-  &begin_request_handler_cb, NULL, &log_message_cb, NULL, NULL, NULL, NULL,
-  &upload_cb, NULL, NULL
-};
-
 static const char *OPTIONS[] = {
   "document_root", ".",
   "listening_ports", LISTENING_ADDR,
@@ -283,7 +247,7 @@ static const char *OPTIONS[] = {
 };
 
 static char *read_conn(struct mg_connection *conn, int *size) {
-  char buf[100], *data = NULL;
+  char buf[MG_BUF_LEN], *data = NULL;
   int len;
   *size = 0;
   while ((len = mg_read(conn, buf, sizeof(buf))) > 0) {
@@ -300,7 +264,7 @@ static void test_mg_download(void) {
   struct mg_connection *conn;
   struct mg_context *ctx;
 
-  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
 
   ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
   ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL);
@@ -362,33 +326,7 @@ static void test_mg_upload(void) {
   char ebuf[100], buf[20], *file_data, *file2_data, *post_data;
   int file_len, file2_len, post_data_len;
 
-  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
-
-  // Upload one file
-  ASSERT((file_data = read_file("main.c", &file_len)) != NULL);
-  post_data = NULL;
-  post_data_len = alloc_printf(&post_data, 0,
-                                       "--%s\r\n"
-                                       "Content-Disposition: form-data; "
-                                       "name=\"file\"; "
-                                       "filename=\"%s\"\r\n\r\n"
-                                       "%.*s\r\n"
-                                       "--%s--\r\n",
-                                       boundary, upload_filename,
-                                       file_len, file_data, boundary);
-  ASSERT(post_data_len > 0);
-  ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
-                             ebuf, sizeof(ebuf),
-                             "POST /upload?1 HTTP/1.1\r\n"
-                             "Content-Length: %d\r\n"
-                             "Content-Type: multipart/form-data; "
-                             "boundary=%s\r\n\r\n"
-                             "%.*s", post_data_len, boundary,
-                             post_data_len, post_data)) != NULL);
-  free(file_data), free(post_data);
-  ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
-  ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
-  mg_close_connection(conn);
+  ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
 
   // Upload two files
   ASSERT((file_data = read_file("lua_5.2.1.h", &file_len)) != NULL);
@@ -411,21 +349,20 @@ static void test_mg_upload(void) {
 
                                // Final boundary
                                "--%s--\r\n",
-                               boundary, upload_filename,
+                               boundary, "f1.txt",
                                file_len, file_data,
-                               boundary, upload_filename2,
+                               boundary, "f2.txt",
                                file2_len, file2_data,
                                boundary);
   ASSERT(post_data_len > 0);
   ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
                              ebuf, sizeof(ebuf),
-                             "POST /upload?2 HTTP/1.1\r\n"
+                             "POST /upload HTTP/1.1\r\n"
                              "Content-Length: %d\r\n"
                              "Content-Type: multipart/form-data; "
                              "boundary=%s\r\n\r\n"
                              "%.*s", post_data_len, boundary,
                              post_data_len, post_data)) != NULL);
-  free(file_data), free(file2_data), free(post_data);
   ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message));
   ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0);
   mg_close_connection(conn);
@@ -523,7 +460,7 @@ static void test_lua(void) {
 
   conn.ctx = &ctx;
   conn.buf = http_request;
-  conn.buf_size = conn.data_len = strlen(http_request);
+  conn.buf_size = conn.data_len = conn.num_bytes_read = strlen(http_request);
   conn.request_len = parse_http_message(conn.buf, conn.data_len,
                                         &conn.request_info);
   conn.content_len = conn.data_len - conn.request_len;
@@ -595,7 +532,7 @@ static void test_request_replies(void) {
     {NULL, NULL},
   };
 
-  ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL);
   for (i = 0; tests[i].request != NULL; i++) {
     ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
                                tests[i].request)) != NULL);
@@ -604,39 +541,42 @@ static void test_request_replies(void) {
   mg_stop(ctx);
 }
 
-static int api_callback(struct mg_connection *conn) {
-  struct mg_request_info *ri = mg_get_request_info(conn);
+static const char *api_uri = "/?a=%20&b=&c=xx";
+static int api_cb(struct mg_event *event) {
+  struct mg_request_info *ri = event->request_info;
   char post_data[100] = "";
 
-  ASSERT(ri->user_data == (void *) 123);
-  ASSERT(ri->num_headers == 2);
-  ASSERT(strcmp(mg_get_header(conn, "host"), "blah.com") == 0);
-  ASSERT(mg_read(conn, post_data, sizeof(post_data)) == 3);
-  ASSERT(memcmp(post_data, "b=1", 3) == 0);
-  ASSERT(ri->query_string != NULL);
-  ASSERT(ri->remote_ip > 0);
-  ASSERT(ri->remote_port > 0);
-  ASSERT(strcmp(ri->http_version, "1.0") == 0);
-
-  mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n");
-  return 1;
+  if (event->type == MG_REQUEST_BEGIN) {
+    ASSERT(event->user_data == (void *) 123);
+    ASSERT(ri->num_headers == 2);
+    ASSERT(strcmp(mg_get_header(event->conn, "host"), "blah.com") == 0);
+    ASSERT(mg_read(event->conn, post_data, sizeof(post_data)) == 3);
+    ASSERT(memcmp(post_data, "b=1", 3) == 0);
+    ASSERT(ri->query_string != NULL);
+    ASSERT(strcmp(ri->query_string, api_uri + 2) == 0);
+    ASSERT(ri->remote_ip > 0);
+    ASSERT(ri->remote_port > 0);
+    ASSERT(strcmp(ri->http_version, "1.0") == 0);
+
+    mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\n");
+    return 1;
+  }
+
+  return 0;
 }
 
 static void test_api_calls(void) {
   char ebuf[100];
-  struct mg_callbacks callbacks;
   struct mg_connection *conn;
   struct mg_context *ctx;
-  static const char *request = "POST /?a=%20&b=&c=xx HTTP/1.0\r\n"
+  static const char *fmt = "POST %s HTTP/1.0\r\n"
     "Host:  blah.com\n"     // More spaces before
     "content-length: 3\r\n" // Lower case header name
     "\r\nb=123456";         // Content size > content-length, test for mg_read()
 
-  memset(&callbacks, 0, sizeof(callbacks));
-  callbacks.begin_request = api_callback;
-  ASSERT((ctx = mg_start(&callbacks, (void *) 123, OPTIONS)) != NULL);
+  ASSERT((ctx = mg_start(OPTIONS, api_cb, (void *) 123)) != NULL);
   ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1,
-                             ebuf, sizeof(ebuf), "%s", request)) != NULL);
+                             ebuf, sizeof(ebuf), fmt, api_uri)) != NULL);
   mg_close_connection(conn);
   mg_stop(ctx);
 }
@@ -732,8 +672,8 @@ int __cdecl main(void) {
   test_base64_encode();
   test_match_prefix();
   test_remove_double_dots();
-  test_should_keep_alive();
   test_parse_http_message();
+  test_should_keep_alive();
   test_mg_download();
   test_mg_get_var();
   test_set_throttle();
-- 
GitLab