Selaa lähdekoodia

Add websocket server example, fix issue with WS subprotocol

The websocket subprotocol string was not parsed correctly: If multiple subprotocols have been proposed by a client,
sometimes a supported subprotocol was not found. This has been fixed while creating the example.
bel2125 3 vuotta sitten
vanhempi
commit
d41158bb3a
4 muutettua tiedostoa jossa 222 lisäystä ja 44 poistoa
  1. 4 0
      examples/ws_server/build.sh
  2. 192 0
      examples/ws_server/ws_server.c
  3. 1 0
      format.bat
  4. 25 44
      src/civetweb.c

+ 4 - 0
examples/ws_server/build.sh

@@ -0,0 +1,4 @@
+#!/bin/sh
+[ -e ws_server ] && rm ws_server
+
+gcc ../../src/civetweb.c ws_server.c -I../../include/ -lpthread -o ws_server -DNO_SSL -DNO_CGI -DNO_FILESYSTEM -DUSE_WEBSOCKET -Wall

+ 192 - 0
examples/ws_server/ws_server.c

@@ -0,0 +1,192 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h> /* for sleep() */
+
+#include "civetweb.h"
+
+
+/* Global options for this example. */
+static const char WS_URL[] = "/wsURL";
+static const char *SERVER_OPTIONS[] =
+    {"listening_ports", "8081", "num_threads", "10", NULL, NULL};
+
+/* Define websocket sub-protocols. */
+/* This must be static data, available between mg_start and mg_stop. */
+static const char subprotocol_bin[] = "Company.ProtoName.bin";
+static const char subprotocol_json[] = "Company.ProtoName.json";
+static const char *subprotocols[] = {subprotocol_bin, subprotocol_json, NULL};
+static struct mg_websocket_subprotocols wsprot = {2, subprotocols};
+
+
+/* Exit flag for the server */
+volatile int g_exit = 0;
+
+
+/* User defined data structure for websocket client context. */
+struct tClientContext {
+	uint32_t connectionNumber;
+	uint32_t demo_var;
+};
+
+
+/* Handler for new websocket connections. */
+static int
+ws_connect_handler(const struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Allocate data for websocket client context, and initialize context. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)calloc(1, sizeof(struct tClientContext));
+	if (!wsCliCtx) {
+		/* reject client */
+		return 1;
+	}
+	static uint32_t connectionCounter = 0; /* Example data: client number */
+	wsCliCtx->connectionNumber = __sync_add_and_fetch(&connectionCounter, 1);
+	mg_set_user_connection_data(
+	    conn, wsCliCtx); /* client context assigned to connection */
+
+	/* DEBUG: New client connected (but not ready to receive data yet). */
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	printf("Client %u connected with subprotocol: %s\n",
+	       wsCliCtx->connectionNumber,
+	       ri->acceptedWebSocketSubprotocol);
+
+	return 0;
+}
+
+
+/* Handler indicating the client is ready to receive data. */
+static void
+ws_ready_handler(struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	(void)ri; /* in this example, we do not need the request_info */
+
+	/* Send "hello" message. */
+	const char *hello = "{}";
+	size_t hello_len = strlen(hello);
+	mg_websocket_write(conn, MG_WEBSOCKET_OPCODE_TEXT, hello, hello_len);
+
+	/* DEBUG: New client ready to receive data. */
+	printf("Client %u ready to receive data\n", wsCliCtx->connectionNumber);
+}
+
+
+/* Handler indicating the client sent data to the server. */
+static int
+ws_data_handler(struct mg_connection *conn,
+                int opcode,
+                char *data,
+                size_t datasize,
+                void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+	const struct mg_request_info *ri = mg_get_request_info(conn);
+	(void)ri; /* in this example, we do not need the request_info */
+
+	/* DEBUG: Print data received from client. */
+	const char *messageType = "";
+	switch (opcode & 0xf) {
+	case MG_WEBSOCKET_OPCODE_TEXT:
+		messageType = "text";
+		break;
+	case MG_WEBSOCKET_OPCODE_BINARY:
+		messageType = "binary";
+		break;
+	case MG_WEBSOCKET_OPCODE_PING:
+		messageType = "ping";
+		break;
+	case MG_WEBSOCKET_OPCODE_PONG:
+		messageType = "pong";
+		break;
+	}
+	printf("Websocket received %lu bytes of %s data from client %u\n",
+	       (unsigned long)datasize,
+	       messageType,
+	       wsCliCtx->connectionNumber);
+
+	return 1;
+}
+
+
+/* Handler indicating the connection to the client is closing. */
+static void
+ws_close_handler(const struct mg_connection *conn, void *user_data)
+{
+	(void)user_data; /* unused */
+
+	/* Get websocket client context information. */
+	struct tClientContext *wsCliCtx =
+	    (struct tClientContext *)mg_get_user_connection_data(conn);
+
+	/* DEBUG: Client has left. */
+	printf("Client %u closing connection\n", wsCliCtx->connectionNumber);
+
+	/* Free memory allocated for client context in ws_connect_handler() call. */
+	free(wsCliCtx);
+}
+
+
+int
+main(int argc, char *argv[])
+{
+	/* Initialize CivetWeb library without OpenSSL/TLS support. */
+	mg_init_library(0);
+
+	/* Start the server using the advanced API. */
+	struct mg_callbacks callbacks = {0};
+	void *user_data = NULL;
+
+	struct mg_init_data mg_start_init_data = {0};
+	mg_start_init_data.callbacks = &callbacks;
+	mg_start_init_data.user_data = user_data;
+	mg_start_init_data.configuration_options = SERVER_OPTIONS;
+
+	struct mg_error_data mg_start_error_data = {0};
+	char errtxtbuf[256] = {0};
+	mg_start_error_data.text = errtxtbuf;
+	mg_start_error_data.text_buffer_size = sizeof(errtxtbuf);
+
+	struct mg_context *ctx =
+	    mg_start2(&mg_start_init_data, &mg_start_error_data);
+	if (!ctx) {
+		fprintf(stderr, "Cannot start server: %s\n", errtxtbuf);
+		mg_exit_library();
+		return 1;
+	}
+
+	/* Register the websocket callback functions. */
+	mg_set_websocket_handler_with_subprotocols(ctx,
+	                                           WS_URL,
+	                                           &wsprot,
+	                                           ws_connect_handler,
+	                                           ws_ready_handler,
+	                                           ws_data_handler,
+	                                           ws_close_handler,
+	                                           user_data);
+
+	/* Let the server run. */
+	printf("Websocket server running\n");
+	while (!g_exit) {
+		sleep(1);
+	}
+	printf("Websocket server stopping\n");
+
+	/* Stop server, disconnect all clients. Then deinitialize CivetWeb library.
+	 */
+	mg_stop(ctx);
+	mg_exit_library();
+}

+ 1 - 0
format.bat

@@ -41,4 +41,5 @@ clang-format -i fuzztest/fuzzmain.c
 clang-format -i examples/embedded_c/embedded_c.c
 clang-format -i examples/rest/rest.c
 clang-format -i examples/embed_certificate/ec_example.c
+clang-format -i examples/ws_server/ws_server.c
 

+ 25 - 44
src/civetweb.c

@@ -13035,28 +13035,35 @@ handle_websocket_request(struct mg_connection *conn,
 		                                          "Sec-WebSocket-Protocol",
 		                                          protocols,
 		                                          64);
+
 		if ((nbSubprotocolHeader > 0) && subprotocols) {
-			int cnt = 0;
-			int idx;
-			unsigned long len;
+
+			int headerNo, idx;
+			size_t len;
 			const char *sep, *curSubProtocol,
 			    *acceptedWebSocketSubprotocol = NULL;
 
-
 			/* look for matching subprotocol */
-			do {
-				const char *protocol = protocols[cnt];
-
-				do {
-					sep = strchr(protocol, ',');
-					curSubProtocol = protocol;
-					len = sep ? (unsigned long)(sep - protocol)
-					          : (unsigned long)strlen(protocol);
-					while (sep && isspace((unsigned char)*++sep))
-						; // ignore leading whitespaces
-					protocol = sep;
+			for (headerNo = 0; headerNo < nbSubprotocolHeader; headerNo++) {
+				/* There might be multiple headers ... */
+				const char *protocol = protocols[headerNo];
+				curSubProtocol = protocol;
+
+				/* ... and in every header there might be a , separated list */
+				while (!acceptedWebSocketSubprotocol && (*curSubProtocol)) {
+
+					while ((*curSubProtocol == ' ') || (*curSubProtocol == ','))
+						curSubProtocol++;
+					sep = strchr(curSubProtocol, ',');
+					if (sep) {
+						len = (size_t)(sep - curSubProtocol);
+					} else {
+						len = strlen(curSubProtocol);
+					}
 
 					for (idx = 0; idx < subprotocols->nb_subprotocols; idx++) {
+						// COMPARE: curSubProtocol ==
+						// subprotocols->subprotocols[idx]
 						if ((strlen(subprotocols->subprotocols[idx]) == len)
 						    && (strncmp(curSubProtocol,
 						                subprotocols->subprotocols[idx],
@@ -13067,38 +13074,12 @@ handle_websocket_request(struct mg_connection *conn,
 							break;
 						}
 					}
-				} while (sep && !acceptedWebSocketSubprotocol);
-			} while (++cnt < nbSubprotocolHeader
-			         && !acceptedWebSocketSubprotocol);
+					curSubProtocol += len;
+				}
+			}
 
 			conn->request_info.acceptedWebSocketSubprotocol =
 			    acceptedWebSocketSubprotocol;
-
-		} else if (nbSubprotocolHeader > 0) {
-			/* keep legacy behavior */
-			const char *protocol = protocols[0];
-
-			/* The protocol is a comma separated list of names. */
-			/* The server must only return one value from this list. */
-			/* First check if it is a list or just a single value. */
-			const char *sep = strrchr(protocol, ',');
-			if (sep == NULL) {
-				/* Just a single protocol -> accept it. */
-				conn->request_info.acceptedWebSocketSubprotocol = protocol;
-			} else {
-				/* Multiple protocols -> accept the last one. */
-				/* This is just a quick fix if the client offers multiple
-				 * protocols. The handler should have a list of accepted
-				 * protocols on his own
-				 * and use it to select one protocol among those the client
-				 * has
-				 * offered.
-				 */
-				while (isspace((unsigned char)*++sep)) {
-					; /* ignore leading whitespaces */
-				}
-				conn->request_info.acceptedWebSocketSubprotocol = sep;
-			}
 		}
 
 #if defined(USE_ZLIB) && defined(MG_EXPERIMENTAL_INTERFACES)