aboutsummaryrefslogtreecommitdiff
path: root/doc/examples/websocket.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 /doc/examples/websocket.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 'doc/examples/websocket.c')
-rw-r--r--doc/examples/websocket.c446
1 files changed, 446 insertions, 0 deletions
diff --git a/doc/examples/websocket.c b/doc/examples/websocket.c
new file mode 100644
index 00000000..39995479
--- /dev/null
+++ b/doc/examples/websocket.c
@@ -0,0 +1,446 @@
1/* Feel free to use this example code in any way
2 you see fit (Public Domain) */
3
4#include <sys/types.h>
5#ifndef _WIN32
6#include <sys/select.h>
7#include <sys/socket.h>
8#include <fcntl.h>
9#else
10#include <winsock2.h>
11#endif
12#include <microhttpd.h>
13#include <microhttpd_ws.h>
14#include <time.h>
15#include <string.h>
16#include <stdlib.h>
17#include <stdio.h>
18#include <errno.h>
19
20#define PORT 80
21
22#define PAGE \
23 "<!DOCTYPE html>\n" \
24 "<html>\n" \
25 "<head>\n" \
26 "<meta charset=\"UTF-8\">\n" \
27 "<title>Websocket Demo</title>\n" \
28 "<script>\n" \
29 "\n" \
30 "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \
31 " + '://' +\n" \
32 " window.location.host + '/chat';\n" \
33 "let socket = null;\n" \
34 "\n" \
35 "window.onload = function(event) {\n" \
36 " socket = new WebSocket(url);\n" \
37 " socket.onopen = function(event) {\n" \
38 " document.write('The websocket connection has been " \
39 "established.<br>');\n" \
40 "\n" \
41 " // Send some text\n" \
42 " socket.send('Hello from JavaScript!');\n" \
43 " }\n" \
44 "\n" \
45 " socket.onclose = function(event) {\n" \
46 " document.write('The websocket connection has been closed.<br>');\n" \
47 " }\n" \
48 "\n" \
49 " socket.onerror = function(event) {\n" \
50 " document.write('An error occurred during the websocket " \
51 "communication.<br>');\n" \
52 " }\n" \
53 "\n" \
54 " socket.onmessage = function(event) {\n" \
55 " document.write('Websocket message received: ' + " \
56 "event.data + '<br>');\n" \
57 " }\n" \
58 "}\n" \
59 "\n" \
60 "</script>\n" \
61 "</head>\n" \
62 "<body>\n" \
63 "</body>\n" \
64 "</html>"
65
66#define PAGE_NOT_FOUND \
67 "404 Not Found"
68
69#define PAGE_INVALID_WEBSOCKET_REQUEST \
70 "Invalid WebSocket request!"
71
72static void
73send_all (MHD_socket fd,
74 const char *buf,
75 size_t len);
76static void
77make_blocking (MHD_socket fd);
78
79static void
80upgrade_handler (void *cls,
81 struct MHD_Connection *connection,
82 void *con_cls,
83 const char *extra_in,
84 size_t extra_in_size,
85 MHD_socket fd,
86 struct MHD_UpgradeResponseHandle *urh)
87{
88 /* make the socket blocking (operating-system-dependent code) */
89 make_blocking (fd);
90
91 /* create a websocket stream for this connection */
92 struct MHD_WebSocketStream* ws;
93 int result = MHD_websocket_stream_init (&ws,
94 0,
95 0);
96 if (0 != result)
97 {
98 /* Couldn't create the websocket stream.
99 * So we close the socket and leave
100 */
101 MHD_upgrade_action (urh,
102 MHD_UPGRADE_ACTION_CLOSE);
103 return;
104 }
105
106 /* Let's wait for incoming data */
107 const size_t buf_len = 256;
108 char buf[buf_len];
109 ssize_t got;
110 while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws))
111 {
112 got = recv (fd,
113 buf,
114 buf_len,
115 0);
116 if (0 >= got)
117 {
118 /* the TCP/IP socket has been closed */
119 break;
120 }
121
122 /* parse the entire received data */
123 size_t buf_offset = 0;
124 while (buf_offset < (size_t) got)
125 {
126 size_t new_offset = 0;
127 char *frame_data = NULL;
128 size_t frame_len = 0;
129 int status = MHD_websocket_decode (ws,
130 buf + buf_offset,
131 ((size_t) got) - buf_offset,
132 &new_offset,
133 &frame_data,
134 &frame_len);
135 if (0 > status)
136 {
137 /* an error occurred and the connection must be closed */
138 if (NULL != frame_data)
139 {
140 MHD_websocket_free (ws, frame_data);
141 }
142 break;
143 }
144 else
145 {
146 buf_offset += new_offset;
147 if (0 < status)
148 {
149 /* the frame is complete */
150 switch (status)
151 {
152 case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
153 /* The client has sent some text.
154 * We will display it and answer with a text frame.
155 */
156 if (NULL != frame_data)
157 {
158 printf ("Received message: %s\n", frame_data);
159 MHD_websocket_free (ws, frame_data);
160 frame_data = NULL;
161 }
162 result = MHD_websocket_encode_text (ws,
163 "Hello",
164 5, /* length of "Hello" */
165 0,
166 &frame_data,
167 &frame_len,
168 NULL);
169 if (0 == result)
170 {
171 send_all (fd,
172 frame_data,
173 frame_len);
174 }
175 break;
176
177 case MHD_WEBSOCKET_STATUS_CLOSE_FRAME:
178 /* if we receive a close frame, we will respond with one */
179 MHD_websocket_free (ws,
180 frame_data);
181 frame_data = NULL;
182
183 result = MHD_websocket_encode_close (ws,
184 0,
185 NULL,
186 0,
187 &frame_data,
188 &frame_len);
189 if (0 == result)
190 {
191 send_all (fd,
192 frame_data,
193 frame_len);
194 }
195 break;
196
197 case MHD_WEBSOCKET_STATUS_PING_FRAME:
198 /* if we receive a ping frame, we will respond */
199 /* with the corresponding pong frame */
200 {
201 char *pong = NULL;
202 size_t pong_len = 0;
203 result = MHD_websocket_encode_pong (ws,
204 frame_data,
205 frame_len,
206 &pong,
207 &pong_len);
208 if (0 == result)
209 {
210 send_all (fd,
211 pong,
212 pong_len);
213 }
214 MHD_websocket_free (ws,
215 pong);
216 }
217 break;
218
219 default:
220 /* Other frame types are ignored
221 * in this minimal example.
222 * This is valid, because they become
223 * automatically skipped if we receive them unexpectedly
224 */
225 break;
226 }
227 }
228 if (NULL != frame_data)
229 {
230 MHD_websocket_free (ws, frame_data);
231 }
232 }
233 }
234 }
235
236 /* free the websocket stream */
237 MHD_websocket_stream_free (ws);
238
239 /* close the socket when it is not needed anymore */
240 MHD_upgrade_action (urh,
241 MHD_UPGRADE_ACTION_CLOSE);
242}
243
244/* This helper function is used for the case that
245 * we need to resend some data
246 */
247static void
248send_all (MHD_socket fd,
249 const char *buf,
250 size_t len)
251{
252 ssize_t ret;
253 size_t off;
254
255 for (off = 0; off < len; off += ret)
256 {
257 ret = send (fd,
258 &buf[off],
259 (int) (len - off),
260 0);
261 if (0 > ret)
262 {
263 if (EAGAIN == errno)
264 {
265 ret = 0;
266 continue;
267 }
268 break;
269 }
270 if (0 == ret)
271 break;
272 }
273}
274
275/* This helper function contains operating-system-dependent code and
276 * is used to make a socket blocking.
277 */
278static void
279make_blocking (MHD_socket fd)
280{
281#ifndef _WIN32
282 int flags;
283
284 flags = fcntl (fd, F_GETFL);
285 if (-1 == flags)
286 return;
287 if ((flags & ~O_NONBLOCK) != flags)
288 if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
289 abort ();
290#else
291 unsigned long flags = 0;
292
293 ioctlsocket (fd, FIONBIO, &flags);
294#endif
295}
296
297static enum MHD_Result
298access_handler (void *cls,
299 struct MHD_Connection *connection,
300 const char *url,
301 const char *method,
302 const char *version,
303 const char *upload_data,
304 size_t *upload_data_size,
305 void **ptr)
306{
307 static int aptr;
308 struct MHD_Response *response;
309 int ret;
310
311 (void) cls; /* Unused. Silent compiler warning. */
312 (void) upload_data; /* Unused. Silent compiler warning. */
313 (void) upload_data_size; /* Unused. Silent compiler warning. */
314
315 if (0 != strcmp (method, "GET"))
316 return MHD_NO; /* unexpected method */
317 if (&aptr != *ptr)
318 {
319 /* do never respond on first call */
320 *ptr = &aptr;
321 return MHD_YES;
322 }
323 *ptr = NULL; /* reset when done */
324
325 if (0 == strcmp (url, "/"))
326 {
327 /* Default page for visiting the server */
328 struct MHD_Response *response = MHD_create_response_from_buffer (
329 strlen (PAGE),
330 PAGE,
331 MHD_RESPMEM_PERSISTENT);
332 ret = MHD_queue_response (connection,
333 MHD_HTTP_OK,
334 response);
335 MHD_destroy_response (response);
336 }
337 else if (0 == strcmp (url, "/chat"))
338 {
339 char is_valid = 1;
340 const char* value = NULL;
341 char sec_websocket_accept[29];
342
343 if (0 != MHD_websocket_check_http_version (version))
344 {
345 is_valid = 0;
346 }
347 value = MHD_lookup_connection_value (connection,
348 MHD_HEADER_KIND,
349 MHD_HTTP_HEADER_CONNECTION);
350 if (0 != MHD_websocket_check_connection_header (value))
351 {
352 is_valid = 0;
353 }
354 value = MHD_lookup_connection_value (connection,
355 MHD_HEADER_KIND,
356 MHD_HTTP_HEADER_UPGRADE);
357 if (0 != MHD_websocket_check_upgrade_header (value))
358 {
359 is_valid = 0;
360 }
361 value = MHD_lookup_connection_value (connection,
362 MHD_HEADER_KIND,
363 MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
364 if (0 != MHD_websocket_check_version_header (value))
365 {
366 is_valid = 0;
367 }
368 value = MHD_lookup_connection_value (connection,
369 MHD_HEADER_KIND,
370 MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
371 if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
372 {
373 is_valid = 0;
374 }
375
376 if (1 == is_valid)
377 {
378 /* upgrade the connection */
379 response = MHD_create_response_for_upgrade (&upgrade_handler,
380 NULL);
381 MHD_add_response_header (response,
382 MHD_HTTP_HEADER_CONNECTION,
383 "Upgrade");
384 MHD_add_response_header (response,
385 MHD_HTTP_HEADER_UPGRADE,
386 "websocket");
387 MHD_add_response_header (response,
388 MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
389 sec_websocket_accept);
390 ret = MHD_queue_response (connection,
391 MHD_HTTP_SWITCHING_PROTOCOLS,
392 response);
393 MHD_destroy_response (response);
394 }
395 else
396 {
397 /* return error page */
398 struct MHD_Response*response = MHD_create_response_from_buffer (
399 strlen (PAGE_INVALID_WEBSOCKET_REQUEST),
400 PAGE_INVALID_WEBSOCKET_REQUEST,
401 MHD_RESPMEM_PERSISTENT);
402 ret = MHD_queue_response (connection,
403 MHD_HTTP_BAD_REQUEST,
404 response);
405 MHD_destroy_response (response);
406 }
407 }
408 else
409 {
410 struct MHD_Response*response = MHD_create_response_from_buffer (
411 strlen (PAGE_NOT_FOUND),
412 PAGE_NOT_FOUND,
413 MHD_RESPMEM_PERSISTENT);
414 ret = MHD_queue_response (connection,
415 MHD_HTTP_NOT_FOUND,
416 response);
417 MHD_destroy_response (response);
418 }
419
420 return ret;
421}
422
423int
424main (int argc,
425 char *const *argv)
426{
427 (void) argc; /* Unused. Silent compiler warning. */
428 (void) argv; /* Unused. Silent compiler warning. */
429 struct MHD_Daemon *daemon;
430
431 daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD |
432 MHD_USE_THREAD_PER_CONNECTION |
433 MHD_ALLOW_UPGRADE |
434 MHD_USE_ERROR_LOG,
435 PORT, NULL, NULL,
436 &access_handler, NULL,
437 MHD_OPTION_END);
438
439 if (NULL == daemon)
440 return 1;
441 (void) getc (stdin);
442
443 MHD_stop_daemon (daemon);
444
445 return 0;
446}