Parcourir la source

first prototype for websocket compression

Johan De Taeye il y a 4 ans
Parent
commit
df526366c4
1 fichiers modifiés avec 183 ajouts et 29 suppressions
  1. 183 29
      src/civetweb.c

+ 183 - 29
src/civetweb.c

@@ -2960,6 +2960,23 @@ enum {
 	PROTOCOL_TYPE_HTTP2 = 2
 };
 
+#define mg_cry_internal(conn, fmt, ...)                                        \
+	mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
+
+#define mg_cry_ctx_internal(ctx, fmt, ...)                                     \
+	mg_cry_internal_wrap(NULL, ctx, __func__, __LINE__, fmt, __VA_ARGS__)
+
+static void mg_cry_internal_wrap(const struct mg_connection *conn,
+                                 struct mg_context *ctx,
+                                 const char *func,
+                                 unsigned line,
+                                 const char *fmt,
+                                 ...) PRINTF_ARGS(5, 6);
+
+#if defined(USE_ZLIB)
+#include "mod_zlib.inl"
+#endif
+
 struct mg_connection {
 	int connection_type; /* see CONNECTION_TYPE_* above */
 	int protocol_type;   /* see PROTOCOL_TYPE_*: 0=http/1.x, 1=ws, 2=http/2 */
@@ -3010,6 +3027,17 @@ struct mg_connection {
 	                       * pages */
 #if defined(USE_WEBSOCKET)
 	int in_websocket_handling; /* 1 if in read_websocket */
+#if defined(USE_ZLIB)
+	/* Parameters for websocket data compression according to https://tools.ietf.org/html/rfc7692 */
+    int websocket_deflate_server_max_windows_bits;
+    int websocket_deflate_client_max_windows_bits;
+    int websocket_deflate_server_no_context_takeover;
+    int websocket_deflate_client_no_context_takeover;
+    int websocket_deflate_initialized;
+    int websocket_deflate_flush;
+    z_stream websocket_deflate_state;
+    z_stream websocket_inflate_state;
+#endif
 #endif
 	int handled_requests; /* Number of requests handled by this connection
 	                       */
@@ -3041,20 +3069,6 @@ struct de {
 };
 
 
-#define mg_cry_internal(conn, fmt, ...)                                        \
-	mg_cry_internal_wrap(conn, NULL, __func__, __LINE__, fmt, __VA_ARGS__)
-
-#define mg_cry_ctx_internal(ctx, fmt, ...)                                     \
-	mg_cry_internal_wrap(NULL, ctx, __func__, __LINE__, fmt, __VA_ARGS__)
-
-static void mg_cry_internal_wrap(const struct mg_connection *conn,
-                                 struct mg_context *ctx,
-                                 const char *func,
-                                 unsigned line,
-                                 const char *fmt,
-                                 ...) PRINTF_ARGS(5, 6);
-
-
 #if !defined(NO_THREAD_NAME)
 #if defined(_WIN32) && defined(_MSC_VER)
 /* Set the thread name for debugging purposes in Visual Studio
@@ -4283,6 +4297,71 @@ get_req_headers(const struct mg_request_info *ri,
 #endif
 
 
+#if defined(USE_WEBSOCKET) && defined(USE_ZLIB)
+int websocket_deflate_initialize(struct mg_connection *conn) {
+  fflush(stdout);
+  z_stream websocket_deflate_state;
+  int websocket_deflate_server_max_windows_bits;
+  int websocket_deflate_client_max_windows_bits;
+  int websocket_deflate_server_no_context_takeover;
+  int websocket_deflate_client_no_context_takeover;
+
+  uint8_t deflate_bits;
+  uint8_t inflate_bits;
+
+  // if (server)
+  deflate_bits = conn->websocket_deflate_server_max_windows_bits;
+  inflate_bits = conn->websocket_deflate_client_max_windows_bits;
+  /*
+      deflate_bits = conn->websocket_deflate_client_max_windows_bits;
+      inflate_bits = conn->websocket_deflate_server_max_windows_bits;
+      */
+
+  int ret = deflateInit2(&conn->websocket_deflate_state, Z_DEFAULT_COMPRESSION,
+                         Z_DEFLATED, -1 * deflate_bits,
+                         4,  // memory level 1-9
+                         Z_DEFAULT_STRATEGY);
+  if (ret != Z_OK) return ret;
+
+  ret = inflateInit2(&conn->websocket_inflate_state, -1 * inflate_bits);
+  if (ret != Z_OK) return ret;
+
+  /*
+  if ((m_server_no_context_takeover && is_server) ||
+      (m_client_no_context_takeover && !is_server)) {
+      */
+  conn->websocket_deflate_flush = Z_FULL_FLUSH;
+  //} else
+  //  conn->websocket_deflate_flush = Z_SYNC_FLUSH;
+
+  conn->websocket_deflate_initialized = 1;
+  return Z_OK;
+}
+
+void websocket_deflate_negotiate(struct mg_connection *conn) {
+  const char *extensions = mg_get_header(conn, "Sec-WebSocket-Extensions");
+  if (extensions && strstr(extensions, "permessage-deflate")) {
+    conn->accept_gzip = 1;
+    conn->websocket_deflate_client_max_windows_bits = 15;
+    conn->websocket_deflate_server_max_windows_bits = 15;
+  } else {
+    conn->accept_gzip = 0;
+    conn->websocket_deflate_client_max_windows_bits = 0;
+    conn->websocket_deflate_server_max_windows_bits = 0;
+  }
+  conn->websocket_deflate_initialized = 0;
+}
+
+void websocket_deflate_response(struct mg_connection *conn) {
+  if (conn->accept_gzip) {
+    mg_printf(conn,
+              "Sec-WebSocket-Extensions: permessage-deflate; "
+              "client_no_context_takeover; server_no_context_takeover\r\n");
+  };
+}
+#endif
+
+
 const char *
 mg_get_header(const struct mg_connection *conn, const char *name)
 {
@@ -10039,11 +10118,6 @@ fclose_on_exec(struct mg_file_access *filep, struct mg_connection *conn)
 }
 
 
-#if defined(USE_ZLIB)
-#include "mod_zlib.inl"
-#endif
-
-
 #if !defined(NO_FILESYSTEMS)
 static void
 handle_static_file_request(struct mg_connection *conn,
@@ -12534,6 +12608,12 @@ send_websocket_handshake(struct mg_connection *conn, const char *websock_key)
 	          "Connection: Upgrade\r\n"
 	          "Sec-WebSocket-Accept: %s\r\n",
 	          b64_sha);
+
+#if defined(USE_ZLIB)
+    // Send negotiated compression extension parameters
+    websocket_deflate_response(conn);
+#endif
+
 	if (conn->request_info.acceptedWebSocketSubprotocol) {
 		mg_printf(conn,
 		          "Sec-WebSocket-Protocol: %s\r\n\r\n",
@@ -12772,14 +12852,51 @@ read_websocket(struct mg_connection *conn,
 			} else {
 				/* Exit the loop if callback signals to exit (server side),
 				 * or "connection close" opcode received (client side). */
-				if ((ws_data_handler != NULL)
-				    && !ws_data_handler(conn,
+				if (ws_data_handler != NULL) {
+#if defined(USE_ZLIB)
+				  if (mop & 0x40) {
+					/* Inflate the data received if bit RSV1 is set. */
+					if (!conn->websocket_deflate_initialized) {
+					  if (websocket_deflate_initialize(conn) != Z_OK)
+						exit_by_callback = 1;
+					}
+					if (!exit_by_callback) {
+					  Bytef *inflated = mg_calloc(10 * 1024 * 1024, sizeof(Bytef));
+					  // Add trailing 0x00 0x00 0xff 0xff bytes
+					  data[data_len] = '\x00';
+					  data[data_len + 1] = '\x00';
+					  data[data_len + 2] = '\xff';
+					  data[data_len + 3] = '\xff';
+					  conn->websocket_inflate_state.avail_in = data_len + 4;
+					  conn->websocket_inflate_state.next_in = data;
+					  conn->websocket_inflate_state.avail_out = 10 * 1024 * 1024;
+					  conn->websocket_inflate_state.next_out = inflated;
+					  int ret = inflate(&conn->websocket_inflate_state, Z_SYNC_FLUSH);
+					  // TODO loop allocate bigger buffer if needed
+					  if (ret == Z_NEED_DICT || ret == Z_DATA_ERROR ||
+						  ret == Z_MEM_ERROR || ret < 0)
+						exit_by_callback = 1;
+					  inflated[10 * 1024 * 1024 -
+							   conn->websocket_inflate_state.avail_out] = '\0';
+					  // if (conn->websocket_inflate_state.avail_out == 0)
+					  //   TODO output buffer overflow
+					  if (!ws_data_handler(conn, mop, (char *)inflated,
+										   10 * 1024 * 1024 -
+											   conn->websocket_inflate_state.avail_out,
+										   callback_data)) {
+						exit_by_callback = 1;
+					  }
+					}
+				  } else
+#endif
+        	  if (!ws_data_handler(conn,
 				                        mop,
 				                        (char *)data,
 				                        (size_t)data_len,
 				                        callback_data)) {
 					exit_by_callback = 1;
 				}
+				}
 			}
 
 			/* It a buffer has been allocated, free it again */
@@ -12877,12 +12994,46 @@ mg_websocket_write_exec(struct mg_connection *conn,
 	size_t headerLen;
 	int retval;
 
+#if defined(USE_ZLIB)
+    uLong deflated_size;
+    Bytef *deflated;
+    // Deflate websocket messages over 100kb
+    int use_deflate = dataLen > 100 * 1024 && conn->accept_gzip;
+#endif
+
 #if defined(GCC_DIAGNOSTIC)
 /* Disable spurious conversion warning for GCC */
 #pragma GCC diagnostic push
 #pragma GCC diagnostic ignored "-Wconversion"
 #endif
 
+	/* TODO: Check if this lock should be moved to user land.
+	 * Currently the server sets this lock for websockets, but
+	 * not for any other connection. It must be set for every
+	 * conn read/written by more than one thread, no matter if
+	 * it is a websocket or regular connection. */
+	(void)mg_lock_connection(conn);
+
+#if defined(USE_ZLIB)
+	if (use_deflate) {
+		if (!conn->websocket_deflate_initialized) {
+		  if (websocket_deflate_initialize(conn) != Z_OK) return 0;
+		}
+
+		// Deflating the message
+		header[0] = 0xC0u | (unsigned char)((unsigned)opcode & 0xf);
+		conn->websocket_deflate_state.avail_in = dataLen;
+		conn->websocket_deflate_state.next_in = (unsigned char *)data;
+		deflated_size = compressBound(dataLen);
+		deflated = mg_calloc(deflated_size, sizeof(Bytef));
+		conn->websocket_deflate_state.avail_out = deflated_size;
+		conn->websocket_deflate_state.next_out = deflated =
+			mg_calloc(deflated_size, sizeof(Bytef));
+		deflate(&conn->websocket_deflate_state, conn->websocket_deflate_flush);
+		dataLen = deflated_size - conn->websocket_deflate_state.avail_out -
+				  4;  // Strip trailing 0x00 0x00 0xff 0xff bytes
+	} else
+#endif
 	header[0] = 0x80u | (unsigned char)((unsigned)opcode & 0xf);
 
 #if defined(GCC_DIAGNOSTIC)
@@ -12923,20 +13074,19 @@ mg_websocket_write_exec(struct mg_connection *conn,
 	 * push(), although that is only a problem if the packet is large or
 	 * outgoing buffer is full). */
 
-	/* TODO: Check if this lock should be moved to user land.
-	 * Currently the server sets this lock for websockets, but
-	 * not for any other connection. It must be set for every
-	 * conn read/written by more than one thread, no matter if
-	 * it is a websocket or regular connection. */
-	(void)mg_lock_connection(conn);
-
 	retval = mg_write(conn, header, headerLen);
 	if (retval != (int)headerLen) {
 		/* Did not send complete header */
 		retval = -1;
 	} else {
 		if (dataLen > 0) {
-			retval = mg_write(conn, data, dataLen);
+#if defined(USE_ZLIB)
+		  if (use_deflate) {
+			  retval = mg_write(conn, deflated, dataLen);
+			  mg_free(deflated);
+		  } else
+#endif
+			  retval = mg_write(conn, data, dataLen);
 		}
 		/* if dataLen == 0, the header length (2) is returned */
 	}
@@ -13153,6 +13303,10 @@ handle_websocket_request(struct mg_connection *conn,
 			}
 		}
 
+#if defined(USE_ZLIB)
+        websocket_deflate_negotiate(conn);
+#endif
+
 		if ((ws_connect_handler != NULL)
 		    && (ws_connect_handler(conn, cbData) != 0)) {
 			/* C callback has returned non-zero, do not proceed with