Browse Source

Support websocket PING PONG in the websocket server

Websocket ping pong is a method to check if the client (browser) is still
connected to a websocket.

See also #515
bel2125 7 năm trước cách đây
mục cha
commit
0dd24e7ec1
2 tập tin đã thay đổi với 87 bổ sung21 xóa
  1. 26 4
      docs/UserManual.md
  2. 61 17
      src/civetweb.c

+ 26 - 4
docs/UserManual.md

@@ -416,9 +416,31 @@ Setting the value to -1 will turn off linger.
 If the value is not set (or set to -2), CivetWeb will not set the linger
 option at all.
 
-Note: For consistency with other timeouts, the value is configured in
-milliseconds. However, the TCP socket layer usually only offers a timeout in 
-seconds, so the value should be an integer multiple of 1000.
+Note: For consistency with other timeout configurations, the value is 
+configured in milliseconds. However, the TCP socket layer usually only
+offers a timeout in seconds, so the value should be an integer multiple
+of 1000.
+
+### websocket\_timeout\_ms
+Timeout for network read and network write operations for websockets, WS(S),
+in milliseconds. If this value is not set, the value of request\_timeout\_ms
+is used for HTTP(S) as well as for WS(S). In case websocket\_timeout\_ms is
+set, HTTP(S) and WS(S) can use different timeouts.
+
+Note: This configuration value only exists, if the server has been built 
+with websocket support enabled.
+
+### enable_websocket_ping_pong `no`
+If this configuration value is set to `yes`, the server will send a
+websocket PING message to a websocket client, once the timeout set by
+websocket\_timeout\_ms expires. Clients (Web browsers) supporting this
+feature will reply with a PONG message.
+
+If this configuration value is set to `no`, the websocket server will
+close the connection, once the timeout expires.
+
+Note: This configuration value only exists, if the server has been built 
+with websocket support enabled.
 
 ### lua\_preload\_file
 This configuration option can be used to specify a Lua script file, which
@@ -465,7 +487,7 @@ In case CivetWeb is built with Lua and websocket support, Lua scripts may
 be used for websockets as well. Since websockets use a different URL scheme
 (ws, wss) than other http pages (http, https), the Lua scripts used for
 websockets may also be served from a different directory. By default,
-the document_root is used as websocket_root as well.
+the document\_root is used as websocket\_root as well.
 
 
 ### access\_control\_allow\_origin `*`

+ 61 - 17
src/civetweb.c

@@ -2121,6 +2121,7 @@ enum {
 	KEEP_ALIVE_TIMEOUT,
 #if defined(USE_WEBSOCKET)
 	WEBSOCKET_TIMEOUT,
+	ENABLE_WEBSOCKET_PING_PONG,
 #endif
 	DECODE_URL,
 #if defined(USE_LUA)
@@ -2214,7 +2215,8 @@ static struct mg_option config_options[] = {
     {"request_timeout_ms", MG_CONFIG_TYPE_NUMBER, "30000"},
     {"keep_alive_timeout_ms", MG_CONFIG_TYPE_NUMBER, "500"},
 #if defined(USE_WEBSOCKET)
-    {"websocket_timeout_ms", MG_CONFIG_TYPE_NUMBER, "30000"},
+    {"websocket_timeout_ms", MG_CONFIG_TYPE_NUMBER, NULL},
+    {"enable_websocket_ping_pong", MG_CONFIG_TYPE_BOOLEAN, "no"},
 #endif
     {"decode_url", MG_CONFIG_TYPE_BOOLEAN, "yes"},
 #if defined(USE_LUA)
@@ -11507,7 +11509,12 @@ read_websocket(struct mg_connection *conn,
 	 * dynamically allocated buffer if it is too large. */
 	unsigned char mem[4096];
 	unsigned char mop; /* mask flag and opcode */
+
+
 	double timeout = -1.0;
+	int enable_ping_pong =
+	    !mg_strcasecmp(conn->dom_ctx->config[ENABLE_WEBSOCKET_PING_PONG],
+	                   "yes");
 
 	if (conn->dom_ctx->config[WEBSOCKET_TIMEOUT]) {
 		timeout = atoi(conn->dom_ctx->config[WEBSOCKET_TIMEOUT]) / 1000.0;
@@ -11646,18 +11653,26 @@ read_websocket(struct mg_connection *conn,
 				}
 			}
 
-			/* Exit the loop if callback signals to exit (server side),
-			 * or "connection close" opcode received (client side). */
 			exit_by_callback = 0;
-			if ((ws_data_handler != NULL)
-			    && !ws_data_handler(conn,
-			                        mop,
-			                        (char *)data,
-			                        (size_t)data_len,
-			                        callback_data)) {
-				exit_by_callback = 1;
+			if (enable_ping_pong && ((mop & 0xF) == MG_WEBSOCKET_OPCODE_PONG)) {
+				/* filter PONG messages */
+				DEBUG_TRACE("PONG from %s:%u",
+				            conn->request_info.remote_addr,
+				            conn->request_info.remote_port);
+			} 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,
+				                        mop,
+				                        (char *)data,
+				                        (size_t)data_len,
+				                        callback_data)) {
+					exit_by_callback = 1;
+				}
 			}
 
+			/* It a buffer has been allocated, free it again */
 			if (data != mem) {
 				mg_free(data);
 			}
@@ -11684,6 +11699,25 @@ read_websocket(struct mg_connection *conn,
 			if (n > 0) {
 				conn->data_len += n;
 			} else {
+				if (!conn->phys_ctx->stop_flag && !conn->must_close) {
+					if (enable_ping_pong) {
+						int ret;
+
+						/* Send Websocket PING message */
+						DEBUG_TRACE("PING to %s:%u",
+						            conn->request_info.remote_addr,
+						            conn->request_info.remote_port);
+						ret = mg_websocket_write(conn,
+						                         MG_WEBSOCKET_OPCODE_PING,
+						                         NULL,
+						                         0);
+
+						if (ret <= 0) {
+							/* Error: send failed */
+							break;
+						}
+					}
+				}
 				/* Timeout: should retry */
 				/* TODO: get timeout def */
 			}
@@ -11703,9 +11737,8 @@ mg_websocket_write_exec(struct mg_connection *conn,
                         uint32_t masking_key)
 {
 	unsigned char header[14];
-	size_t headerLen = 1;
-
-	int retval = -1;
+	size_t headerLen;
+	int retval;
 
 #if defined(__GNUC__) || defined(__MINGW32__)
 /* Disable spurious conversion warning for GCC */
@@ -11761,11 +11794,17 @@ mg_websocket_write_exec(struct mg_connection *conn,
 	(void)mg_lock_connection(conn);
 
 	retval = mg_write(conn, header, headerLen);
-	if (dataLen > 0) {
-		retval = mg_write(conn, data, dataLen);
+	if (retval != (int)headerLen) {
+		/* Did not send complete header */
+		retval = -1;
+	} else {
+		if (dataLen > 0) {
+			retval = mg_write(conn, data, dataLen);
+		}
+		/* if dataLen == 0, the header length (2) is returned */
 	}
 
-	/* TODO: Remove this unlock as well, when lock is moved. */
+	/* TODO: Remove this unlock as well, when lock is removed. */
 	mg_unlock_connection(conn);
 
 	return retval;
@@ -11816,7 +11855,7 @@ mg_websocket_client_write(struct mg_connection *conn,
 	int retval = -1;
 	char *masked_data =
 	    (char *)mg_malloc_ctx(((dataLen + 7) / 4) * 4, conn->phys_ctx);
-	uint32_t masking_key = (uint32_t)get_random();
+	uint32_t masking_key = 0;
 
 	if (masked_data == NULL) {
 		/* Return -1 in an error case */
@@ -11827,6 +11866,11 @@ mg_websocket_client_write(struct mg_connection *conn,
 		return -1;
 	}
 
+	do {
+		/* Get a masking key - but not 0 */
+		masking_key = (uint32_t)get_random();
+	} while (masking_key == 0);
+
 	mask_data(data, dataLen, masking_key, masked_data);
 
 	retval = mg_websocket_write_exec(