aboutsummaryrefslogtreecommitdiff
path: root/src/microhttpd_ws/test_websocket_browser.c
diff options
context:
space:
mode:
authorDavid Gausmann <David.Gausmann@measX.com>2021-10-17 19:53:09 +0200
committerEvgeny Grin (Karlson2k) <k2k@narod.ru>2021-10-31 16:02:31 +0300
commitddad59c1e3a5d20b19003b1d47673501b0665b36 (patch)
tree8cd367e724477bc092d7dd38c83cd15f4c12c95f /src/microhttpd_ws/test_websocket_browser.c
parentffbb000f890f23e14d972a6660168aff4a97b66c (diff)
downloadlibmicrohttpd-ddad59c1e3a5d20b19003b1d47673501b0665b36.tar.gz
libmicrohttpd-ddad59c1e3a5d20b19003b1d47673501b0665b36.zip
websocket update
- added API documentation to libmicrohttpd.texi - added websocket tutorial chapter to libmicrohttpd-tutorial and an much easier example for the tutorial - added additional helper functions to ease the HTTP websocket handshake - the code can now be compiled on Linux without errors - changed sha1.c and sha1.h to the files provided by Evgeny (I replaced those files in src/microhttpd_ws/ with the files from src/microhttpd/ - maybe there is a smarter way...?) - removed dependency for "htons" and "htonl" (these functions are now implemented in MHD_websocket.c; no need for OS-dependent files anymore) - added an additional test script for testing of the library with any webbrowser (for manual practice test) - several bugfixes - parameters renamed - special things clarified (fragmentation, RNG for client mode) The new version of the API is at some points incompatible with the old version, but since it was in an experimental phase and it didn't compile on Linux, I guess this shouldn't bother anyone. From my point of view, I am now finished with the library and it could go out of experimental.
Diffstat (limited to 'src/microhttpd_ws/test_websocket_browser.c')
-rw-r--r--src/microhttpd_ws/test_websocket_browser.c563
1 files changed, 563 insertions, 0 deletions
diff --git a/src/microhttpd_ws/test_websocket_browser.c b/src/microhttpd_ws/test_websocket_browser.c
new file mode 100644
index 00000000..dfbcd116
--- /dev/null
+++ b/src/microhttpd_ws/test_websocket_browser.c
@@ -0,0 +1,563 @@
1/*
2 This file is part of libmicrohttpd
3 Copyright (C) 2021 David Gausmann
4
5 libmicrohttpd is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 option) any later version.
9
10 libmicrohttpd is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with libmicrohttpd; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20/**
21 * @file test_websocket_browser.c
22 * @brief Testcase for WebSocket decoding/encoding with external browser
23 * @author David Gausmann
24 */
25#include <sys/types.h>
26#ifndef _WIN32
27#include <sys/select.h>
28#include <sys/socket.h>
29#include <fcntl.h>
30#else
31#include <winsock2.h>
32#endif
33#include "microhttpd.h"
34#include "microhttpd_ws.h"
35#include <stdlib.h>
36#include <string.h>
37#include <stdio.h>
38#include <stdint.h>
39#include <time.h>
40#include <errno.h>
41
42#define PORT 80
43
44#define PAGE \
45 "<!DOCTYPE html>\n" \
46 "<html>\n" \
47 "<head>\n" \
48 "<meta charset=\"UTF-8\">\n" \
49 "<title>Websocket External Test with Webbrowser</title>\n" \
50 "<script>\n" \
51 "\n" \
52 "let current_mode = 0;\n" \
53 "let current_step = 0;\n" \
54 "let sent_payload = null;\n" \
55 "let charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_!@%&/\\\\';\n" \
56 "let step_to_bytes = [ 0, 1, 2, 3, 122, 123, 124, 125, 126, 127, 128, 32766, 32767, 32768, 65534, 65535, 65536, 65537, 1048576, 10485760 ];\n" \
57 "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \
58 " + '://' +\n" \
59 " window.location.host + '/websocket';\n" \
60 "let socket = null;\n" \
61 "\n" \
62 "window.onload = function (event) {\n" \
63 " if (!window.WebSocket) {\n" \
64 " document.write ('ERROR: The WebSocket class is not supported by your browser.<br>');\n" \
65 " }\n" \
66 " if (!window.fetch) {\n" \
67 " document.write ('ERROR: The fetch-API is not supported by your browser.<br>');\n" \
68 " }\n" \
69 " document.write ('Starting tests.<br>');\n" \
70 " runTest ();\n" \
71 "}\n" \
72 "\n" \
73 "function runTest () {\n" \
74 " switch (current_mode) {\n" \
75 " case 0:\n" \
76 " document.write ('TEXT');\n" \
77 " break;\n" \
78 " case 1:\n" \
79 " document.write ('BINARY');\n" \
80 " break;\n" \
81 " }\n" \
82 " document.write (', ' + step_to_bytes[current_step] + ' Bytes: ');\n" \
83 " socket = new WebSocket(url);\n" \
84 " socket.binaryType = 'arraybuffer';\n" \
85 " socket.onopen = function (event) {\n" \
86 " switch (current_mode) {\n" \
87 " case 0:\n" \
88 " sent_payload = randomText (step_to_bytes[current_step]);\n" \
89 " socket.send (sent_payload);\n" \
90 " break;\n" \
91 " case 1:\n" \
92 " sent_payload = randomBinary (step_to_bytes[current_step]);\n" \
93 " socket.send (sent_payload);\n" \
94 " break;\n" \
95 " }\n" \
96 " }\n" \
97 "\n" \
98 " socket.onclose = function (event) {\n" \
99 " socket.onmessage = null;\n" \
100 " socket.onclose = null;\n" \
101 " socket.onerror = null;\n" \
102 " document.write ('CLOSED unexpectedly.<br>');\n" \
103 " notifyError ();\n" \
104 " }\n" \
105 "\n" \
106 " socket.onerror = function (event) {\n" \
107 " socket.onmessage = null;\n" \
108 " socket.onclose = null;\n" \
109 " socket.onerror = null;\n" \
110 " document.write ('ERROR.<br>');\n" \
111 " notifyError ();\n" \
112 " }\n" \
113 "\n" \
114 " socket.onmessage = async function (event) {\n" \
115 " if (compareData (event.data, sent_payload)) {\n" \
116 " document.write ('SUCCESS.<br>');\n" \
117 " socket.onmessage = null;\n" \
118 " socket.onclose = null;\n" \
119 " socket.onerror = null;\n" \
120 " socket.close();\n" \
121 " socket = null;\n" \
122 " if (step_to_bytes.length <= ++current_step) {\n" \
123 " current_step = 0;\n" \
124 " if (1 < ++current_mode) {\n" \
125 " document.write ('FINISHED ALL TESTS.<br>');\n" \
126 " return;\n" \
127 " }\n" \
128 " }\n" \
129 " runTest ();\n" \
130 " }" \
131 " }\n" \
132 "}\n" \
133 "\n" \
134 "function compareData (data, data2) {\n" \
135 " if (typeof (data) === 'string' && typeof (data2) === 'string') {\n" \
136 " return (data === data2); \n" \
137 " } \n" \
138 " else if ((data instanceof ArrayBuffer) && (data2 instanceof ArrayBuffer)) {\n" \
139 " let view1 = new Uint8Array (data);\n" \
140 " let view2 = new Uint8Array (data2);\n" \
141 " if (view1.length != view2.length)\n" \
142 " return false;\n" \
143 " for (let i = 0; i < view1.length; ++i) {\n" \
144 " if (view1[i] !== view2[i])\n" \
145 " return false;\n" \
146 " }\n" \
147 " return true;\n" \
148 " }\n" \
149 " else\n" \
150 " {\n" \
151 " return false;\n" \
152 " }\n" \
153 "}\n" \
154 "\n" \
155 "function randomText (length) {\n" \
156 " let result = new Array (length);\n" \
157 " for (let i = 0; i < length; ++i)\n" \
158 " result [i] = charset [~~(Math.random () * charset.length)];\n" \
159 " return result.join ('');\n" \
160 "}\n" \
161 "\n" \
162 "function randomBinary (length) {\n" \
163 " let buffer = new ArrayBuffer (length);\n" \
164 " let view = new Uint8Array (buffer);\n" \
165 " for (let i = 0; i < length; ++i)\n" \
166 " view [i] = ~~(Math.random () * 256);\n" \
167 " return buffer;\n" \
168 "}\n" \
169 "\n" \
170 "function notifyError () {\n" \
171 " fetch('error/' + (0 == current_mode ? 'text' : 'binary') + '/' + step_to_bytes[current_step]);\n" \
172 "}\n" \
173 "\n" \
174 "</script>\n" \
175 "</head>\n" \
176 "<body>\n" \
177 "</body>\n" \
178 "</html>"
179
180#define PAGE_NOT_FOUND \
181 "404 Not Found"
182
183#define PAGE_INVALID_WEBSOCKET_REQUEST \
184 "Invalid WebSocket request!"
185
186static void
187send_all (MHD_socket fd,
188 const char *buf,
189 size_t len);
190static void
191make_blocking (MHD_socket fd);
192
193static void
194upgrade_handler (void *cls,
195 struct MHD_Connection *connection,
196 void *con_cls,
197 const char *extra_in,
198 size_t extra_in_size,
199 MHD_socket fd,
200 struct MHD_UpgradeResponseHandle *urh)
201{
202 /* make the socket blocking (operating-system-dependent code) */
203 make_blocking (fd);
204
205 /* create a websocket stream for this connection */
206 struct MHD_WebSocketStream* ws;
207 int result = MHD_websocket_stream_init (&ws,
208 0,
209 0);
210 if (0 != result)
211 {
212 /* Couldn't create the websocket stream.
213 * So we close the socket and leave
214 */
215 MHD_upgrade_action (urh,
216 MHD_UPGRADE_ACTION_CLOSE);
217 return;
218 }
219
220 /* Let's wait for incoming data */
221 const size_t buf_len = 256;
222 char buf[buf_len];
223 ssize_t got;
224 while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws))
225 {
226 got = recv (fd,
227 buf,
228 sizeof (buf),
229 0);
230 if (0 >= got)
231 {
232 /* the TCP/IP socket has been closed */
233 fprintf (stderr,
234 "Error (The socket has been closed unexpectedly)\n");
235 break;
236 }
237
238 /* parse the entire received data */
239 size_t buf_offset = 0;
240 while (buf_offset < (size_t) got)
241 {
242 size_t new_offset = 0;
243 char *payload_data = NULL;
244 size_t payload_len = 0;
245 char *frame_data = NULL;
246 size_t frame_len = 0;
247 int status = MHD_websocket_decode (ws,
248 buf + buf_offset,
249 ((size_t) got) - buf_offset,
250 &new_offset,
251 &payload_data,
252 &payload_len);
253 if (0 > status)
254 {
255 /* an error occurred and the connection must be closed */
256 printf ("Decoding failed: status=%d, passed=%u\n", status, ((size_t) got) - buf_offset);
257 if (NULL != payload_data)
258 {
259 MHD_websocket_free (ws, payload_data);
260 }
261 break;
262 }
263 else
264 {
265 buf_offset += new_offset;
266 if (0 < status)
267 {
268 /* the frame is complete */
269 printf ("Decoding succeeded: type=%d, passed=%u, parsed=%u, payload_len=%d\n", status, ((size_t) got) - buf_offset, new_offset, payload_len);
270 switch (status)
271 {
272 case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
273 case MHD_WEBSOCKET_STATUS_BINARY_FRAME:
274 /* The client has sent some data. */
275 if (NULL != payload_data || 0 == payload_len)
276 {
277 /* Send the received data back to the client */
278 if (MHD_WEBSOCKET_STATUS_TEXT_FRAME == status)
279 {
280 result = MHD_websocket_encode_text (ws,
281 payload_data,
282 payload_len,
283 0,
284 &frame_data,
285 &frame_len,
286 NULL);
287 }
288 else
289 {
290 result = MHD_websocket_encode_binary (ws,
291 payload_data,
292 payload_len,
293 0,
294 &frame_data,
295 &frame_len);
296 }
297 if (0 == result)
298 {
299 send_all (fd,
300 frame_data,
301 frame_len);
302 }
303 }
304 else
305 {
306 /* should never happen */
307 fprintf (stderr,
308 "Error (Empty buffer with payload_len != 0)\n");
309 }
310 break;
311
312 default:
313 /* Other frame types are ignored
314 * in this test script.
315 */
316 break;
317 }
318 }
319 if (NULL != payload_data)
320 {
321 MHD_websocket_free (ws, payload_data);
322 }
323 if (NULL != frame_data)
324 {
325 MHD_websocket_free (ws, frame_data);
326 }
327 }
328 }
329 }
330
331 /* free the websocket stream */
332 MHD_websocket_stream_free (ws);
333
334 /* close the socket when it is not needed anymore */
335 MHD_upgrade_action (urh,
336 MHD_UPGRADE_ACTION_CLOSE);
337}
338
339/* This helper function is used for the case that
340 * we need to resend some data
341 */
342static void
343send_all (MHD_socket fd,
344 const char *buf,
345 size_t len)
346{
347 ssize_t ret;
348 size_t off;
349
350 for (off = 0; off < len; off += ret)
351 {
352 ret = send (fd,
353 &buf[off],
354 (int) (len - off),
355 0);
356 if (0 > ret)
357 {
358 if (EAGAIN == errno)
359 {
360 ret = 0;
361 continue;
362 }
363 break;
364 }
365 if (0 == ret)
366 break;
367 }
368}
369
370/* This helper function contains operating-system-dependent code and
371 * is used to make a socket blocking.
372 */
373static void
374make_blocking (MHD_socket fd)
375{
376#ifndef _WIN32
377 int flags;
378
379 flags = fcntl (fd, F_GETFL);
380 if (-1 == flags)
381 return;
382 if ((flags & ~O_NONBLOCK) != flags)
383 if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
384 abort ();
385#else
386 unsigned long flags = 0;
387
388 ioctlsocket (fd, FIONBIO, &flags);
389#endif
390}
391
392static enum MHD_Result
393access_handler (void *cls,
394 struct MHD_Connection *connection,
395 const char *url,
396 const char *method,
397 const char *version,
398 const char *upload_data,
399 size_t *upload_data_size,
400 void **ptr)
401{
402 static int aptr;
403 struct MHD_Response *response;
404 int ret;
405
406 (void) cls; /* Unused. Silent compiler warning. */
407 (void) upload_data; /* Unused. Silent compiler warning. */
408 (void) upload_data_size; /* Unused. Silent compiler warning. */
409
410 if (0 != strcmp (method, "GET"))
411 return MHD_NO; /* unexpected method */
412 if (&aptr != *ptr)
413 {
414 /* do never respond on first call */
415 *ptr = &aptr;
416 return MHD_YES;
417 }
418 *ptr = NULL; /* reset when done */
419
420 if (0 == strcmp (url, "/"))
421 {
422 /* Default page for visiting the server */
423 struct MHD_Response *response = MHD_create_response_from_buffer (
424 strlen (PAGE),
425 PAGE,
426 MHD_RESPMEM_PERSISTENT);
427 ret = MHD_queue_response (connection,
428 MHD_HTTP_OK,
429 response);
430 MHD_destroy_response (response);
431 }
432 else if (0 == strncmp (url, "/error/", 7))
433 {
434 /* Report error */
435 fprintf (stderr, "Error in test (%s)\n", url + 7);
436
437 struct MHD_Response *response = MHD_create_response_from_buffer (
438 0,
439 "",
440 MHD_RESPMEM_PERSISTENT);
441 ret = MHD_queue_response (connection,
442 MHD_HTTP_OK,
443 response);
444 MHD_destroy_response (response);
445 }
446 else if (0 == strcmp (url, "/websocket"))
447 {
448 char is_valid = 1;
449 const char* value = NULL;
450 char sec_websocket_accept[29];
451
452 if (0 != MHD_websocket_check_http_version (version))
453 {
454 is_valid = 0;
455 }
456 value = MHD_lookup_connection_value (connection,
457 MHD_HEADER_KIND,
458 MHD_HTTP_HEADER_CONNECTION);
459 if (0 != MHD_websocket_check_connection_header (value))
460 {
461 is_valid = 0;
462 }
463 value = MHD_lookup_connection_value (connection,
464 MHD_HEADER_KIND,
465 MHD_HTTP_HEADER_UPGRADE);
466 if (0 != MHD_websocket_check_upgrade_header (value))
467 {
468 is_valid = 0;
469 }
470 value = MHD_lookup_connection_value (connection,
471 MHD_HEADER_KIND,
472 MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
473 if (0 != MHD_websocket_check_version_header (value))
474 {
475 is_valid = 0;
476 }
477 value = MHD_lookup_connection_value (connection,
478 MHD_HEADER_KIND,
479 MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
480 if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
481 {
482 is_valid = 0;
483 }
484
485 if (1 == is_valid)
486 {
487 /* upgrade the connection */
488 response = MHD_create_response_for_upgrade (&upgrade_handler,
489 NULL);
490 MHD_add_response_header (response,
491 MHD_HTTP_HEADER_CONNECTION,
492 "Upgrade");
493 MHD_add_response_header (response,
494 MHD_HTTP_HEADER_UPGRADE,
495 "websocket");
496 MHD_add_response_header (response,
497 MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
498 sec_websocket_accept);
499 ret = MHD_queue_response (connection,
500 MHD_HTTP_SWITCHING_PROTOCOLS,
501 response);
502 MHD_destroy_response (response);
503 }
504 else
505 {
506 /* return error page */
507 struct MHD_Response*response = MHD_create_response_from_buffer (
508 strlen (PAGE_INVALID_WEBSOCKET_REQUEST),
509 PAGE_INVALID_WEBSOCKET_REQUEST,
510 MHD_RESPMEM_PERSISTENT);
511 ret = MHD_queue_response (connection,
512 MHD_HTTP_BAD_REQUEST,
513 response);
514 MHD_destroy_response (response);
515 }
516 }
517 else
518 {
519 struct MHD_Response*response = MHD_create_response_from_buffer (
520 strlen (PAGE_NOT_FOUND),
521 PAGE_NOT_FOUND,
522 MHD_RESPMEM_PERSISTENT);
523 ret = MHD_queue_response (connection,
524 MHD_HTTP_NOT_FOUND,
525 response);
526 MHD_destroy_response (response);
527 }
528
529 return ret;
530}
531
532int
533main (int argc,
534 char *const *argv)
535{
536 (void) argc; /* Unused. Silent compiler warning. */
537 (void) argv; /* Unused. Silent compiler warning. */
538 struct MHD_Daemon *daemon;
539
540 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD |
541 MHD_USE_THREAD_PER_CONNECTION |
542 MHD_ALLOW_UPGRADE |
543 MHD_USE_ERROR_LOG,
544 PORT, NULL, NULL,
545 &access_handler, NULL,
546 MHD_OPTION_END);
547
548 if (NULL == daemon)
549 {
550 fprintf (stderr, "Error (Couldn't start daemon for testing)\n");
551 return 1;
552 }
553 printf("The server is listening now.\n");
554 printf("Access the server now with a websocket-capable webbrowser.\n\n");
555 printf("Press return to close.\n");
556
557 (void) getc (stdin);
558
559 MHD_stop_daemon (daemon);
560
561 return 0;
562}
563