瀏覽代碼

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 年之前
父節點
當前提交
0dd24e7ec1
共有 2 個文件被更改,包括 87 次插入21 次删除
  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
 If the value is not set (or set to -2), CivetWeb will not set the linger
 option at all.
 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
 ### lua\_preload\_file
 This configuration option can be used to specify a Lua script file, which
 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
 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
 (ws, wss) than other http pages (http, https), the Lua scripts used for
 websockets may also be served from a different directory. By default,
 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 `*`
 ### access\_control\_allow\_origin `*`

+ 61 - 17
src/civetweb.c

@@ -2121,6 +2121,7 @@ enum {
 	KEEP_ALIVE_TIMEOUT,
 	KEEP_ALIVE_TIMEOUT,
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
 	WEBSOCKET_TIMEOUT,
 	WEBSOCKET_TIMEOUT,
+	ENABLE_WEBSOCKET_PING_PONG,
 #endif
 #endif
 	DECODE_URL,
 	DECODE_URL,
 #if defined(USE_LUA)
 #if defined(USE_LUA)
@@ -2214,7 +2215,8 @@ static struct mg_option config_options[] = {
     {"request_timeout_ms", MG_CONFIG_TYPE_NUMBER, "30000"},
     {"request_timeout_ms", MG_CONFIG_TYPE_NUMBER, "30000"},
     {"keep_alive_timeout_ms", MG_CONFIG_TYPE_NUMBER, "500"},
     {"keep_alive_timeout_ms", MG_CONFIG_TYPE_NUMBER, "500"},
 #if defined(USE_WEBSOCKET)
 #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
 #endif
     {"decode_url", MG_CONFIG_TYPE_BOOLEAN, "yes"},
     {"decode_url", MG_CONFIG_TYPE_BOOLEAN, "yes"},
 #if defined(USE_LUA)
 #if defined(USE_LUA)
@@ -11507,7 +11509,12 @@ read_websocket(struct mg_connection *conn,
 	 * dynamically allocated buffer if it is too large. */
 	 * dynamically allocated buffer if it is too large. */
 	unsigned char mem[4096];
 	unsigned char mem[4096];
 	unsigned char mop; /* mask flag and opcode */
 	unsigned char mop; /* mask flag and opcode */
+
+
 	double timeout = -1.0;
 	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]) {
 	if (conn->dom_ctx->config[WEBSOCKET_TIMEOUT]) {
 		timeout = atoi(conn->dom_ctx->config[WEBSOCKET_TIMEOUT]) / 1000.0;
 		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;
 			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) {
 			if (data != mem) {
 				mg_free(data);
 				mg_free(data);
 			}
 			}
@@ -11684,6 +11699,25 @@ read_websocket(struct mg_connection *conn,
 			if (n > 0) {
 			if (n > 0) {
 				conn->data_len += n;
 				conn->data_len += n;
 			} else {
 			} 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 */
 				/* Timeout: should retry */
 				/* TODO: get timeout def */
 				/* TODO: get timeout def */
 			}
 			}
@@ -11703,9 +11737,8 @@ mg_websocket_write_exec(struct mg_connection *conn,
                         uint32_t masking_key)
                         uint32_t masking_key)
 {
 {
 	unsigned char header[14];
 	unsigned char header[14];
-	size_t headerLen = 1;
-
-	int retval = -1;
+	size_t headerLen;
+	int retval;
 
 
 #if defined(__GNUC__) || defined(__MINGW32__)
 #if defined(__GNUC__) || defined(__MINGW32__)
 /* Disable spurious conversion warning for GCC */
 /* Disable spurious conversion warning for GCC */
@@ -11761,11 +11794,17 @@ mg_websocket_write_exec(struct mg_connection *conn,
 	(void)mg_lock_connection(conn);
 	(void)mg_lock_connection(conn);
 
 
 	retval = mg_write(conn, header, headerLen);
 	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);
 	mg_unlock_connection(conn);
 
 
 	return retval;
 	return retval;
@@ -11816,7 +11855,7 @@ mg_websocket_client_write(struct mg_connection *conn,
 	int retval = -1;
 	int retval = -1;
 	char *masked_data =
 	char *masked_data =
 	    (char *)mg_malloc_ctx(((dataLen + 7) / 4) * 4, conn->phys_ctx);
 	    (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) {
 	if (masked_data == NULL) {
 		/* Return -1 in an error case */
 		/* Return -1 in an error case */
@@ -11827,6 +11866,11 @@ mg_websocket_client_write(struct mg_connection *conn,
 		return -1;
 		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);
 	mask_data(data, dataLen, masking_key, masked_data);
 
 
 	retval = mg_websocket_write_exec(
 	retval = mg_websocket_write_exec(