diff options
author | David Gausmann <David.Gausmann@measX.com> | 2021-10-17 19:53:09 +0200 |
---|---|---|
committer | Evgeny Grin (Karlson2k) <k2k@narod.ru> | 2021-10-31 16:02:31 +0300 |
commit | ddad59c1e3a5d20b19003b1d47673501b0665b36 (patch) | |
tree | 8cd367e724477bc092d7dd38c83cd15f4c12c95f /doc/examples/websocket.c | |
parent | ffbb000f890f23e14d972a6660168aff4a97b66c (diff) | |
download | libmicrohttpd-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.c | 446 |
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 | |||
72 | static void | ||
73 | send_all (MHD_socket fd, | ||
74 | const char *buf, | ||
75 | size_t len); | ||
76 | static void | ||
77 | make_blocking (MHD_socket fd); | ||
78 | |||
79 | static void | ||
80 | upgrade_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 | */ | ||
247 | static void | ||
248 | send_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 | */ | ||
278 | static void | ||
279 | make_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 | |||
297 | static enum MHD_Result | ||
298 | access_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 | |||
423 | int | ||
424 | main (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 | } | ||