1 #include <time.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 
6 #include "civetweb.h"
7 
8 /* Get an OS independent definition for sleep() */
9 #ifdef _WIN32
10 #include <windows.h>
11 #define sleep(x) Sleep((x)*1000)
12 #else
13 #include <unistd.h>
14 #endif
15 
16 
17 /* User defined client data structure */
18 struct tclient_data {
19 
20 	time_t started;
21 	time_t closed;
22 	struct tmsg_list_elem *msgs;
23 };
24 
25 struct tmsg_list_elem {
26 	time_t timestamp;
27 	void *data;
28 	size_t len;
29 	struct tmsg_list_elem *next;
30 };
31 
32 
33 /* Helper function to get a printable name for websocket opcodes */
34 static const char *
msgtypename(int flags)35 msgtypename(int flags)
36 {
37 	unsigned f = (unsigned)flags & 0xFu;
38 	switch (f) {
39 	case MG_WEBSOCKET_OPCODE_CONTINUATION:
40 		return "continuation";
41 	case MG_WEBSOCKET_OPCODE_TEXT:
42 		return "text";
43 	case MG_WEBSOCKET_OPCODE_BINARY:
44 		return "binary";
45 	case MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE:
46 		return "connection close";
47 	case MG_WEBSOCKET_OPCODE_PING:
48 		return "PING";
49 	case MG_WEBSOCKET_OPCODE_PONG:
50 		return "PONG";
51 	}
52 	return "unknown";
53 }
54 
55 
56 /* Callback for handling data received from the server */
57 static int
websocket_client_data_handler(struct mg_connection * conn,int flags,char * data,size_t data_len,void * user_data)58 websocket_client_data_handler(struct mg_connection *conn,
59                               int flags,
60                               char *data,
61                               size_t data_len,
62                               void *user_data)
63 {
64 	struct tclient_data *pclient_data = (struct tclient_data *)user_data;
65 	time_t now = time(NULL);
66 
67 	/* We may get some different message types (websocket opcodes).
68 	 * We will handle these messages differently. */
69 	int is_text = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_TEXT);
70 	int is_bin = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_BINARY);
71 	int is_ping = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PING);
72 	int is_pong = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_PONG);
73 	int is_close = ((flags & 0xf) == MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE);
74 
75 	/* Log output: We got some data */
76 	printf("%10.0f - Client received %lu bytes of %s data from server%s",
77 	       difftime(now, pclient_data->started),
78 	       (long unsigned)data_len,
79 	       msgtypename(flags),
80 	       (is_text ? ": " : ".\n"));
81 
82 	/* Check if we got a websocket PING request */
83 	if (is_ping) {
84 		/* PING requests are to check if the connection is broken.
85 		 * They should be replied with a PONG with the same data.
86 		 */
87 		mg_websocket_client_write(conn,
88 		                          MG_WEBSOCKET_OPCODE_PONG,
89 		                          data,
90 		                          data_len);
91 		return 1;
92 	}
93 
94 	/* Check if we got a websocket PONG message */
95 	if (is_pong) {
96 		/* A PONG message may be a response to our PING, but
97 		 * it is also allowed to send unsolicited PONG messages
98 		 * send by the server to check some lower level TCP
99 		 * connections. Just ignore all kinds of PONGs. */
100 		return 1;
101 	}
102 
103 	/* It we got a websocket TEXT message, handle it ... */
104 	if (is_text) {
105 		struct tmsg_list_elem *p;
106 		struct tmsg_list_elem **where = &(pclient_data->msgs);
107 
108 		/* ... by printing it to the log ... */
109 		fwrite(data, 1, data_len, stdout);
110 		printf("\n");
111 
112 		/* ... and storing it (OOM ignored for simplicity). */
113 		p = (struct tmsg_list_elem *)malloc(sizeof(struct tmsg_list_elem));
114 		p->timestamp = now;
115 		p->data = malloc(data_len);
116 		memcpy(p->data, data, data_len);
117 		p->len = data_len;
118 		p->next = NULL;
119 		while (*where != NULL) {
120 			where = &((*where)->next);
121 		}
122 		*where = p;
123 	}
124 
125 	/* Another option would be BINARY data. */
126 	if (is_bin) {
127 		/* In this example, we just ignore binary data.
128 		 * According to some blogs, discriminating TEXT and
129 		 * BINARY may be some remains from earlier drafts
130 		 * of the WebSocket protocol.
131 		 * Anyway, a real application will usually use
132 		 * either TEXT or BINARY. */
133 	}
134 
135 	/* It could be a CLOSE message as well. */
136 	if (is_close) {
137 		printf("%10.0f - Goodbye\n", difftime(now, pclient_data->started));
138 		return 0;
139 	}
140 
141 	/* Return 1 to keep the connection open */
142 	return 1;
143 }
144 
145 
146 /* Callback for handling a close message received from the server */
147 static void
websocket_client_close_handler(const struct mg_connection * conn,void * user_data)148 websocket_client_close_handler(const struct mg_connection *conn,
149                                void *user_data)
150 {
151 	struct tclient_data *pclient_data = (struct tclient_data *)user_data;
152 
153 	pclient_data->closed = time(NULL);
154 	printf("%10.0f - Client: Close handler\n",
155 	       difftime(pclient_data->closed, pclient_data->started));
156 }
157 
158 
159 /* Websocket client test function */
160 void
run_websocket_client(const char * host,int port,int secure,const char * path,const char * greetings)161 run_websocket_client(const char *host,
162                      int port,
163                      int secure,
164                      const char *path,
165                      const char *greetings)
166 {
167 	char err_buf[100] = {0};
168 	struct mg_connection *client_conn;
169 	struct tclient_data *pclient_data;
170 	int i;
171 
172 	/* Allocate some memory for callback specific data.
173 	 * For simplicity, we ignore OOM handling in this example. */
174 	pclient_data = (struct tclient_data *)malloc(sizeof(struct tclient_data));
175 
176 	/* Store start time in the private structure */
177 	pclient_data->started = time(NULL);
178 	pclient_data->closed = 0;
179 	pclient_data->msgs = NULL;
180 
181 	/* Log first action (time = 0.0) */
182 	printf("%10.0f - Connecting to %s:%i\n", 0.0, host, port);
183 
184 	/* Connect to the given WS or WSS (WS secure) server */
185 	client_conn = mg_connect_websocket_client(host,
186 	                                          port,
187 	                                          secure,
188 	                                          err_buf,
189 	                                          sizeof(err_buf),
190 	                                          path,
191 	                                          NULL,
192 	                                          websocket_client_data_handler,
193 	                                          websocket_client_close_handler,
194 	                                          pclient_data);
195 
196 	/* Check if connection is possible */
197 	if (client_conn == NULL) {
198 		printf("mg_connect_websocket_client error: %s\n", err_buf);
199 		return;
200 	}
201 
202 	/* Connection established */
203 	printf("%10.0f - Connected\n", difftime(time(NULL), pclient_data->started));
204 
205 	/* If there are greetings to send, do it now */
206 	if (greetings) {
207 		printf("%10.0f - Sending greetings\n",
208 		       difftime(time(NULL), pclient_data->started));
209 
210 		mg_websocket_client_write(client_conn,
211 		                          MG_WEBSOCKET_OPCODE_TEXT,
212 		                          greetings,
213 		                          strlen(greetings));
214 	}
215 
216 	/* Wait for some seconds */
217 	sleep(5);
218 
219 	/* Does the server play "ping pong" ? */
220 	for (i = 0; i < 5; i++) {
221 		/* Send a PING message every 5 seconds. */
222 		printf("%10.0f - Sending PING\n",
223 		       difftime(time(NULL), pclient_data->started));
224 		mg_websocket_client_write(client_conn,
225 		                          MG_WEBSOCKET_OPCODE_PING,
226 		                          (const char *)&i,
227 		                          sizeof(int));
228 		sleep(5);
229 	}
230 
231 	/* Wait a while */
232 	/* If we do not use "ping pong", the server will probably
233 	 * close the connection with a timeout earlier. */
234 	sleep(150);
235 
236 	/* Send greetings again */
237 	if (greetings) {
238 		printf("%10.0f - Sending greetings again\n",
239 		       difftime(time(NULL), pclient_data->started));
240 
241 		mg_websocket_client_write(client_conn,
242 		                          MG_WEBSOCKET_OPCODE_TEXT,
243 		                          greetings,
244 		                          strlen(greetings));
245 	}
246 
247 	/* Wait for some seconds */
248 	sleep(5);
249 
250 	/* Send some "song text": http://www.99-bottles-of-beer.net/ */
251 	{
252 		char txt[128];
253 		int b = 99; /* start with 99 bottles */
254 
255 		while (b > 0) {
256 			/* Send "b bottles" text line. */
257 			sprintf(txt,
258 			        "%i bottle%s of beer on the wall, "
259 			        "%i bottle%s of beer.",
260 			        b,
261 			        ((b != 1) ? "s" : ""),
262 			        b,
263 			        ((b != 1) ? "s" : ""));
264 			mg_websocket_client_write(client_conn,
265 			                          MG_WEBSOCKET_OPCODE_TEXT,
266 			                          txt,
267 			                          strlen(txt));
268 
269 			/* Take a breath. */
270 			sleep(1);
271 
272 			/* Drink a bottle */
273 			b--;
274 
275 			/* Send "remaining bottles" text line. */
276 			if (b) {
277 				sprintf(txt,
278 				        "Take one down and pass it around, "
279 				        "%i bottle%s of beer on the wall.",
280 				        b,
281 				        ((b != 1) ? "s" : ""));
282 			} else {
283 				strcpy(txt,
284 				       "Take one down and pass it around, "
285 				       "no more bottles of beer on the wall.");
286 			}
287 			mg_websocket_client_write(client_conn,
288 			                          MG_WEBSOCKET_OPCODE_TEXT,
289 			                          txt,
290 			                          strlen(txt));
291 
292 			/* Take a breath. */
293 			sleep(2);
294 		}
295 
296 		/* Send "no more bottles" text line. */
297 		strcpy(txt,
298 		       "No more bottles of beer on the wall, "
299 		       "no more bottles of beer.");
300 		mg_websocket_client_write(client_conn,
301 		                          MG_WEBSOCKET_OPCODE_TEXT,
302 		                          txt,
303 		                          strlen(txt));
304 
305 		/* Take a breath. */
306 		sleep(1);
307 
308 		/* Buy new bottles. */
309 		b = 99;
310 
311 		/* Send "buy some more" text line. */
312 		sprintf(txt,
313 		        "Go to the store and buy some more, "
314 		        "%i bottle%s of beer on the wall.",
315 		        b,
316 		        ((b != 1) ? "s" : ""));
317 		mg_websocket_client_write(client_conn,
318 		                          MG_WEBSOCKET_OPCODE_TEXT,
319 		                          txt,
320 		                          strlen(txt));
321 	}
322 
323 	/* Wait for some seconds */
324 	sleep(5);
325 
326 	/* Somewhat boring conversation, isn't it?
327 	 * Tell the server we have to leave. */
328 	printf("%10.0f - Sending close message\n",
329 	       difftime(time(NULL), pclient_data->started));
330 	mg_websocket_client_write(client_conn,
331 	                          MG_WEBSOCKET_OPCODE_CONNECTION_CLOSE,
332 	                          NULL,
333 	                          0);
334 
335 	/* We don't need to wait, this is just to have the log timestamp
336 	 * a second later, and to not log from the handlers and from
337 	 * here the same time (printf to stdout is not thread-safe, but
338 	 * adding flock or mutex or an explicit logging function makes
339 	 * this example unnecessarily complex). */
340 	sleep(5);
341 
342 	/* Connection should be closed by now. */
343 	printf("%10.0f - Connection state: %s\n",
344 	       difftime(time(NULL), pclient_data->started),
345 	       ((pclient_data->closed == 0) ? "open" : "closed"));
346 
347 	/* Close client connection */
348 	mg_close_connection(client_conn);
349 	printf("%10.0f - End of test\n",
350 	       difftime(time(NULL), pclient_data->started));
351 
352 
353 	/* Print collected data */
354 	printf("\n\nPrint all text messages from server again:\n");
355 	{
356 		struct tmsg_list_elem **where = &(pclient_data->msgs);
357 		while (*where != NULL) {
358 			printf("%10.0f - [%5lu] ",
359 			       difftime((*where)->timestamp, pclient_data->started),
360 			       (unsigned long)(*where)->len);
361 			fwrite((const char *)(*where)->data, 1, (*where)->len, stdout);
362 			printf("\n");
363 
364 			where = &((*where)->next);
365 		}
366 	}
367 
368 	/* Free collected data */
369 	{
370 		struct tmsg_list_elem **where = &(pclient_data->msgs);
371 		void *p1 = 0;
372 		void *p2 = 0;
373 		while (*where != NULL) {
374 			free((*where)->data);
375 			free(p2);
376 			p2 = p1;
377 			p1 = *where;
378 
379 			where = &((*where)->next);
380 		}
381 		free(p2);
382 		free(p1);
383 	}
384 	free(pclient_data);
385 }
386 
387 
388 /* main will initialize the CivetWeb library
389  * and start the WebSocket client test function */
390 int
main(int argc,char * argv[])391 main(int argc, char *argv[])
392 {
393 	const char *greetings = "Hello World!";
394 
395 	const char *host = "echo.websocket.org";
396 	const char *path = "/";
397 
398 #if defined(NO_SSL)
399 	const int port = 80;
400 	const int secure = 0;
401 	mg_init_library(0);
402 #else
403 	const int port = 443;
404 	const int secure = 1;
405 	mg_init_library(MG_FEATURES_SSL);
406 #endif
407 
408 	run_websocket_client(host, port, secure, path, greetings);
409 }
410