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 | |
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.
-rw-r--r-- | doc/chapters/bibliography.inc | 3 | ||||
-rw-r--r-- | doc/chapters/websocket.inc | 886 | ||||
-rw-r--r-- | doc/examples/websocket.c | 446 | ||||
-rw-r--r-- | doc/libmicrohttpd-tutorial.texi | 12 | ||||
-rw-r--r-- | doc/libmicrohttpd.texi | 1281 | ||||
-rw-r--r-- | src/examples/websocket_chatserver_example.c | 1051 | ||||
-rw-r--r-- | src/include/microhttpd_ws.h | 534 | ||||
-rw-r--r-- | src/microhttpd_ws/mhd_websocket.c | 587 | ||||
-rw-r--r-- | src/microhttpd_ws/sha1.c | 720 | ||||
-rw-r--r-- | src/microhttpd_ws/sha1.h | 245 | ||||
-rw-r--r-- | src/microhttpd_ws/test_websocket.c | 1247 | ||||
-rw-r--r-- | src/microhttpd_ws/test_websocket_browser.c | 563 |
12 files changed, 6141 insertions, 1434 deletions
diff --git a/doc/chapters/bibliography.inc b/doc/chapters/bibliography.inc index cc288bc0..bdaa6187 100644 --- a/doc/chapters/bibliography.inc +++ b/doc/chapters/bibliography.inc | |||
@@ -16,6 +16,9 @@ All referenced RFCs can be found on the website of @emph{The Internet Engineerin | |||
16 | @emph{RFC 2617}: Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., | 16 | @emph{RFC 2617}: Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., |
17 | Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999. | 17 | Luotonen, A., and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999. |
18 | 18 | ||
19 | @item | ||
20 | @emph{RFC 6455}: Fette, I., Melnikov, A., "The WebSocket Protocol", RFC 6455, December 2011. | ||
21 | |||
19 | 22 | ||
20 | @item | 23 | @item |
21 | A well--structured @emph{HTML} reference can be found on | 24 | A well--structured @emph{HTML} reference can be found on |
diff --git a/doc/chapters/websocket.inc b/doc/chapters/websocket.inc new file mode 100644 index 00000000..a480fd13 --- /dev/null +++ b/doc/chapters/websocket.inc | |||
@@ -0,0 +1,886 @@ | |||
1 | Websockets are a genuine way to implement push notifications, | ||
2 | where the server initiates the communication while the client can be idle. | ||
3 | Usually a HTTP communication is half-duplex and always requested by the client, | ||
4 | but websockets are full-duplex and only initialized by the client. | ||
5 | In the further communication both sites can use the websocket at any time | ||
6 | to send data to the other site. | ||
7 | |||
8 | To initialize a websocket connection the client sends a special HTTP request | ||
9 | to the server and initializes | ||
10 | a handshake between client and server which switches from the HTTP protocol | ||
11 | to the websocket protocol. | ||
12 | Thus both the server as well as the client must support websockets. | ||
13 | If proxys are used, they must support websockets too. | ||
14 | In this chapter we take a look on server and client, but with a focus on | ||
15 | the server with @emph{libmicrohttpd}. | ||
16 | |||
17 | Since version 0.9.52 @emph{libmicrohttpd} supports upgrading requests, | ||
18 | which is required for switching from the HTTP protocol. | ||
19 | Since version 0.9.74 the library @emph{libmicrohttpd_ws} has been added | ||
20 | to support the websocket protocol. | ||
21 | |||
22 | @heading Upgrading connections with libmicrohttpd | ||
23 | |||
24 | To support websockets we need to enable upgrading of HTTP connections first. | ||
25 | This is done by passing the flag @code{MHD_ALLOW_UPGRADE} to | ||
26 | @code{MHD_start_daemon()}. | ||
27 | |||
28 | |||
29 | @verbatim | ||
30 | daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | | ||
31 | MHD_USE_THREAD_PER_CONNECTION | | ||
32 | MHD_ALLOW_UPGRADE | | ||
33 | MHD_USE_ERROR_LOG, | ||
34 | PORT, NULL, NULL, | ||
35 | &access_handler, NULL, | ||
36 | MHD_OPTION_END); | ||
37 | @end verbatim | ||
38 | @noindent | ||
39 | |||
40 | |||
41 | The next step is to turn a specific request into an upgraded connection. | ||
42 | This done in our @code{access_handler} by calling | ||
43 | @code{MHD_create_response_for_upgrade()}. | ||
44 | An @code{upgrade_handler} will be passed to perform the low-level actions | ||
45 | on the socket. | ||
46 | |||
47 | @emph{Please note that the socket here is just a regular socket as provided | ||
48 | by the operating system. | ||
49 | To use it as a websocket, some more steps from the following | ||
50 | chapters are required.} | ||
51 | |||
52 | |||
53 | @verbatim | ||
54 | static enum MHD_Result | ||
55 | access_handler (void *cls, | ||
56 | struct MHD_Connection *connection, | ||
57 | const char *url, | ||
58 | const char *method, | ||
59 | const char *version, | ||
60 | const char *upload_data, | ||
61 | size_t *upload_data_size, | ||
62 | void **ptr) | ||
63 | { | ||
64 | /* ... */ | ||
65 | /* some code to decide whether to upgrade or not */ | ||
66 | /* ... */ | ||
67 | |||
68 | /* create the response for upgrade */ | ||
69 | response = MHD_create_response_for_upgrade (&upgrade_handler, | ||
70 | NULL); | ||
71 | |||
72 | /* ... */ | ||
73 | /* additional headers, etc. */ | ||
74 | /* ... */ | ||
75 | |||
76 | ret = MHD_queue_response (connection, | ||
77 | MHD_HTTP_SWITCHING_PROTOCOLS, | ||
78 | response); | ||
79 | MHD_destroy_response (response); | ||
80 | |||
81 | return ret; | ||
82 | } | ||
83 | @end verbatim | ||
84 | @noindent | ||
85 | |||
86 | |||
87 | In the @code{upgrade_handler} we receive the low-level socket, | ||
88 | which is used for the communication with the specific client. | ||
89 | In addition to the low-level socket we get: | ||
90 | @itemize @bullet | ||
91 | @item | ||
92 | Some data, which has been read too much while @emph{libmicrohttpd} was | ||
93 | switching the protocols. | ||
94 | This value is usually empty, because it would mean that the client | ||
95 | has sent data before the handshake was complete. | ||
96 | |||
97 | @item | ||
98 | A @code{struct MHD_UpgradeResponseHandle} which is used to perform | ||
99 | special actions like closing, corking or uncorking the socket. | ||
100 | These commands are executed by passing the handle | ||
101 | to @code{MHD_upgrade_action()}. | ||
102 | |||
103 | |||
104 | @end itemize | ||
105 | |||
106 | Depending of the flags specified while calling @code{MHD_start_deamon()} | ||
107 | our @code{upgrade_handler} is either executed in the same thread | ||
108 | as our deamon or in a thread specific for each connection. | ||
109 | If it is executed in the same thread then @code{upgrade_handler} is | ||
110 | a blocking call for our webserver and | ||
111 | we should finish it as fast as possible (i. e. by creating a thread and | ||
112 | passing the information there). | ||
113 | If @code{MHD_USE_THREAD_PER_CONNECTION} was passed to | ||
114 | @code{MHD_start_daemon()} then a separate thread is used and | ||
115 | thus our @code{upgrade_handler} needs not to start a separate thread. | ||
116 | |||
117 | An @code{upgrade_handler}, which is called with a separate thread | ||
118 | per connection, could look like this: | ||
119 | |||
120 | |||
121 | @verbatim | ||
122 | static void | ||
123 | upgrade_handler (void *cls, | ||
124 | struct MHD_Connection *connection, | ||
125 | void *con_cls, | ||
126 | const char *extra_in, | ||
127 | size_t extra_in_size, | ||
128 | MHD_socket fd, | ||
129 | struct MHD_UpgradeResponseHandle *urh) | ||
130 | { | ||
131 | /* ... */ | ||
132 | /* do something with the socket `fd` like `recv()` or `send()` */ | ||
133 | /* ... */ | ||
134 | |||
135 | /* close the socket when it is not needed anymore */ | ||
136 | MHD_upgrade_action (urh, | ||
137 | MHD_UPGRADE_ACTION_CLOSE); | ||
138 | } | ||
139 | @end verbatim | ||
140 | @noindent | ||
141 | |||
142 | |||
143 | This is all you need to know for upgrading connections | ||
144 | with @emph{libmicrohttpd}. | ||
145 | The next chapters focus on using the websocket protocol | ||
146 | with @emph{libmicrohttpd_ws}. | ||
147 | |||
148 | |||
149 | @heading Websocket handshake with libmicrohttpd_ws | ||
150 | |||
151 | To request a websocket connection the client must send | ||
152 | the following information with the HTTP request: | ||
153 | |||
154 | @itemize @bullet | ||
155 | @item | ||
156 | A @code{GET} request must be sent. | ||
157 | |||
158 | @item | ||
159 | The version of the HTTP protocol must be 1.1 or higher. | ||
160 | |||
161 | @item | ||
162 | A @code{Host} header field must be sent | ||
163 | |||
164 | @item | ||
165 | A @code{Upgrade} header field containing the keyword "websocket" | ||
166 | (case-insensitive). | ||
167 | Please note that the client could pass multiple protocols separated by comma. | ||
168 | |||
169 | @item | ||
170 | A @code{Connection} header field that includes the token "Upgrade" | ||
171 | (case-insensitive). | ||
172 | Please note that the client could pass multiple tokens separated by comma. | ||
173 | |||
174 | @item | ||
175 | A @code{Sec-WebSocket-Key} header field with a base64-encoded value. | ||
176 | The decoded the value is 16 bytes long | ||
177 | and has been generated randomly by the client. | ||
178 | |||
179 | @item | ||
180 | A @code{Sec-WebSocket-Version} header field with the value "13". | ||
181 | |||
182 | @end itemize | ||
183 | |||
184 | |||
185 | Optionally the client can also send the following information: | ||
186 | |||
187 | |||
188 | @itemize @bullet | ||
189 | @item | ||
190 | A @code{Origin} header field can be used to determine the source | ||
191 | of the client (i. e. the website). | ||
192 | |||
193 | @item | ||
194 | A @code{Sec-WebSocket-Protocol} header field can contain a list | ||
195 | of supported protocols by the client, which can be sent over the websocket. | ||
196 | |||
197 | @item | ||
198 | A @code{Sec-WebSocket-Extensions} header field which may contain extensions | ||
199 | to the websocket protocol. The extensions must be registered by IANA. | ||
200 | |||
201 | @end itemize | ||
202 | |||
203 | |||
204 | A valid example request from the client could look like this: | ||
205 | |||
206 | |||
207 | @verbatim | ||
208 | GET /chat HTTP/1.1 | ||
209 | Host: server.example.com | ||
210 | Upgrade: websocket | ||
211 | Connection: Upgrade | ||
212 | Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== | ||
213 | Sec-WebSocket-Version: 13 | ||
214 | @end verbatim | ||
215 | @noindent | ||
216 | |||
217 | |||
218 | To complete the handshake the server must respond with | ||
219 | some specific response headers: | ||
220 | |||
221 | @itemize @bullet | ||
222 | @item | ||
223 | The HTTP response code @code{101 Switching Protocols} must be answered. | ||
224 | |||
225 | @item | ||
226 | An @code{Upgrade} header field containing the value "websocket" must be sent. | ||
227 | |||
228 | @item | ||
229 | A @code{Connection} header field containing the value "Upgrade" must be sent. | ||
230 | |||
231 | @item | ||
232 | A @code{Sec-WebSocket-Accept} header field containing a value, which | ||
233 | has been calculated from the @code{Sec-WebSocket-Key} request header field, | ||
234 | must be sent. | ||
235 | |||
236 | @end itemize | ||
237 | |||
238 | |||
239 | Optionally the server may send following headers: | ||
240 | |||
241 | |||
242 | @itemize @bullet | ||
243 | @item | ||
244 | A @code{Sec-WebSocket-Protocol} header field containing a protocol | ||
245 | of the list specified in the corresponding request header field. | ||
246 | |||
247 | @item | ||
248 | A @code{Sec-WebSocket-Extension} header field containing all used extensions | ||
249 | of the list specified in the corresponding request header field. | ||
250 | |||
251 | @end itemize | ||
252 | |||
253 | |||
254 | A valid websocket HTTP response could look like this: | ||
255 | |||
256 | @verbatim | ||
257 | HTTP/1.1 101 Switching Protocols | ||
258 | Upgrade: websocket | ||
259 | Connection: Upgrade | ||
260 | Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= | ||
261 | @end verbatim | ||
262 | @noindent | ||
263 | |||
264 | |||
265 | To upgrade a connection to a websocket the @emph{libmicrohttpd_ws} provides | ||
266 | some helper functions for the @code{access_handler} callback function: | ||
267 | |||
268 | @itemize @bullet | ||
269 | @item | ||
270 | @code{MHD_websocket_check_http_version()} checks whether the HTTP version | ||
271 | is 1.1 or above. | ||
272 | |||
273 | @item | ||
274 | @code{MHD_websocket_check_connection_header()} checks whether the value | ||
275 | of the @code{Connection} request header field contains | ||
276 | an "Upgrade" token (case-insensitive). | ||
277 | |||
278 | @item | ||
279 | @code{MHD_websocket_check_upgrade_header()} checks whether the value | ||
280 | of the @code{Upgrade} request header field contains | ||
281 | the "websocket" keyword (case-insensitive). | ||
282 | |||
283 | @item | ||
284 | @code{MHD_websocket_check_version_header()} checks whether the value | ||
285 | of the @code{Sec-WebSocket-Version} request header field is "13". | ||
286 | |||
287 | @item | ||
288 | @code{MHD_websocket_create_accept_header()} takes the value from | ||
289 | the @code{Sec-WebSocket-Key} request header and calculates the value | ||
290 | for the @code{Sec-WebSocket-Accept} response header field. | ||
291 | |||
292 | @end itemize | ||
293 | |||
294 | |||
295 | The @code{access_handler} example of the previous chapter can now be | ||
296 | extended with these helper functions to perform the websocket handshake: | ||
297 | |||
298 | @verbatim | ||
299 | static enum MHD_Result | ||
300 | access_handler (void *cls, | ||
301 | struct MHD_Connection *connection, | ||
302 | const char *url, | ||
303 | const char *method, | ||
304 | const char *version, | ||
305 | const char *upload_data, | ||
306 | size_t *upload_data_size, | ||
307 | void **ptr) | ||
308 | { | ||
309 | static int aptr; | ||
310 | struct MHD_Response *response; | ||
311 | int ret; | ||
312 | |||
313 | (void) cls; /* Unused. Silent compiler warning. */ | ||
314 | (void) upload_data; /* Unused. Silent compiler warning. */ | ||
315 | (void) upload_data_size; /* Unused. Silent compiler warning. */ | ||
316 | |||
317 | if (0 != strcmp (method, "GET")) | ||
318 | return MHD_NO; /* unexpected method */ | ||
319 | if (&aptr != *ptr) | ||
320 | { | ||
321 | /* do never respond on first call */ | ||
322 | *ptr = &aptr; | ||
323 | return MHD_YES; | ||
324 | } | ||
325 | *ptr = NULL; /* reset when done */ | ||
326 | |||
327 | if (0 == strcmp (url, "/")) | ||
328 | { | ||
329 | /* Default page for visiting the server */ | ||
330 | struct MHD_Response *response = MHD_create_response_from_buffer ( | ||
331 | strlen (PAGE), | ||
332 | PAGE, | ||
333 | MHD_RESPMEM_PERSISTENT); | ||
334 | ret = MHD_queue_response (connection, | ||
335 | MHD_HTTP_OK, | ||
336 | response); | ||
337 | MHD_destroy_response (response); | ||
338 | } | ||
339 | else if (0 == strcmp (url, "/chat")) | ||
340 | { | ||
341 | char is_valid = 1; | ||
342 | const char* value = NULL; | ||
343 | char sec_websocket_accept[29]; | ||
344 | |||
345 | if (0 != MHD_websocket_check_http_version (version)) | ||
346 | { | ||
347 | is_valid = 0; | ||
348 | } | ||
349 | value = MHD_lookup_connection_value (connection, | ||
350 | MHD_HEADER_KIND, | ||
351 | MHD_HTTP_HEADER_CONNECTION); | ||
352 | if (0 != MHD_websocket_check_connection_header (value)) | ||
353 | { | ||
354 | is_valid = 0; | ||
355 | } | ||
356 | value = MHD_lookup_connection_value (connection, | ||
357 | MHD_HEADER_KIND, | ||
358 | MHD_HTTP_HEADER_UPGRADE); | ||
359 | if (0 != MHD_websocket_check_upgrade_header (value)) | ||
360 | { | ||
361 | is_valid = 0; | ||
362 | } | ||
363 | value = MHD_lookup_connection_value (connection, | ||
364 | MHD_HEADER_KIND, | ||
365 | MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); | ||
366 | if (0 != MHD_websocket_check_version_header (value)) | ||
367 | { | ||
368 | is_valid = 0; | ||
369 | } | ||
370 | value = MHD_lookup_connection_value (connection, | ||
371 | MHD_HEADER_KIND, | ||
372 | MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); | ||
373 | if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept)) | ||
374 | { | ||
375 | is_valid = 0; | ||
376 | } | ||
377 | |||
378 | if (1 == is_valid) | ||
379 | { | ||
380 | /* upgrade the connection */ | ||
381 | response = MHD_create_response_for_upgrade (&upgrade_handler, | ||
382 | NULL); | ||
383 | MHD_add_response_header (response, | ||
384 | MHD_HTTP_HEADER_CONNECTION, | ||
385 | "Upgrade"); | ||
386 | MHD_add_response_header (response, | ||
387 | MHD_HTTP_HEADER_UPGRADE, | ||
388 | "websocket"); | ||
389 | MHD_add_response_header (response, | ||
390 | MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, | ||
391 | sec_websocket_accept); | ||
392 | ret = MHD_queue_response (connection, | ||
393 | MHD_HTTP_SWITCHING_PROTOCOLS, | ||
394 | response); | ||
395 | MHD_destroy_response (response); | ||
396 | } | ||
397 | else | ||
398 | { | ||
399 | /* return error page */ | ||
400 | struct MHD_Response*response = MHD_create_response_from_buffer ( | ||
401 | strlen (PAGE_INVALID_WEBSOCKET_REQUEST), | ||
402 | PAGE_INVALID_WEBSOCKET_REQUEST, | ||
403 | MHD_RESPMEM_PERSISTENT); | ||
404 | ret = MHD_queue_response (connection, | ||
405 | MHD_HTTP_BAD_REQUEST, | ||
406 | response); | ||
407 | MHD_destroy_response (response); | ||
408 | } | ||
409 | } | ||
410 | else | ||
411 | { | ||
412 | struct MHD_Response*response = MHD_create_response_from_buffer ( | ||
413 | strlen (PAGE_NOT_FOUND), | ||
414 | PAGE_NOT_FOUND, | ||
415 | MHD_RESPMEM_PERSISTENT); | ||
416 | ret = MHD_queue_response (connection, | ||
417 | MHD_HTTP_NOT_FOUND, | ||
418 | response); | ||
419 | MHD_destroy_response (response); | ||
420 | } | ||
421 | |||
422 | return ret; | ||
423 | } | ||
424 | @end verbatim | ||
425 | @noindent | ||
426 | |||
427 | Please note that we skipped the check of the Host header field here, | ||
428 | because we don't know the host for this example. | ||
429 | |||
430 | @heading Decoding/encoding the websocket protocol with libmicrohttpd_ws | ||
431 | |||
432 | Once the websocket connection is established you can receive/send frame data | ||
433 | with the low-level socket functions @code{recv()} and @code{send()}. | ||
434 | The frame data which goes over the low-level socket is encoded according | ||
435 | to the websocket protocol. | ||
436 | To use received payload data, you need to decode the frame data first. | ||
437 | To send payload data, you need to encode it into frame data first. | ||
438 | |||
439 | @emph{libmicrohttpd_ws} provides serveral functions for encoding of | ||
440 | payload data and decoding of frame data: | ||
441 | |||
442 | @itemize @bullet | ||
443 | @item | ||
444 | @code{MHD_websocket_decode()} decodes received frame data. | ||
445 | The payload data may be of any kind, depending upon what the client has sent. | ||
446 | So this decode function is used for all kind of frames and returns | ||
447 | the frame type along with the payload data. | ||
448 | |||
449 | @item | ||
450 | @code{MHD_websocket_encode_text()} encodes text. | ||
451 | The text must be encoded with UTF-8. | ||
452 | |||
453 | @item | ||
454 | @code{MHD_websocket_encode_binary()} encodes binary data. | ||
455 | |||
456 | @item | ||
457 | @code{MHD_websocket_encode_ping()} encodes a ping request to | ||
458 | check whether the websocket is still valid and to test latency. | ||
459 | |||
460 | @item | ||
461 | @code{MHD_websocket_encode_ping()} encodes a pong response to | ||
462 | answer a received ping request. | ||
463 | |||
464 | @item | ||
465 | @code{MHD_websocket_encode_close()} encodes a close request. | ||
466 | |||
467 | @item | ||
468 | @code{MHD_websocket_free()} frees data returned by the encode/decode functions. | ||
469 | |||
470 | @end itemize | ||
471 | |||
472 | Since you could receive or send fragmented data (i. e. due to a too | ||
473 | small buffer passed to @code{recv}) all of these encode/decode | ||
474 | functions require a pointer to a @code{struct MHD_WebSocketStream} passed | ||
475 | as argument. | ||
476 | In this structure @emph{libmicrohttpd_ws} stores information | ||
477 | about encoding/decoding of the particular websocket. | ||
478 | For each websocket you need a unique @code{struct MHD_WebSocketStream} | ||
479 | to encode/decode with this library. | ||
480 | |||
481 | To create or destroy @code{struct MHD_WebSocketStream} | ||
482 | we have additional functions: | ||
483 | |||
484 | @itemize @bullet | ||
485 | @item | ||
486 | @code{MHD_websocket_stream_init()} allocates and initializes | ||
487 | a new @code{struct MHD_WebSocketStream}. | ||
488 | You can specify some options here to alter the behavior of the websocket stream. | ||
489 | |||
490 | @item | ||
491 | @code{MHD_websocket_stream_free()} frees a previously allocated | ||
492 | @code{struct MHD_WebSocketStream}. | ||
493 | |||
494 | @end itemize | ||
495 | |||
496 | With these encode/decode functions we can improve our @code{upgrade_handler} | ||
497 | callback function from an earlier example to a working websocket: | ||
498 | |||
499 | |||
500 | @verbatim | ||
501 | static void | ||
502 | upgrade_handler (void *cls, | ||
503 | struct MHD_Connection *connection, | ||
504 | void *con_cls, | ||
505 | const char *extra_in, | ||
506 | size_t extra_in_size, | ||
507 | MHD_socket fd, | ||
508 | struct MHD_UpgradeResponseHandle *urh) | ||
509 | { | ||
510 | /* make the socket blocking (operating-system-dependent code) */ | ||
511 | make_blocking (fd); | ||
512 | |||
513 | /* create a websocket stream for this connection */ | ||
514 | struct MHD_WebSocketStream* ws; | ||
515 | int result = MHD_websocket_stream_init (&ws, | ||
516 | 0, | ||
517 | 0); | ||
518 | if (0 != result) | ||
519 | { | ||
520 | /* Couldn't create the websocket stream. | ||
521 | * So we close the socket and leave | ||
522 | */ | ||
523 | MHD_upgrade_action (urh, | ||
524 | MHD_UPGRADE_ACTION_CLOSE); | ||
525 | return; | ||
526 | } | ||
527 | |||
528 | /* Let's wait for incoming data */ | ||
529 | const size_t buf_len = 256; | ||
530 | char buf[buf_len]; | ||
531 | ssize_t got; | ||
532 | while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws)) | ||
533 | { | ||
534 | got = recv (fd, | ||
535 | buf, | ||
536 | buf_len, | ||
537 | 0); | ||
538 | if (0 >= got) | ||
539 | { | ||
540 | /* the TCP/IP socket has been closed */ | ||
541 | break; | ||
542 | } | ||
543 | |||
544 | /* parse the entire received data */ | ||
545 | size_t buf_offset = 0; | ||
546 | while (buf_offset < (size_t) got) | ||
547 | { | ||
548 | size_t new_offset = 0; | ||
549 | char *frame_data = NULL; | ||
550 | size_t frame_len = 0; | ||
551 | int status = MHD_websocket_decode (ws, | ||
552 | buf + buf_offset, | ||
553 | ((size_t) got) - buf_offset, | ||
554 | &new_offset, | ||
555 | &frame_data, | ||
556 | &frame_len); | ||
557 | if (0 > status) | ||
558 | { | ||
559 | /* an error occurred and the connection must be closed */ | ||
560 | if (NULL != frame_data) | ||
561 | { | ||
562 | MHD_websocket_free (ws, frame_data); | ||
563 | } | ||
564 | break; | ||
565 | } | ||
566 | else | ||
567 | { | ||
568 | buf_offset += new_offset; | ||
569 | if (0 < status) | ||
570 | { | ||
571 | /* the frame is complete */ | ||
572 | switch (status) | ||
573 | { | ||
574 | case MHD_WEBSOCKET_STATUS_TEXT_FRAME: | ||
575 | /* The client has sent some text. | ||
576 | * We will display it and answer with a text frame. | ||
577 | */ | ||
578 | if (NULL != frame_data) | ||
579 | { | ||
580 | printf ("Received message: %s\n", frame_data); | ||
581 | MHD_websocket_free (ws, frame_data); | ||
582 | frame_data = NULL; | ||
583 | } | ||
584 | result = MHD_websocket_encode_text (ws, | ||
585 | "Hello", | ||
586 | 5, /* length of "Hello" */ | ||
587 | 0, | ||
588 | &frame_data, | ||
589 | &frame_len, | ||
590 | NULL); | ||
591 | if (0 == result) | ||
592 | { | ||
593 | send_all (fd, | ||
594 | frame_data, | ||
595 | frame_len); | ||
596 | } | ||
597 | break; | ||
598 | |||
599 | case MHD_WEBSOCKET_STATUS_CLOSE_FRAME: | ||
600 | /* if we receive a close frame, we will respond with one */ | ||
601 | MHD_websocket_free (ws, | ||
602 | frame_data); | ||
603 | frame_data = NULL; | ||
604 | |||
605 | result = MHD_websocket_encode_close (ws, | ||
606 | 0, | ||
607 | NULL, | ||
608 | 0, | ||
609 | &frame_data, | ||
610 | &frame_len); | ||
611 | if (0 == result) | ||
612 | { | ||
613 | send_all (fd, | ||
614 | frame_data, | ||
615 | frame_len); | ||
616 | } | ||
617 | break; | ||
618 | |||
619 | case MHD_WEBSOCKET_STATUS_PING_FRAME: | ||
620 | /* if we receive a ping frame, we will respond */ | ||
621 | /* with the corresponding pong frame */ | ||
622 | { | ||
623 | char *pong = NULL; | ||
624 | size_t pong_len = 0; | ||
625 | result = MHD_websocket_encode_pong (ws, | ||
626 | frame_data, | ||
627 | frame_len, | ||
628 | &pong, | ||
629 | &pong_len); | ||
630 | if (0 == result) | ||
631 | { | ||
632 | send_all (fd, | ||
633 | pong, | ||
634 | pong_len); | ||
635 | } | ||
636 | MHD_websocket_free (ws, | ||
637 | pong); | ||
638 | } | ||
639 | break; | ||
640 | |||
641 | default: | ||
642 | /* Other frame types are ignored | ||
643 | * in this minimal example. | ||
644 | * This is valid, because they become | ||
645 | * automatically skipped if we receive them unexpectedly | ||
646 | */ | ||
647 | break; | ||
648 | } | ||
649 | } | ||
650 | if (NULL != frame_data) | ||
651 | { | ||
652 | MHD_websocket_free (ws, frame_data); | ||
653 | } | ||
654 | } | ||
655 | } | ||
656 | } | ||
657 | |||
658 | /* free the websocket stream */ | ||
659 | MHD_websocket_stream_free (ws); | ||
660 | |||
661 | /* close the socket when it is not needed anymore */ | ||
662 | MHD_upgrade_action (urh, | ||
663 | MHD_UPGRADE_ACTION_CLOSE); | ||
664 | } | ||
665 | |||
666 | /* This helper function is used for the case that | ||
667 | * we need to resend some data | ||
668 | */ | ||
669 | static void | ||
670 | send_all (MHD_socket fd, | ||
671 | const char *buf, | ||
672 | size_t len) | ||
673 | { | ||
674 | ssize_t ret; | ||
675 | size_t off; | ||
676 | |||
677 | for (off = 0; off < len; off += ret) | ||
678 | { | ||
679 | ret = send (fd, | ||
680 | &buf[off], | ||
681 | (int) (len - off), | ||
682 | 0); | ||
683 | if (0 > ret) | ||
684 | { | ||
685 | if (EAGAIN == errno) | ||
686 | { | ||
687 | ret = 0; | ||
688 | continue; | ||
689 | } | ||
690 | break; | ||
691 | } | ||
692 | if (0 == ret) | ||
693 | break; | ||
694 | } | ||
695 | } | ||
696 | |||
697 | /* This helper function contains operating-system-dependent code and | ||
698 | * is used to make a socket blocking. | ||
699 | */ | ||
700 | static void | ||
701 | make_blocking (MHD_socket fd) | ||
702 | { | ||
703 | #if defined(MHD_POSIX_SOCKETS) | ||
704 | int flags; | ||
705 | |||
706 | flags = fcntl (fd, F_GETFL); | ||
707 | if (-1 == flags) | ||
708 | return; | ||
709 | if ((flags & ~O_NONBLOCK) != flags) | ||
710 | if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) | ||
711 | abort (); | ||
712 | #elif defined(MHD_WINSOCK_SOCKETS) | ||
713 | unsigned long flags = 0; | ||
714 | |||
715 | ioctlsocket (fd, FIONBIO, &flags); | ||
716 | #endif /* MHD_WINSOCK_SOCKETS */ | ||
717 | } | ||
718 | |||
719 | @end verbatim | ||
720 | @noindent | ||
721 | |||
722 | |||
723 | Please note that the websocket in this example is only half-duplex. | ||
724 | It waits until the blocking @code{recv()} call returns and | ||
725 | only does then something. | ||
726 | In this example all frame types are decoded by @emph{libmicrohttpd_ws}, | ||
727 | but we only do something when a text, ping or close frame is received. | ||
728 | Binary and pong frames are ignored in our code. | ||
729 | This is legit, because the server is only required to implement at | ||
730 | least support for ping frame or close frame (the other frame types | ||
731 | could be skipped in theory, because they don't require an answer). | ||
732 | The pong frame doesn't require an answer and whether text frames or | ||
733 | binary frames get an answer simply belongs to your server application. | ||
734 | So this is a valid minimal example. | ||
735 | |||
736 | Until this point you've learned everything you need to basically | ||
737 | use websockets with @emph{libmicrohttpd} and @emph{libmicrohttpd_ws}. | ||
738 | These libraries offer much more functions for some specific cases. | ||
739 | |||
740 | |||
741 | The further chapters of this tutorial focus on some specific problems | ||
742 | and the client site programming. | ||
743 | |||
744 | |||
745 | @heading Using full-duplex websockets | ||
746 | |||
747 | To use full-duplex websockets you can simply create two threads | ||
748 | per websocket connection. | ||
749 | One of these threads is used for receiving data with | ||
750 | a blocking @code{recv()} call and the other thread is triggered | ||
751 | by the application internal codes and sends the data. | ||
752 | |||
753 | A full-duplex websocket example is implemented in the example file | ||
754 | @code{websocket_chatserver_example.c}. | ||
755 | |||
756 | @heading Error handling | ||
757 | |||
758 | The most functions of @emph{libmicrohttpd_ws} return a value | ||
759 | of @code{enum MHD_WEBSOCKET_STATUS}. | ||
760 | The values of this enumeration can be converted into an integer | ||
761 | and have an easy interpretation: | ||
762 | |||
763 | @itemize @bullet | ||
764 | @item | ||
765 | If the value is less than zero an error occurred and the call has failed. | ||
766 | Check the enumeration values for more specific information. | ||
767 | |||
768 | @item | ||
769 | If the value is equal to zero, the call succeeded. | ||
770 | |||
771 | @item | ||
772 | If the value is greater than zero, the call succeeded and the value | ||
773 | specifies the decoded frame type. | ||
774 | Currently positive values are only returned by @code{MHD_websocket_decode()} | ||
775 | (of the functions with this return enumeration type). | ||
776 | |||
777 | @end itemize | ||
778 | |||
779 | A websocket stream can also get broken when invalid frame data is received. | ||
780 | Also the other site could send a close frame which puts the stream into | ||
781 | a state where it may not be used for regular communication. | ||
782 | Whether a stream has become broken, can be checked with | ||
783 | @code{MHD_websocket_stream_is_valid()}. | ||
784 | |||
785 | |||
786 | @heading Fragmentation | ||
787 | |||
788 | In addition to the regular TCP/IP fragmentation the websocket protocol also | ||
789 | supports fragmentation. | ||
790 | Fragmentation could be used for continuous payload data such as video data | ||
791 | from a webcam. | ||
792 | Whether or not you want to receive fragmentation is specified upon | ||
793 | initialization of the websocket stream. | ||
794 | If you pass @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} in the flags parameter | ||
795 | of @code{MHD_websocket_stream_init()} then you can receive fragments. | ||
796 | If you don't pass this flag (in the most cases you just pass zero as flags) | ||
797 | then you don't want to handle fragments on your own. | ||
798 | @emph{libmicrohttpd_ws} removes then the fragmentation for you | ||
799 | in the background. | ||
800 | You only get the completely assembled frames. | ||
801 | |||
802 | Upon encoding you specify whether or not you want to create a fragmented frame | ||
803 | by passing a flag to the corresponding encode function. | ||
804 | Only @code{MHD_websocket_encode_text()} and @code{MHD_websocket_encode_binary()} | ||
805 | can be used for fragmentation, because the other frame types may | ||
806 | not be fragmented. | ||
807 | Encoding fragmented frames is independent of | ||
808 | the @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} flag upon initialization. | ||
809 | |||
810 | @heading Quick guide to websockets in JavaScript | ||
811 | |||
812 | Websockets are supported in all modern web browsers. | ||
813 | You initialize a websocket connection by creating an instance of | ||
814 | the @code{WebSocket} class provided by the web browser. | ||
815 | |||
816 | There are some simple rules for using websockets in the browser: | ||
817 | |||
818 | @itemize @bullet | ||
819 | @item | ||
820 | When you initialize the instance of the websocket class you must pass an URL. | ||
821 | The URL must either start with @code{ws://} | ||
822 | (for not encrypted websocket protocol) or @code{wss://} | ||
823 | (for TLS-encrypted websocket protocol). | ||
824 | |||
825 | @strong{IMPORTANT:} If your website is accessed via @code{https://} | ||
826 | then you are in a security context, which means that you are only allowed to | ||
827 | access other secure protocols. | ||
828 | So you can only use @code{wss://} for websocket connections then. | ||
829 | If you try to @code{ws://} instead then your websocket connection will | ||
830 | automatically fail. | ||
831 | |||
832 | @item | ||
833 | The WebSocket class uses events to handle the receiving of data. | ||
834 | JavaScript is per definition a single-threaded language so | ||
835 | the receiving events will never overlap. | ||
836 | Sending is done directly by calling a method of the instance of | ||
837 | the WebSocket class. | ||
838 | |||
839 | @end itemize | ||
840 | |||
841 | |||
842 | Here is a short example for receiving/sending data to the same host | ||
843 | as the website is running on: | ||
844 | |||
845 | @verbatim | ||
846 | <!DOCTYPE html> | ||
847 | <html> | ||
848 | <head> | ||
849 | <meta charset="UTF-8"> | ||
850 | <title>Websocket Demo</title> | ||
851 | <script> | ||
852 | |||
853 | let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' + | ||
854 | window.location.host + '/chat'; | ||
855 | let socket = null; | ||
856 | |||
857 | window.onload = function(event) { | ||
858 | socket = new WebSocket(url); | ||
859 | socket.onopen = function(event) { | ||
860 | document.write('The websocket connection has been established.<br>'); | ||
861 | |||
862 | // Send some text | ||
863 | socket.send('Hello from JavaScript!'); | ||
864 | } | ||
865 | |||
866 | socket.onclose = function(event) { | ||
867 | document.write('The websocket connection has been closed.<br>'); | ||
868 | } | ||
869 | |||
870 | socket.onerror = function(event) { | ||
871 | document.write('An error occurred during the websocket communication.<br>'); | ||
872 | } | ||
873 | |||
874 | socket.onmessage = function(event) { | ||
875 | document.write('Websocket message received: ' + event.data + '<br>'); | ||
876 | } | ||
877 | } | ||
878 | |||
879 | </script> | ||
880 | </head> | ||
881 | <body> | ||
882 | </body> | ||
883 | </html> | ||
884 | |||
885 | @end verbatim | ||
886 | @noindent | ||
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 | } | ||
diff --git a/doc/libmicrohttpd-tutorial.texi b/doc/libmicrohttpd-tutorial.texi index de040fe8..7d3cd23a 100644 --- a/doc/libmicrohttpd-tutorial.texi +++ b/doc/libmicrohttpd-tutorial.texi | |||
@@ -68,6 +68,7 @@ Free Documentation License". | |||
68 | * Improved processing of POST data:: | 68 | * Improved processing of POST data:: |
69 | * Session management:: | 69 | * Session management:: |
70 | * Adding a layer of security:: | 70 | * Adding a layer of security:: |
71 | * Websockets:: | ||
71 | * Bibliography:: | 72 | * Bibliography:: |
72 | * License text:: | 73 | * License text:: |
73 | * Example programs:: | 74 | * Example programs:: |
@@ -109,6 +110,10 @@ Free Documentation License". | |||
109 | @chapter Adding a layer of security | 110 | @chapter Adding a layer of security |
110 | @include chapters/tlsauthentication.inc | 111 | @include chapters/tlsauthentication.inc |
111 | 112 | ||
113 | @node Websockets | ||
114 | @chapter Websockets | ||
115 | @include chapters/websocket.inc | ||
116 | |||
112 | @node Bibliography | 117 | @node Bibliography |
113 | @appendix Bibliography | 118 | @appendix Bibliography |
114 | @include chapters/bibliography.inc | 119 | @include chapters/bibliography.inc |
@@ -128,6 +133,7 @@ Free Documentation License". | |||
128 | * largepost.c:: | 133 | * largepost.c:: |
129 | * sessions.c:: | 134 | * sessions.c:: |
130 | * tlsauthentication.c:: | 135 | * tlsauthentication.c:: |
136 | * websocket.c:: | ||
131 | @end menu | 137 | @end menu |
132 | 138 | ||
133 | @node hellobrowser.c | 139 | @node hellobrowser.c |
@@ -178,4 +184,10 @@ Free Documentation License". | |||
178 | @verbatiminclude examples/tlsauthentication.c | 184 | @verbatiminclude examples/tlsauthentication.c |
179 | @end smalldisplay | 185 | @end smalldisplay |
180 | 186 | ||
187 | @node websocket.c | ||
188 | @section websocket.c | ||
189 | @smalldisplay | ||
190 | @verbatiminclude examples/websocket.c | ||
191 | @end smalldisplay | ||
192 | |||
181 | @bye | 193 | @bye |
diff --git a/doc/libmicrohttpd.texi b/doc/libmicrohttpd.texi index 8e275a3b..a6bd12eb 100644 --- a/doc/libmicrohttpd.texi +++ b/doc/libmicrohttpd.texi | |||
@@ -67,6 +67,7 @@ Free Documentation License". | |||
67 | * microhttpd-post:: Adding a @code{POST} processor. | 67 | * microhttpd-post:: Adding a @code{POST} processor. |
68 | * microhttpd-info:: Obtaining and modifying status information. | 68 | * microhttpd-info:: Obtaining and modifying status information. |
69 | * microhttpd-util:: Utilities. | 69 | * microhttpd-util:: Utilities. |
70 | * microhttpd-websocket:: Websockets. | ||
70 | 71 | ||
71 | Appendices | 72 | Appendices |
72 | 73 | ||
@@ -1246,6 +1247,493 @@ list. | |||
1246 | @end deftp | 1247 | @end deftp |
1247 | 1248 | ||
1248 | 1249 | ||
1250 | @deftp {Enumeration} MHD_WEBSOCKET_FLAG | ||
1251 | @cindex websocket | ||
1252 | Options for the MHD websocket stream. | ||
1253 | |||
1254 | This is used for initialization of a websocket stream when calling | ||
1255 | @code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2} and | ||
1256 | alters the behavior of the websocket stream. | ||
1257 | |||
1258 | Note that websocket streams are only available if you include the header file | ||
1259 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1260 | |||
1261 | @table @code | ||
1262 | @item MHD_WEBSOCKET_FLAG_SERVER | ||
1263 | The websocket stream is initialized in server mode (default). | ||
1264 | Thus all outgoing payload will not be masked. | ||
1265 | All incoming payload must be masked. | ||
1266 | |||
1267 | This flag cannot be used together with @code{MHD_WEBSOCKET_FLAG_CLIENT}. | ||
1268 | |||
1269 | @item MHD_WEBSOCKET_FLAG_CLIENT | ||
1270 | The websocket stream is initialized in client mode. | ||
1271 | You will usually never use that mode in combination with @emph{libmicrohttpd}, | ||
1272 | because @emph{libmicrohttpd} provides a server and not a client. | ||
1273 | In client mode all outgoing payload will be masked | ||
1274 | (XOR-ed with random values). | ||
1275 | All incoming payload must be unmasked. | ||
1276 | If you use this mode, you must always call @code{MHD_websocket_stream_init2} | ||
1277 | instead of @code{MHD_websocket_stream_init}, because you need | ||
1278 | to pass a random number generator callback function for masking. | ||
1279 | |||
1280 | This flag cannot be used together with @code{MHD_WEBSOCKET_FLAG_SERVER}. | ||
1281 | |||
1282 | @item MHD_WEBSOCKET_FLAG_NO_FRAGMENTS | ||
1283 | You don't want to get fragmented data while decoding (default). | ||
1284 | Fragmented frames will be internally put together until | ||
1285 | they are complete. | ||
1286 | Whether or not data is fragmented is decided | ||
1287 | by the sender of the data during encoding. | ||
1288 | |||
1289 | This cannot be used together with @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS}. | ||
1290 | |||
1291 | @item MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS | ||
1292 | You want fragmented data, if it appears while decoding. | ||
1293 | You will receive the content of the fragmented frame, | ||
1294 | but if you are decoding text, you will never get an unfinished | ||
1295 | UTF-8 sequence (if the sequence appears between two fragments). | ||
1296 | Instead the text will end before the unfinished UTF-8 sequence. | ||
1297 | With the next fragment, which finishes the UTF-8 sequence, | ||
1298 | you will get the complete UTF-8 sequence. | ||
1299 | |||
1300 | This cannot be used together with @code{MHD_WEBSOCKET_FLAG_NO_FRAGMENTS}. | ||
1301 | |||
1302 | @item MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR | ||
1303 | If the websocket stream becomes invalid during decoding due to | ||
1304 | protocol errors, a matching close frame will automatically | ||
1305 | be generated. | ||
1306 | The close frame will be returned via the parameters | ||
1307 | @code{payload} and @code{payload_len} of @code{MHD_websocket_decode} and | ||
1308 | the return value is negative (a value of @code{enum MHD_WEBSOCKET_STATUS}). | ||
1309 | |||
1310 | The generated close frame must be freed by the caller | ||
1311 | with @code{MHD_websocket_free}. | ||
1312 | |||
1313 | @end table | ||
1314 | @end deftp | ||
1315 | |||
1316 | |||
1317 | @deftp {Enumeration} MHD_WEBSOCKET_FRAGMENTATION | ||
1318 | @cindex websocket | ||
1319 | This enumeration is used to specify the fragmentation behavior | ||
1320 | when encoding of data (text/binary) for a websocket stream. | ||
1321 | This is used with @code{MHD_websocket_encode_text} or | ||
1322 | @code{MHD_websocket_encode_binary}. | ||
1323 | |||
1324 | Note that websocket streams are only available if you include the header file | ||
1325 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1326 | |||
1327 | @table @code | ||
1328 | @item MHD_WEBSOCKET_FRAGMENTATION_NONE | ||
1329 | You don't want to use fragmentation. | ||
1330 | The encoded frame consists of only one frame. | ||
1331 | |||
1332 | @item MHD_WEBSOCKET_FRAGMENTATION_FIRST | ||
1333 | You want to use fragmentation. | ||
1334 | The encoded frame is the first frame of | ||
1335 | a series of data frames of the same type | ||
1336 | (text or binary). | ||
1337 | You may send control frames (ping, pong or close) | ||
1338 | between these data frames. | ||
1339 | |||
1340 | @item MHD_WEBSOCKET_FRAGMENTATION_FOLLOWING | ||
1341 | You want to use fragmentation. | ||
1342 | The encoded frame is not the first frame of | ||
1343 | the series of data frames, but also not the last one. | ||
1344 | You may send control frames (ping, pong or close) | ||
1345 | between these data frames. | ||
1346 | |||
1347 | @item MHD_WEBSOCKET_FRAGMENTATION_LAST | ||
1348 | You want to use fragmentation. | ||
1349 | The encoded frame is the last frame of | ||
1350 | the series of data frames, but also not the first one. | ||
1351 | After this frame, you may send all types of frames again. | ||
1352 | |||
1353 | @end table | ||
1354 | @end deftp | ||
1355 | |||
1356 | |||
1357 | @deftp {Enumeration} MHD_WEBSOCKET_STATUS | ||
1358 | @cindex websocket | ||
1359 | This enumeration is used for the return value of almost | ||
1360 | every websocket stream function. | ||
1361 | Errors are negative and values equal to or above zero mean a success. | ||
1362 | Positive values are only used by @code{MHD_websocket_decode}. | ||
1363 | |||
1364 | Note that websocket streams are only available if you include the header file | ||
1365 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1366 | |||
1367 | @table @code | ||
1368 | @item MHD_WEBSOCKET_STATUS_OK | ||
1369 | The call succeeded. | ||
1370 | Especially for @code{MHD_websocket_decode} this means that no error occurred, | ||
1371 | but also no frame has been completed yet. | ||
1372 | For other functions this means simply a success. | ||
1373 | |||
1374 | @item MHD_WEBSOCKET_STATUS_TEXT_FRAME | ||
1375 | @code{MHD_websocket_decode} has decoded a text frame. | ||
1376 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1377 | the decoded text (if any). | ||
1378 | You must free the returned @code{payload} after use with | ||
1379 | @code{MHD_websocket_free}. | ||
1380 | |||
1381 | @item MHD_WEBSOCKET_STATUS_BINARY_FRAME | ||
1382 | @code{MHD_websocket_decode} has decoded a binary frame. | ||
1383 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1384 | the decoded binary data (if any). | ||
1385 | You must free the returned @code{payload} after use with | ||
1386 | @code{MHD_websocket_free}. | ||
1387 | |||
1388 | @item MHD_WEBSOCKET_STATUS_CLOSE_FRAME | ||
1389 | @code{MHD_websocket_decode} has decoded a close frame. | ||
1390 | This means you must close the socket using @code{MHD_upgrade_action} | ||
1391 | with @code{MHD_UPGRADE_ACTION_CLOSE}. | ||
1392 | You may respond with a close frame before closing. | ||
1393 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1394 | the close reason (if any). | ||
1395 | The close reason starts with a two byte sequence of close code | ||
1396 | in network byte order (see @code{enum MHD_WEBSOCKET_CLOSEREASON}). | ||
1397 | After these two bytes a UTF-8 encoded close reason may follow. | ||
1398 | You can call @code{MHD_websocket_split_close_reason} to split that | ||
1399 | close reason. | ||
1400 | You must free the returned @code{payload} after use with | ||
1401 | @code{MHD_websocket_free}. | ||
1402 | |||
1403 | @item MHD_WEBSOCKET_STATUS_PING_FRAME | ||
1404 | @code{MHD_websocket_decode} has decoded a ping frame. | ||
1405 | You should respond to this with a pong frame. | ||
1406 | The pong frame must contain the same binary data as | ||
1407 | the corresponding ping frame (if it had any). | ||
1408 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1409 | the binary ping data (if any). | ||
1410 | You must free the returned @code{payload} after use with | ||
1411 | @code{MHD_websocket_free}. | ||
1412 | |||
1413 | @item MHD_WEBSOCKET_STATUS_PONG_FRAME | ||
1414 | @code{MHD_websocket_decode} has decoded a pong frame. | ||
1415 | You should usually only receive pong frames if you sent | ||
1416 | a ping frame before. | ||
1417 | The binary data should be equal to your ping frame and can be | ||
1418 | used to distinguish the response if you sent multiple ping frames. | ||
1419 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1420 | the binary pong data (if any). | ||
1421 | You must free the returned @code{payload} after use with | ||
1422 | @code{MHD_websocket_free}. | ||
1423 | |||
1424 | @item MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT | ||
1425 | @code{MHD_websocket_decode} has decoded a text frame fragment. | ||
1426 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1427 | the decoded text (if any). | ||
1428 | This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FRAME}, but it can only | ||
1429 | appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} during | ||
1430 | the call of @code{MHD_websocket_stream_init} or | ||
1431 | @code{MHD_websocket_stream_init2}. | ||
1432 | You must free the returned @code{payload} after use with | ||
1433 | @code{MHD_websocket_free}. | ||
1434 | |||
1435 | @item MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT | ||
1436 | @code{MHD_websocket_decode} has decoded a binary frame fragment. | ||
1437 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1438 | the decoded binary data (if any). | ||
1439 | This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FRAME}, but it can only | ||
1440 | appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} during | ||
1441 | the call of @code{MHD_websocket_stream_init} or | ||
1442 | @code{MHD_websocket_stream_init2}. | ||
1443 | You must free the returned @code{payload} after use with | ||
1444 | @code{MHD_websocket_free}. | ||
1445 | |||
1446 | @item MHD_WEBSOCKET_STATUS_TEXT_NEXT_FRAGMENT | ||
1447 | @code{MHD_websocket_decode} has decoded the next text frame fragment. | ||
1448 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1449 | the decoded text (if any). | ||
1450 | This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT}, but it appears | ||
1451 | only after the first and before the last fragment of a series of fragments. | ||
1452 | It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} | ||
1453 | during the call of @code{MHD_websocket_stream_init} or | ||
1454 | @code{MHD_websocket_stream_init2}. | ||
1455 | You must free the returned @code{payload} after use with | ||
1456 | @code{MHD_websocket_free}. | ||
1457 | |||
1458 | @item MHD_WEBSOCKET_STATUS_BINARY_NEXT_FRAGMENT | ||
1459 | @code{MHD_websocket_decode} has decoded the next binary frame fragment. | ||
1460 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1461 | the decoded binary data (if any). | ||
1462 | This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FIRST_FRAGMENT}, but it appears | ||
1463 | only after the first and before the last fragment of a series of fragments. | ||
1464 | It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} | ||
1465 | during the call of @code{MHD_websocket_stream_init} or | ||
1466 | @code{MHD_websocket_stream_init2}. | ||
1467 | You must free the returned @code{payload} after use with | ||
1468 | @code{MHD_websocket_free}. | ||
1469 | |||
1470 | @item MHD_WEBSOCKET_STATUS_TEXT_LAST_FRAGMENT | ||
1471 | @code{MHD_websocket_decode} has decoded the last text frame fragment. | ||
1472 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1473 | the decoded text (if any). | ||
1474 | This is like @code{MHD_WEBSOCKET_STATUS_TEXT_FIRST_FRAGMENT}, but it appears | ||
1475 | only for the last fragment of a series of fragments. | ||
1476 | It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} | ||
1477 | during the call of @code{MHD_websocket_stream_init} or | ||
1478 | @code{MHD_websocket_stream_init2}. | ||
1479 | You must free the returned @code{payload} after use with | ||
1480 | @code{MHD_websocket_free}. | ||
1481 | |||
1482 | @item MHD_WEBSOCKET_STATUS_BINARY_LAST_FRAGMENT | ||
1483 | @code{MHD_websocket_decode} has decoded the last binary frame fragment. | ||
1484 | The parameters @code{payload} and @code{payload_len} are filled with | ||
1485 | the decoded binary data (if any). | ||
1486 | This is like @code{MHD_WEBSOCKET_STATUS_BINARY_FIRST_FRAGMENT}, but it appears | ||
1487 | only for the last fragment of a series of fragments. | ||
1488 | It can only appear if you specified @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} | ||
1489 | during the call of @code{MHD_websocket_stream_init} or | ||
1490 | @code{MHD_websocket_stream_init2}. | ||
1491 | You must free the returned @code{payload} after use with | ||
1492 | @code{MHD_websocket_free}. | ||
1493 | |||
1494 | @item MHD_WEBSOCKET_STATUS_PROTOCOL_ERROR | ||
1495 | The call failed and the stream is invalid now for decoding. | ||
1496 | You must close the websocket now using @code{MHD_upgrade_action} | ||
1497 | with @code{MHD_UPGRADE_ACTION_CLOSE}. | ||
1498 | You may send a close frame before closing. | ||
1499 | This is only used by @code{MHD_websocket_decode} and happens | ||
1500 | if the stream contains errors (i. e. invalid byte data). | ||
1501 | |||
1502 | @item MHD_WEBSOCKET_STATUS_STREAM_BROKEN | ||
1503 | You tried to decode something, but the stream has already | ||
1504 | been marked invalid. | ||
1505 | You must close the websocket now using @code{MHD_upgrade_action} | ||
1506 | with @code{MHD_UPGRADE_ACTION_CLOSE}. | ||
1507 | You may send a close frame before closing. | ||
1508 | This is only used by @code{MHD_websocket_decode} and happens | ||
1509 | if you call @code{MDM_websocket_decode} again after | ||
1510 | has been invalidated. | ||
1511 | You can call @code{MHD_websocket_stream_is_valid} at any time | ||
1512 | to check whether a stream is invalid or not. | ||
1513 | |||
1514 | @item MHD_WEBSOCKET_STATUS_MEMORY_ERROR | ||
1515 | A memory allocation failed. The stream remains valid. | ||
1516 | If this occurred while decoding, the decoding could be | ||
1517 | possible later if enough memory is available. | ||
1518 | This could happen while decoding if you received a too big data frame. | ||
1519 | You could try to specify max_payload_size during the call of | ||
1520 | @code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2} to | ||
1521 | avoid this and close the websocket instead. | ||
1522 | |||
1523 | @item MHD_WEBSOCKET_STATUS_PARAMETER_ERROR | ||
1524 | You passed invalid parameters during the function call | ||
1525 | (i. e. a NULL pointer for a required parameter). | ||
1526 | The stream remains valid. | ||
1527 | |||
1528 | @item MHD_WEBSOCKET_STATUS_MAXIMUM_SIZE_EXCEEDED | ||
1529 | The maximum payload size has been exceeded. | ||
1530 | If you got this return code from @code{MHD_websocket_decode} then | ||
1531 | the stream becomes invalid and the websocket must be closed | ||
1532 | using @code{MHD_upgrade_action} with @code{MHD_UPGRADE_ACTION_CLOSE}. | ||
1533 | You may send a close frame before closing. | ||
1534 | The maximum payload size is specified during the call of | ||
1535 | @code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2}. | ||
1536 | This can also appear if you specified 0 as maximum payload size | ||
1537 | when the message is greater than the maximum allocatable memory size | ||
1538 | (i. e. more than 4 GiB on 32 bit systems). | ||
1539 | If you got this return code from @code{MHD_websocket_encode_close}, | ||
1540 | @code{MHD_websocket_encode_ping} or @code{MHD_websocket_encode_pong} then | ||
1541 | you passed to much payload data. The stream remains valid then. | ||
1542 | |||
1543 | @item MHD_WEBSOCKET_STATUS_UTF8_ENCODING_ERROR | ||
1544 | An UTF-8 sequence is invalid. | ||
1545 | If you got this return code from @code{MHD_websocket_decode} then | ||
1546 | the stream becomes invalid and you must close the websocket | ||
1547 | using @code{MHD_upgrade_action} with @code{MHD_UPGRADE_ACTION_CLOSE}. | ||
1548 | You may send a close frame before closing. | ||
1549 | If you got this from @code{MHD_websocket_encode_text} or | ||
1550 | @code{MHD_websocket_encode_close} then you passed invalid UTF-8 text. | ||
1551 | The stream remains valid then. | ||
1552 | |||
1553 | @item MHD_WEBSOCKET_STATUS_NO_WEBSOCKET_HANDSHAKE_HEADER | ||
1554 | A check routine for the HTTP headers came to the conclusion that | ||
1555 | the header value isn't valid for a websocket handshake request. | ||
1556 | This value can only be returned from the following functions: | ||
1557 | @code{MHD_websocket_check_http_version}, | ||
1558 | @code{MHD_websocket_check_connection_header}, | ||
1559 | @code{MHD_websocket_check_upgrade_header}, | ||
1560 | @code{MHD_websocket_check_version_header}, | ||
1561 | @code{MHD_websocket_create_accept_header} | ||
1562 | |||
1563 | @end table | ||
1564 | @end deftp | ||
1565 | |||
1566 | |||
1567 | @deftp {Enumeration} MHD_WEBSOCKET_CLOSEREASON | ||
1568 | @cindex websocket | ||
1569 | Enumeration of possible close reasons for websocket close frames. | ||
1570 | |||
1571 | The possible values are specified in RFC 6455 7.4.1 | ||
1572 | These close reasons here are the default set specified by RFC 6455, | ||
1573 | but also other close reasons could be used. | ||
1574 | |||
1575 | The definition is for short: | ||
1576 | @itemize @bullet | ||
1577 | @item 0-999 are never used (if you pass 0 in | ||
1578 | @code{MHD_websocket_encode_close} then no close reason is used). | ||
1579 | @item 1000-2999 are specified by RFC 6455. | ||
1580 | @item 3000-3999 are specified by libraries, etc. but must be registered by IANA. | ||
1581 | @item 4000-4999 are reserved for private use. | ||
1582 | @end itemize | ||
1583 | |||
1584 | Note that websocket streams are only available if you include the header file | ||
1585 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1586 | |||
1587 | @table @code | ||
1588 | @item MHD_WEBSOCKET_CLOSEREASON_NO_REASON | ||
1589 | This value is used as placeholder for @code{MHD_websocket_encode_close} | ||
1590 | to tell that you don't want to specify any reason. | ||
1591 | If you use this value then no reason text may be used. | ||
1592 | This value cannot be a result of decoding, because this value | ||
1593 | is not a valid close reason for the websocket protocol. | ||
1594 | |||
1595 | @item MHD_WEBSOCKET_CLOSEREASON_REGULAR | ||
1596 | You close the websocket because it fulfilled its purpose and shall | ||
1597 | now be closed in a normal, planned way. | ||
1598 | |||
1599 | @item MHD_WEBSOCKET_CLOSEREASON_GOING_AWAY | ||
1600 | You close the websocket because you are shutting down the server or | ||
1601 | something similar. | ||
1602 | |||
1603 | @item MHD_WEBSOCKET_CLOSEREASON_PROTOCOL_ERROR | ||
1604 | You close the websocket because a protocol error occurred | ||
1605 | during decoding (i. e. invalid byte data). | ||
1606 | |||
1607 | @item MHD_WEBSOCKET_CLOSEREASON_UNSUPPORTED_DATATYPE | ||
1608 | You close the websocket because you received data which you don't accept. | ||
1609 | For example if you received a binary frame, | ||
1610 | but your application only expects text frames. | ||
1611 | |||
1612 | @item MHD_WEBSOCKET_CLOSEREASON_MALFORMED_UTF8 | ||
1613 | You close the websocket because it contains malformed UTF-8. | ||
1614 | The UTF-8 validity is automatically checked by @code{MHD_websocket_decode}, | ||
1615 | so you don't need to check it on your own. | ||
1616 | UTF-8 is specified in RFC 3629. | ||
1617 | |||
1618 | @item MHD_WEBSOCKET_CLOSEREASON_POLICY_VIOLATED | ||
1619 | You close the websocket because you received a frame which is too big | ||
1620 | to process. | ||
1621 | You can specify the maximum allowed payload size during the call of | ||
1622 | @code{MHD_websocket_stream_init} or @code{MHD_websocket_stream_init2}. | ||
1623 | |||
1624 | @item MHD_WEBSOCKET_CLOSEREASON_MISSING_EXTENSION | ||
1625 | This status code can be sent by the client if it | ||
1626 | expected a specific extension, but this extension hasn't been negotiated. | ||
1627 | |||
1628 | @item MHD_WEBSOCKET_CLOSEREASON_UNEXPECTED_CONDITION | ||
1629 | The server closes the websocket because it encountered | ||
1630 | an unexpected condition that prevented it from fulfilling the request. | ||
1631 | |||
1632 | @end table | ||
1633 | @end deftp | ||
1634 | |||
1635 | |||
1636 | @deftp {Enumeration} MHD_WEBSOCKET_UTF8STEP | ||
1637 | @cindex websocket | ||
1638 | Enumeration of possible UTF-8 check steps for websocket functions | ||
1639 | |||
1640 | These values are used during the encoding of fragmented text frames | ||
1641 | or for error analysis while encoding text frames. | ||
1642 | Its values specify the next step of the UTF-8 check. | ||
1643 | UTF-8 sequences consist of one to four bytes. | ||
1644 | This enumeration just says how long the current UTF-8 sequence is | ||
1645 | and what is the next expected byte. | ||
1646 | |||
1647 | Note that websocket streams are only available if you include the header file | ||
1648 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1649 | |||
1650 | @table @code | ||
1651 | @item MHD_WEBSOCKET_UTF8STEP_NORMAL | ||
1652 | There is no open UTF-8 sequence. | ||
1653 | The next byte must be 0x00-0x7F or 0xC2-0xF4. | ||
1654 | |||
1655 | @item MHD_WEBSOCKET_UTF8STEP_UTF2TAIL_1OF1 | ||
1656 | The second byte of a two byte UTF-8 sequence. | ||
1657 | The first byte was 0xC2-0xDF. | ||
1658 | The next byte must be 0x80-0xBF. | ||
1659 | |||
1660 | @item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL1_1OF2 | ||
1661 | The second byte of a three byte UTF-8 sequence. | ||
1662 | The first byte was 0xE0. | ||
1663 | The next byte must be 0xA0-0xBF. | ||
1664 | |||
1665 | @item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL2_1OF2 | ||
1666 | The second byte of a three byte UTF-8 sequence. | ||
1667 | The first byte was 0xED. | ||
1668 | The next byte must by 0x80-0x9F. | ||
1669 | |||
1670 | @item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_1OF2 | ||
1671 | The second byte of a three byte UTF-8 sequence. | ||
1672 | The first byte was 0xE1-0xEC or 0xEE-0xEF. | ||
1673 | The next byte must be 0x80-0xBF. | ||
1674 | |||
1675 | @item MHD_WEBSOCKET_UTF8STEP_UTF3TAIL_2OF2 | ||
1676 | The third byte of a three byte UTF-8 sequence. | ||
1677 | The next byte must be 0x80-0xBF. | ||
1678 | |||
1679 | @item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL1_1OF3 | ||
1680 | The second byte of a four byte UTF-8 sequence. | ||
1681 | The first byte was 0xF0. | ||
1682 | The next byte must be 0x90-0xBF. | ||
1683 | |||
1684 | @item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL2_1OF3 | ||
1685 | The second byte of a four byte UTF-8 sequence. | ||
1686 | The first byte was 0xF4. | ||
1687 | The next byte must be 0x80-0x8F. | ||
1688 | |||
1689 | @item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_1OF3 | ||
1690 | The second byte of a four byte UTF-8 sequence. | ||
1691 | The first byte was 0xF1-0xF3. | ||
1692 | The next byte must be 0x80-0xBF. | ||
1693 | |||
1694 | @item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_2OF3 | ||
1695 | The third byte of a four byte UTF-8 sequence. | ||
1696 | The next byte must be 0x80-0xBF. | ||
1697 | |||
1698 | @item MHD_WEBSOCKET_UTF8STEP_UTF4TAIL_3OF3 | ||
1699 | The fourth byte of a four byte UTF-8 sequence. | ||
1700 | The next byte must be 0x80-0xBF. | ||
1701 | |||
1702 | @end table | ||
1703 | @end deftp | ||
1704 | |||
1705 | |||
1706 | @deftp {Enumeration} MHD_WEBSOCKET_VALIDITY | ||
1707 | @cindex websocket | ||
1708 | Enumeration of validity values of a websocket stream | ||
1709 | |||
1710 | These values are used for @code{MHD_websocket_stream_is_valid} | ||
1711 | and specify the validity status. | ||
1712 | |||
1713 | Note that websocket streams are only available if you include the header file | ||
1714 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
1715 | |||
1716 | @table @code | ||
1717 | @item MHD_WEBSOCKET_VALIDITY_INVALID | ||
1718 | The stream is invalid. | ||
1719 | It cannot be used for decoding anymore. | ||
1720 | |||
1721 | @item MHD_WEBSOCKET_VALIDITY_VALID | ||
1722 | The stream is valid. | ||
1723 | Decoding works as expected. | ||
1724 | |||
1725 | @item MHD_WEBSOCKET_VALIDITY_ONLY_VALID_FOR_CONTROL_FRAMES | ||
1726 | The stream has received a close frame and | ||
1727 | is partly invalid. | ||
1728 | You can still use the stream for decoding, | ||
1729 | but if a data frame is received an error will be reported. | ||
1730 | After a close frame has been sent, no data frames | ||
1731 | may follow from the sender of the close frame. | ||
1732 | |||
1733 | @end table | ||
1734 | @end deftp | ||
1735 | |||
1736 | |||
1249 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 1737 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1250 | 1738 | ||
1251 | @c ------------------------------------------------------------ | 1739 | @c ------------------------------------------------------------ |
@@ -1291,6 +1779,12 @@ Information about an MHD daemon. | |||
1291 | @end deftp | 1779 | @end deftp |
1292 | 1780 | ||
1293 | 1781 | ||
1782 | @deftp {C Struct} MHD_WebSocketStream | ||
1783 | @cindex websocket | ||
1784 | Information about a MHD websocket stream. | ||
1785 | @end deftp | ||
1786 | |||
1787 | |||
1294 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 1788 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1295 | 1789 | ||
1296 | @c ------------------------------------------------------------ | 1790 | @c ------------------------------------------------------------ |
@@ -1549,6 +2043,95 @@ iteration. | |||
1549 | @end deftypefn | 2043 | @end deftypefn |
1550 | 2044 | ||
1551 | 2045 | ||
2046 | @deftypefn {Function Pointer} void* {*MHD_WebSocketMallocCallback} (size_t buf_len) | ||
2047 | @cindex websocket | ||
2048 | This callback function is used internally by many websocket functions | ||
2049 | for allocating data. | ||
2050 | By default @code{malloc} is used. | ||
2051 | You can use your own allocation function with @code{MHD_websocket_stream_init2} | ||
2052 | if you wish to. | ||
2053 | This can be useful for operating systems like Windows | ||
2054 | where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent. | ||
2055 | You can call the associated @code{malloc} callback of | ||
2056 | a websocket stream with @code{MHD_websocket_malloc}. | ||
2057 | |||
2058 | @table @var | ||
2059 | @item buf_len | ||
2060 | size of the buffer to allocate in bytes. | ||
2061 | @end table | ||
2062 | |||
2063 | Return the pointer of the allocated buffer or @code{NULL} on failure. | ||
2064 | @end deftypefn | ||
2065 | |||
2066 | |||
2067 | @deftypefn {Function Pointer} void* {*MHD_WebSocketReallocCallback} (void *buf, size_t new_buf_len) | ||
2068 | @cindex websocket | ||
2069 | This callback function is used internally by many websocket | ||
2070 | functions for reallocating data. | ||
2071 | By default @code{realloc} is used. | ||
2072 | You can use your own reallocation function with | ||
2073 | @code{MHD_websocket_stream_init2} if you wish to. | ||
2074 | This can be useful for operating systems like Windows | ||
2075 | where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent. | ||
2076 | You can call the associated @code{realloc} callback of | ||
2077 | a websocket stream with @code{MHD_websocket_realloc}. | ||
2078 | |||
2079 | @table @var | ||
2080 | @item buf | ||
2081 | current buffer, may be @code{NULL}; | ||
2082 | |||
2083 | @item new_buf_len | ||
2084 | new size of the buffer in bytes. | ||
2085 | @end table | ||
2086 | |||
2087 | Return the pointer of the reallocated buffer or @code{NULL} on failure. | ||
2088 | On failure the old pointer must remain valid. | ||
2089 | @end deftypefn | ||
2090 | |||
2091 | |||
2092 | @deftypefn {Function Pointer} void {*MHD_WebSocketFreeCallback} (void *buf) | ||
2093 | @cindex websocket | ||
2094 | This callback function is used internally by many websocket | ||
2095 | functions for freeing data. | ||
2096 | By default @code{free} is used. | ||
2097 | You can use your own free function with | ||
2098 | @code{MHD_websocket_stream_init2} if you wish to. | ||
2099 | This can be useful for operating systems like Windows | ||
2100 | where @code{malloc}, @code{realloc} and @code{free} are compiler-dependent. | ||
2101 | You can call the associated @code{free} callback of | ||
2102 | a websocket stream with @code{MHD_websocket_free}. | ||
2103 | |||
2104 | @table @var | ||
2105 | @item cls | ||
2106 | current buffer to free, this may be @code{NULL} then nothing happens. | ||
2107 | @end table | ||
2108 | @end deftypefn | ||
2109 | |||
2110 | |||
2111 | @deftypefn {Function Pointer} size_t {*MHD_WebSocketRandomNumberGenerator} (void *cls, void* buf, size_t buf_len) | ||
2112 | @cindex websocket | ||
2113 | This callback function is used for generating random numbers | ||
2114 | for masking payload data in client mode. | ||
2115 | If you use websockets in server mode with @emph{libmicrohttpd} then | ||
2116 | you don't need a random number generator, because | ||
2117 | the server doesn't mask its outgoing messages. | ||
2118 | However if you wish to use a websocket stream in client mode, | ||
2119 | you must pass this callback function to @code{MHD_websocket_stream_init2}. | ||
2120 | |||
2121 | @table @var | ||
2122 | @item cls | ||
2123 | closure specified in @code{MHD_websocket_stream_init2}; | ||
2124 | @item buf | ||
2125 | buffer to fill with random values; | ||
2126 | @item buf_len | ||
2127 | size of buffer in bytes. | ||
2128 | @end table | ||
2129 | |||
2130 | Return the number of generated random bytes. | ||
2131 | The return value should usually equal to buf_len. | ||
2132 | @end deftypefn | ||
2133 | |||
2134 | |||
1552 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 2135 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1553 | 2136 | ||
1554 | @c ------------------------------------------------------------ | 2137 | @c ------------------------------------------------------------ |
@@ -3346,6 +3929,704 @@ shorter afterwards due to elimination of escape sequences). | |||
3346 | 3929 | ||
3347 | 3930 | ||
3348 | 3931 | ||
3932 | |||
3933 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
3934 | |||
3935 | @c ------------------------------------------------------------ | ||
3936 | @node microhttpd-websocket | ||
3937 | @chapter Websocket functions. | ||
3938 | |||
3939 | @noindent | ||
3940 | Websocket functions provide what you need to use an upgraded connection | ||
3941 | as a websocket. | ||
3942 | These functions are only available if you include the header file | ||
3943 | @code{microhttpd_ws.h} and compiled @emph{libmicrohttpd} with websockets. | ||
3944 | |||
3945 | @menu | ||
3946 | * microhttpd-websocket handshake:: Websocket handshake functions | ||
3947 | * microhttpd-websocket stream:: Websocket stream functions | ||
3948 | * microhttpd-websocket decode:: Websocket decode functions | ||
3949 | * microhttpd-websocket encode:: Websocket encode functions | ||
3950 | * microhttpd-websocket memory:: Websocket memory functions | ||
3951 | @end menu | ||
3952 | |||
3953 | @c ------------------------------------------------------------ | ||
3954 | @node microhttpd-websocket handshake | ||
3955 | @section Websocket handshake functions | ||
3956 | |||
3957 | |||
3958 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_http_version (const char* http_version) | ||
3959 | @cindex websocket | ||
3960 | Checks the HTTP version of the incoming request. | ||
3961 | Websocket requests are only allowed for HTTP/1.1 or above. | ||
3962 | |||
3963 | @table @var | ||
3964 | @item http_version | ||
3965 | The value of the @code{version} parameter of your | ||
3966 | @code{access_handler} callback. | ||
3967 | If you pass @code{NULL} then this is handled like a not | ||
3968 | matching HTTP version. | ||
3969 | @end table | ||
3970 | |||
3971 | Returns 0 when the HTTP version is | ||
3972 | valid for a websocket request and | ||
3973 | a value less than zero when the HTTP version isn't | ||
3974 | valid for a websocket request. | ||
3975 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
3976 | @end deftypefun | ||
3977 | |||
3978 | |||
3979 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_connection_header (const char* connection_header) | ||
3980 | @cindex websocket | ||
3981 | Checks the value of the @code{Connection} HTTP request header. | ||
3982 | Websocket requests require the token @code{Upgrade} in | ||
3983 | the @code{Connection} HTTP request header. | ||
3984 | |||
3985 | @table @var | ||
3986 | @item connection_header | ||
3987 | Value of the @code{Connection} request header. | ||
3988 | You can get this request header value by passing | ||
3989 | @code{MHD_HTTP_HEADER_CONNECTION} to | ||
3990 | @code{MHD_lookup_connection_value()}. | ||
3991 | If you pass @code{NULL} then this is handled like a not | ||
3992 | matching @code{Connection} header value. | ||
3993 | @end table | ||
3994 | |||
3995 | Returns 0 when the @code{Connection} header is | ||
3996 | valid for a websocket request and | ||
3997 | a value less than zero when the @code{Connection} header isn't | ||
3998 | valid for a websocket request. | ||
3999 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4000 | @end deftypefun | ||
4001 | |||
4002 | |||
4003 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_upgrade_header (const char* upgrade_header) | ||
4004 | @cindex websocket | ||
4005 | Checks the value of the @code{Upgrade} HTTP request header. | ||
4006 | Websocket requests require the value @code{websocket} in | ||
4007 | the @code{Upgrade} HTTP request header. | ||
4008 | |||
4009 | @table @var | ||
4010 | @item upgrade_header | ||
4011 | Value of the @code{Upgrade} request header. | ||
4012 | You can get this request header value by passing | ||
4013 | @code{MHD_HTTP_HEADER_UPGRADE} to | ||
4014 | @code{MHD_lookup_connection_value()}. | ||
4015 | If you pass @code{NULL} then this is handled like a not | ||
4016 | matching @code{Upgrade} header value. | ||
4017 | @end table | ||
4018 | |||
4019 | Returns 0 when the @code{Upgrade} header is | ||
4020 | valid for a websocket request and | ||
4021 | a value less than zero when the @code{Upgrade} header isn't | ||
4022 | valid for a websocket request. | ||
4023 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4024 | @end deftypefun | ||
4025 | |||
4026 | |||
4027 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_check_version_header (const char* version_header) | ||
4028 | @cindex websocket | ||
4029 | Checks the value of the @code{Sec-WebSocket-Version} HTTP request header. | ||
4030 | Websocket requests require the value @code{13} in | ||
4031 | the @code{Sec-WebSocket-Version} HTTP request header. | ||
4032 | |||
4033 | @table @var | ||
4034 | @item version_header | ||
4035 | Value of the @code{Sec-WebSocket-Version} request header. | ||
4036 | You can get this request header value by passing | ||
4037 | @code{MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION} to | ||
4038 | @code{MHD_lookup_connection_value()}. | ||
4039 | If you pass @code{NULL} then this is handled like a not | ||
4040 | matching @code{Sec-WebSocket-Version} header value. | ||
4041 | @end table | ||
4042 | |||
4043 | Returns 0 when the @code{Sec-WebSocket-Version} header is | ||
4044 | valid for a websocket request and | ||
4045 | a value less than zero when the @code{Sec-WebSocket-Version} header isn't | ||
4046 | valid for a websocket request. | ||
4047 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4048 | @end deftypefun | ||
4049 | |||
4050 | |||
4051 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_create_accept_header (const char* sec_websocket_key, char* sec_websocket_accept) | ||
4052 | @cindex websocket | ||
4053 | Checks the value of the @code{Sec-WebSocket-Key} | ||
4054 | HTTP request header and generates the value for | ||
4055 | the @code{Sec-WebSocket-Accept} HTTP response header. | ||
4056 | The generated value must be sent to the client. | ||
4057 | |||
4058 | @table @var | ||
4059 | @item sec_websocket_key | ||
4060 | Value of the @code{Sec-WebSocket-Key} request header. | ||
4061 | You can get this request header value by passing | ||
4062 | @code{MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY} to | ||
4063 | @code{MHD_lookup_connection_value()}. | ||
4064 | If you pass @code{NULL} then this is handled like a not | ||
4065 | matching @code{Sec-WebSocket-Key} header value. | ||
4066 | |||
4067 | @item sec_websocket_accept | ||
4068 | Response buffer, which will receive | ||
4069 | the generated value for the @code{Sec-WebSocket-Accept} | ||
4070 | HTTP response header. | ||
4071 | This buffer must be at least 29 bytes long and | ||
4072 | will contain the response value plus a terminating @code{NUL} | ||
4073 | character on success. | ||
4074 | Must not be @code{NULL}. | ||
4075 | You can add this HTTP header to your response by passing | ||
4076 | @code{MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT} to | ||
4077 | @code{MHD_add_response_header()}. | ||
4078 | @end table | ||
4079 | |||
4080 | Returns 0 when the @code{Sec-WebSocket-Key} header was | ||
4081 | not empty and a result value for the @code{Sec-WebSocket-Accept} | ||
4082 | was calculated. | ||
4083 | A value less than zero is returned when the @code{Sec-WebSocket-Key} | ||
4084 | header isn't valid for a websocket request or when any | ||
4085 | error occurred. | ||
4086 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4087 | @end deftypefun | ||
4088 | |||
4089 | |||
4090 | @c ------------------------------------------------------------ | ||
4091 | @node microhttpd-websocket stream | ||
4092 | @section Websocket stream functions | ||
4093 | |||
4094 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_init (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size) | ||
4095 | @cindex websocket | ||
4096 | Creates a new websocket stream, used for decoding/encoding. | ||
4097 | |||
4098 | @table @var | ||
4099 | @item ws | ||
4100 | pointer a variable to fill with the newly created | ||
4101 | @code{struct MHD_WebSocketStream}, | ||
4102 | receives @code{NULL} on error. May not be @code{NULL}. | ||
4103 | |||
4104 | If not required anymore, free the created websocket stream with | ||
4105 | @code{MHD_websocket_stream_free()}. | ||
4106 | |||
4107 | @item flags | ||
4108 | combination of @code{enum MHD_WEBSOCKET_FLAG} values to | ||
4109 | modify the behavior of the websocket stream. | ||
4110 | |||
4111 | @item max_payload_size | ||
4112 | maximum size for incoming payload data in bytes. Use 0 to allow each size. | ||
4113 | @end table | ||
4114 | |||
4115 | Returns 0 on success, negative values on error. | ||
4116 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4117 | @end deftypefun | ||
4118 | |||
4119 | |||
4120 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_init2 (struct MHD_WebSocketStream **ws, int flags, size_t max_payload_size, MHD_WebSocketMallocCallback callback_malloc, MHD_WebSocketReallocCallback callback_realloc, MHD_WebSocketFreeCallback callback_free, void* cls_rng, MHD_WebSocketRandomNumberGenerator callback_rng) | ||
4121 | @cindex websocket | ||
4122 | Creates a new websocket stream, used for decoding/encoding, | ||
4123 | but with custom memory functions for malloc, realloc and free. | ||
4124 | Also a random number generator can be specified for client mode. | ||
4125 | |||
4126 | @table @var | ||
4127 | @item ws | ||
4128 | pointer a variable to fill with the newly created | ||
4129 | @code{struct MHD_WebSocketStream}, | ||
4130 | receives @code{NULL} on error. Must not be @code{NULL}. | ||
4131 | |||
4132 | If not required anymore, free the created websocket stream with | ||
4133 | @code{MHD_websocket_stream_free}. | ||
4134 | |||
4135 | @item flags | ||
4136 | combination of @code{enum MHD_WEBSOCKET_FLAG} values to | ||
4137 | modify the behavior of the websocket stream. | ||
4138 | |||
4139 | @item max_payload_size | ||
4140 | maximum size for incoming payload data in bytes. Use 0 to allow each size. | ||
4141 | |||
4142 | @item callback_malloc | ||
4143 | callback function for allocating memory. Must not be @code{NULL}. | ||
4144 | The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{malloc} here. | ||
4145 | |||
4146 | @item callback_realloc | ||
4147 | callback function for reallocating memory. Must not be @code{NULL}. | ||
4148 | The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{realloc} here. | ||
4149 | |||
4150 | @item callback_free | ||
4151 | callback function for freeing memory. Must not be @code{NULL}. | ||
4152 | The shorter @code{MHD_websocket_stream_init()} passes a reference to @code{free} here. | ||
4153 | |||
4154 | @item cls_rng | ||
4155 | closure for the random number generator. | ||
4156 | This is only required when | ||
4157 | @code{MHD_WEBSOCKET_FLAG_CLIENT} is passed in @code{flags}. | ||
4158 | The given value is passed to the random number generator callback. | ||
4159 | May be @code{NULL} if not needed. | ||
4160 | Should be @code{NULL} when you are not using @code{MHD_WEBSOCKET_FLAG_CLIENT}. | ||
4161 | The shorter @code{MHD_websocket_stream_init} passes @code{NULL} here. | ||
4162 | |||
4163 | @item callback_rng | ||
4164 | callback function for a secure random number generator. | ||
4165 | This is only required when @code{MHD_WEBSOCKET_FLAG_CLIENT} is | ||
4166 | passed in @code{flags} and must not be @code{NULL} then. | ||
4167 | Should be @code{NULL} otherwise. | ||
4168 | The shorter @code{MHD_websocket_stream_init()} passes @code{NULL} here. | ||
4169 | @end table | ||
4170 | |||
4171 | Returns 0 on success, negative values on error. | ||
4172 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4173 | @end deftypefun | ||
4174 | |||
4175 | |||
4176 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_free (struct MHD_WebSocketStream *ws) | ||
4177 | @cindex websocket | ||
4178 | Frees a previously allocated websocket stream | ||
4179 | |||
4180 | @table @var | ||
4181 | @item ws | ||
4182 | websocket stream to free, this value may be @code{NULL}. | ||
4183 | @end table | ||
4184 | |||
4185 | Returns 0 on success, negative values on error. | ||
4186 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4187 | @end deftypefun | ||
4188 | |||
4189 | |||
4190 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_stream_invalidate (struct MHD_WebSocketStream *ws) | ||
4191 | @cindex websocket | ||
4192 | Invalidates a websocket stream. | ||
4193 | After invalidation a websocket stream cannot be used for decoding anymore. | ||
4194 | Encoding is still possible. | ||
4195 | |||
4196 | @table @var | ||
4197 | @item ws | ||
4198 | websocket stream to invalidate. | ||
4199 | @end table | ||
4200 | |||
4201 | Returns 0 on success, negative values on error. | ||
4202 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4203 | @end deftypefun | ||
4204 | |||
4205 | |||
4206 | @deftypefun {enum MHD_WEBSOCKET_VALIDITY} MHD_websocket_stream_is_valid (struct MHD_WebSocketStream *ws) | ||
4207 | @cindex websocket | ||
4208 | Queries whether a websocket stream is valid. | ||
4209 | Invalidated websocket streams cannot be used for decoding anymore. | ||
4210 | Encoding is still possible. | ||
4211 | |||
4212 | @table @var | ||
4213 | @item ws | ||
4214 | websocket stream to invalidate. | ||
4215 | @end table | ||
4216 | |||
4217 | Returns 0 if invalid, 1 if valid for all types or | ||
4218 | 2 if valid only for control frames. | ||
4219 | Can be compared with @code{enum MHD_WEBSOCKET_VALIDITY}. | ||
4220 | @end deftypefun | ||
4221 | |||
4222 | |||
4223 | @c ------------------------------------------------------------ | ||
4224 | @node microhttpd-websocket decode | ||
4225 | @section Websocket decode functions | ||
4226 | |||
4227 | |||
4228 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_decode (struct MHD_WebSocketStream* ws, const char* streambuf, size_t streambuf_len, size_t* streambuf_read_len, char** payload, size_t* payload_len) | ||
4229 | @cindex websocket | ||
4230 | Decodes a byte sequence for a websocket stream. | ||
4231 | Decoding is done until either a frame is complete or | ||
4232 | the end of the byte sequence is reached. | ||
4233 | |||
4234 | @table @var | ||
4235 | @item ws | ||
4236 | websocket stream for decoding. | ||
4237 | |||
4238 | @item streambuf | ||
4239 | byte sequence for decoding. | ||
4240 | This is what you typically received via @code{recv()}. | ||
4241 | |||
4242 | @item streambuf_len | ||
4243 | length of the byte sequence in parameter @code{streambuf}. | ||
4244 | |||
4245 | @item streambuf_read_len | ||
4246 | pointer to a variable, which receives the number of bytes, | ||
4247 | that has been processed by this call. | ||
4248 | This value may be less than the value of @code{streambuf_len} when | ||
4249 | a frame is decoded before the end of the buffer is reached. | ||
4250 | The remaining bytes of @code{buf} must be passed to | ||
4251 | the next call of this function. | ||
4252 | |||
4253 | @item payload | ||
4254 | pointer to a variable, which receives the allocated buffer with the payload | ||
4255 | data of the decoded frame. Must not be @code{NULL}. | ||
4256 | If no decoded data is available or an error occurred @code{NULL} is returned. | ||
4257 | When the returned value is not @code{NULL} then the buffer contains always | ||
4258 | @code{payload_len} bytes plus one terminating @code{NUL} character | ||
4259 | (regardless of the frame type). | ||
4260 | |||
4261 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4262 | |||
4263 | If you passed the flag @code{MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR} | ||
4264 | upon creation of the websocket stream and a decoding error occurred | ||
4265 | (function return value less than 0), then this buffer contains | ||
4266 | a generated close frame, which must be sent via the socket to the recipient. | ||
4267 | |||
4268 | If you passed the flag @code{MHD_WEBSOCKET_FLAG_WANT_FRAGMENTS} | ||
4269 | upon creation of the websocket stream then | ||
4270 | this payload may only be a part of the complete message. | ||
4271 | Only complete UTF-8 sequences are returned for fragmented text frames. | ||
4272 | If necessary the UTF-8 sequence will be completed with the next text fragment. | ||
4273 | |||
4274 | @item payload_len | ||
4275 | pointer to a variable, which receives length of the result | ||
4276 | @code{payload} buffer in bytes. | ||
4277 | Must not be @code{NULL}. | ||
4278 | This receives 0 when no data is available, when the decoded payload | ||
4279 | has a length of zero or when an error occurred. | ||
4280 | @end table | ||
4281 | |||
4282 | Returns a value greater than zero when a frame is complete. | ||
4283 | Compare with @code{enum MHD_WEBSOCKET_STATUS} to distinguish the frame type. | ||
4284 | Returns 0 when the call succeeded, but no frame is available. | ||
4285 | Returns a value less than zero on errors. | ||
4286 | @end deftypefun | ||
4287 | |||
4288 | |||
4289 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_split_close_reason (const char* payload, size_t payload_len, unsigned short* reason_code, const char** reason_utf8, size_t* reason_utf8_len) | ||
4290 | @cindex websocket | ||
4291 | Splits the payload of a decoded close frame. | ||
4292 | |||
4293 | @table @var | ||
4294 | @item payload | ||
4295 | payload of the close frame. | ||
4296 | This parameter may only be @code{NULL} if @code{payload_len} is 0. | ||
4297 | |||
4298 | @item payload_len | ||
4299 | length of @code{payload}. | ||
4300 | |||
4301 | @item reason_code | ||
4302 | pointer to a variable, which receives the numeric close reason. | ||
4303 | If there was no close reason, this is 0. | ||
4304 | This value can be compared with @code{enum MHD_WEBSOCKET_CLOSEREASON}. | ||
4305 | May be @code{NULL}. | ||
4306 | |||
4307 | @item reason_utf8 | ||
4308 | pointer to a variable, which receives the literal close reason. | ||
4309 | If there was no literal close reason, this will be @code{NULL}. | ||
4310 | May be @code{NULL}. | ||
4311 | |||
4312 | Please note that no memory is allocated in this function. | ||
4313 | If not @code{NULL} the returned value of this parameter | ||
4314 | points to a position in the specified @code{payload}. | ||
4315 | |||
4316 | @item reason_utf8_len | ||
4317 | pointer to a variable, which receives the length of the literal close reason. | ||
4318 | If there was no literal close reason, this is 0. | ||
4319 | May be @code{NULL}. | ||
4320 | @end table | ||
4321 | |||
4322 | Returns 0 on success or a value less than zero on errors. | ||
4323 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4324 | @end deftypefun | ||
4325 | |||
4326 | |||
4327 | @c ------------------------------------------------------------ | ||
4328 | @node microhttpd-websocket encode | ||
4329 | @section Websocket encode functions | ||
4330 | |||
4331 | |||
4332 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_text (struct MHD_WebSocketStream* ws, const char* payload_utf8, size_t payload_utf8_len, int fragmentation, char** frame, size_t* frame_len, int* utf8_step) | ||
4333 | @cindex websocket | ||
4334 | Encodes an UTF-8 encoded text into websocket text frame | ||
4335 | |||
4336 | @table @var | ||
4337 | @item ws | ||
4338 | websocket stream; | ||
4339 | |||
4340 | @item payload_utf8 | ||
4341 | text to send. This must be UTF-8 encoded. | ||
4342 | If you don't want UTF-8 then send a binary frame | ||
4343 | with @code{MHD_websocket_encode_binary()} instead. | ||
4344 | May be be @code{NULL} if @code{payload_utf8_len} is 0, | ||
4345 | must not be @code{NULL} otherwise. | ||
4346 | |||
4347 | @item payload_utf8_len | ||
4348 | length of @code{payload_utf8} in bytes. | ||
4349 | |||
4350 | @item fragmentation | ||
4351 | A value of @code{enum MHD_WEBSOCKET_FRAGMENTATION} | ||
4352 | to specify the fragmentation behavior. | ||
4353 | Specify @code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or just 0 | ||
4354 | if you don't want to use fragmentation (default). | ||
4355 | |||
4356 | @item frame | ||
4357 | pointer to a variable, which receives a buffer with the encoded text frame. | ||
4358 | Must not be @code{NULL}. | ||
4359 | The buffer contains what you typically send via @code{send()} to the recipient. | ||
4360 | If no encoded data is available the variable receives @code{NULL}. | ||
4361 | |||
4362 | If the variable is not @code{NULL} then the buffer contains always | ||
4363 | @code{frame_len} bytes plus one terminating @code{NUL} character. | ||
4364 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4365 | |||
4366 | @item frame_len | ||
4367 | pointer to a variable, which receives the length of the encoded frame in bytes. | ||
4368 | Must not be @code{NULL}. | ||
4369 | |||
4370 | @item utf8_step | ||
4371 | If fragmentation is used (the parameter @code{fragmentation} is not 0) | ||
4372 | then is parameter is required and must not be @code{NULL}. | ||
4373 | If no fragmentation is used, this parameter is optional and | ||
4374 | should be @code{NULL}. | ||
4375 | |||
4376 | This parameter is a pointer to a variable which contains the last check status | ||
4377 | of the UTF-8 sequence. It is required to continue a previous | ||
4378 | UTF-8 sequence check when fragmentation is used, because a UTF-8 sequence | ||
4379 | could be splitted upon fragments. | ||
4380 | |||
4381 | @code{enum MHD_WEBSOCKET_UTF8STEP} is used for this value. | ||
4382 | If you start a new fragment using | ||
4383 | @code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or | ||
4384 | @code{MHD_WEBSOCKET_FRAGMENTATION_FIRST} the old value of this variable | ||
4385 | will be discarded and the value of this variable will be initialized | ||
4386 | to @code{MHD_WEBSOCKET_UTF8STEP_NORMAL}. | ||
4387 | On all other fragmentation modes the previous value of the pointed variable | ||
4388 | will be used to continue the UTF-8 sequence check. | ||
4389 | @end table | ||
4390 | |||
4391 | Returns 0 on success or a value less than zero on errors. | ||
4392 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4393 | @end deftypefun | ||
4394 | |||
4395 | |||
4396 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_binary (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, int fragmentation, char** frame, size_t* frame_len) | ||
4397 | @cindex websocket | ||
4398 | Encodes binary data into websocket binary frame | ||
4399 | |||
4400 | @table @var | ||
4401 | @item ws | ||
4402 | websocket stream; | ||
4403 | |||
4404 | @item payload | ||
4405 | binary data to send. | ||
4406 | May be be @code{NULL} if @code{payload_len} is 0, | ||
4407 | must not be @code{NULL} otherwise. | ||
4408 | |||
4409 | @item payload_len | ||
4410 | length of @code{payload} in bytes. | ||
4411 | |||
4412 | @item fragmentation | ||
4413 | A value of @code{enum MHD_WEBSOCKET_FRAGMENTATION} | ||
4414 | to specify the fragmentation behavior. | ||
4415 | Specify @code{MHD_WEBSOCKET_FRAGMENTATION_NONE} or just 0 | ||
4416 | if you don't want to use fragmentation (default). | ||
4417 | |||
4418 | @item frame | ||
4419 | pointer to a variable, which receives a buffer with the encoded binary frame. | ||
4420 | Must not be @code{NULL}. | ||
4421 | The buffer contains what you typically send via @code{send()} to the recipient. | ||
4422 | If no encoded data is available the variable receives @code{NULL}. | ||
4423 | |||
4424 | If the variable is not @code{NULL} then the buffer contains always | ||
4425 | @code{frame_len} bytes plus one terminating @code{NUL} character. | ||
4426 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4427 | |||
4428 | @item frame_len | ||
4429 | pointer to a variable, which receives the length of the encoded frame in bytes. | ||
4430 | Must not be @code{NULL}. | ||
4431 | @end table | ||
4432 | |||
4433 | Returns 0 on success or a value less than zero on errors. | ||
4434 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4435 | @end deftypefun | ||
4436 | |||
4437 | |||
4438 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_ping (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, char** frame, size_t* frame_len) | ||
4439 | @cindex websocket | ||
4440 | Encodes a websocket ping frame. | ||
4441 | Ping frames are used to check whether a recipient is still available | ||
4442 | and what latency the websocket connection has. | ||
4443 | |||
4444 | @table @var | ||
4445 | @item ws | ||
4446 | websocket stream; | ||
4447 | |||
4448 | @item payload | ||
4449 | binary ping data to send. | ||
4450 | May be @code{NULL} if @code{payload_len} is 0. | ||
4451 | |||
4452 | @item payload_len | ||
4453 | length of @code{payload} in bytes. | ||
4454 | This may not exceed 125 bytes. | ||
4455 | |||
4456 | @item frame | ||
4457 | pointer to a variable, which receives a buffer with the encoded ping frame. | ||
4458 | Must not be @code{NULL}. | ||
4459 | The buffer contains what you typically send via @code{send()} to the recipient. | ||
4460 | If no encoded data is available the variable receives @code{NULL}. | ||
4461 | |||
4462 | If the variable is not @code{NULL} then the buffer contains always | ||
4463 | @code{frame_len} bytes plus one terminating @code{NUL} character. | ||
4464 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4465 | |||
4466 | @item frame_len | ||
4467 | pointer to a variable, which receives the length of the encoded frame in bytes. | ||
4468 | Must not be @code{NULL}. | ||
4469 | @end table | ||
4470 | |||
4471 | Returns 0 on success or a value less than zero on errors. | ||
4472 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4473 | @end deftypefun | ||
4474 | |||
4475 | |||
4476 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_pong (struct MHD_WebSocketStream* ws, const char* payload, size_t payload_len, char** frame, size_t* frame_len) | ||
4477 | @cindex websocket | ||
4478 | Encodes a websocket pong frame. | ||
4479 | Pong frames are used to answer a previously received websocket ping frame. | ||
4480 | |||
4481 | @table @var | ||
4482 | @item ws | ||
4483 | websocket stream; | ||
4484 | |||
4485 | @item payload | ||
4486 | binary pong data to send, which should be | ||
4487 | the decoded payload from the received ping frame. | ||
4488 | May be @code{NULL} if @code{payload_len} is 0. | ||
4489 | |||
4490 | @item payload_len | ||
4491 | length of @code{payload} in bytes. | ||
4492 | This may not exceed 125 bytes. | ||
4493 | |||
4494 | @item frame | ||
4495 | pointer to a variable, which receives a buffer with the encoded pong frame. | ||
4496 | Must not be @code{NULL}. | ||
4497 | The buffer contains what you typically send via @code{send()} to the recipient. | ||
4498 | If no encoded data is available the variable receives @code{NULL}. | ||
4499 | |||
4500 | If the variable is not @code{NULL} then the buffer contains always | ||
4501 | @code{frame_len} bytes plus one terminating @code{NUL} character. | ||
4502 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4503 | |||
4504 | @item frame_len | ||
4505 | pointer to a variable, which receives the length of the encoded frame in bytes. | ||
4506 | Must not be @code{NULL}. | ||
4507 | @end table | ||
4508 | |||
4509 | Returns 0 on success or a value less than zero on errors. | ||
4510 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4511 | @end deftypefun | ||
4512 | |||
4513 | |||
4514 | @deftypefun {enum MHD_WEBSOCKET_STATUS} MHD_websocket_encode_close (struct MHD_WebSocketStream* ws, unsigned short reason_code, const char* reason_utf8, size_t reason_utf8_len, char** frame, size_t* frame_len) | ||
4515 | @cindex websocket | ||
4516 | Encodes a websocket close frame. | ||
4517 | Close frames are used to close a websocket connection in a formal way. | ||
4518 | |||
4519 | @table @var | ||
4520 | @item ws | ||
4521 | websocket stream; | ||
4522 | |||
4523 | @item reason_code | ||
4524 | reason for close. | ||
4525 | You can use @code{enum MHD_WEBSOCKET_CLOSEREASON} for typical reasons, | ||
4526 | but you are not limited to these values. | ||
4527 | The allowed values are specified in RFC 6455 7.4. | ||
4528 | If you don't want to enter a reason, you can specify | ||
4529 | @code{MHD_WEBSOCKET_CLOSEREASON_NO_REASON} (or just 0) then | ||
4530 | no reason is encoded. | ||
4531 | |||
4532 | @item reason_utf8 | ||
4533 | An UTF-8 encoded text reason why the connection is closed. | ||
4534 | This may be @code{NULL} if @code{reason_utf8_len} is 0. | ||
4535 | This must be @code{NULL} if @code{reason_code} equals to zero | ||
4536 | (@code{MHD_WEBSOCKET_CLOSEREASON_NO_REASON}). | ||
4537 | |||
4538 | @item reason_utf8_len | ||
4539 | length of the UTF-8 encoded text reason in bytes. | ||
4540 | This may not exceed 123 bytes. | ||
4541 | |||
4542 | @item frame | ||
4543 | pointer to a variable, which receives a buffer with the encoded close frame. | ||
4544 | Must not be @code{NULL}. | ||
4545 | The buffer contains what you typically send via @code{send()} to the recipient. | ||
4546 | If no encoded data is available the variable receives @code{NULL}. | ||
4547 | |||
4548 | If the variable is not @code{NULL} then the buffer contains always | ||
4549 | @code{frame_len} bytes plus one terminating @code{NUL} character. | ||
4550 | The caller must free this buffer using @code{MHD_websocket_free()}. | ||
4551 | |||
4552 | @item frame_len | ||
4553 | pointer to a variable, which receives the length of the encoded frame in bytes. | ||
4554 | Must not be @code{NULL}. | ||
4555 | @end table | ||
4556 | |||
4557 | Returns 0 on success or a value less than zero on errors. | ||
4558 | Can be compared with @code{enum MHD_WEBSOCKET_STATUS}. | ||
4559 | @end deftypefun | ||
4560 | |||
4561 | |||
4562 | @c ------------------------------------------------------------ | ||
4563 | @node microhttpd-websocket memory | ||
4564 | @section Websocket memory functions | ||
4565 | |||
4566 | |||
4567 | @deftypefun {void*} MHD_websocket_malloc (struct MHD_WebSocketStream* ws, size_t buf_len) | ||
4568 | @cindex websocket | ||
4569 | Allocates memory with the associated @code{malloc()} function | ||
4570 | of the websocket stream. | ||
4571 | The memory allocation function could be different for a websocket stream if | ||
4572 | @code{MHD_websocket_stream_init2()} has been used for initialization. | ||
4573 | |||
4574 | @table @var | ||
4575 | @item ws | ||
4576 | websocket stream; | ||
4577 | |||
4578 | @item buf_len | ||
4579 | size of the buffer to allocate in bytes. | ||
4580 | @end table | ||
4581 | |||
4582 | Returns the pointer of the allocated buffer or @code{NULL} on failure. | ||
4583 | @end deftypefun | ||
4584 | |||
4585 | |||
4586 | @deftypefun {void*} MHD_websocket_realloc (struct MHD_WebSocketStream* ws, void* buf, size_t new_buf_len) | ||
4587 | @cindex websocket | ||
4588 | Reallocates memory with the associated @code{realloc()} function | ||
4589 | of the websocket stream. | ||
4590 | The memory reallocation function could be different for a websocket stream if | ||
4591 | @code{MHD_websocket_stream_init2()} has been used for initialization. | ||
4592 | |||
4593 | @table @var | ||
4594 | @item ws | ||
4595 | websocket stream; | ||
4596 | |||
4597 | @item buf | ||
4598 | current buffer, may be @code{NULL}; | ||
4599 | |||
4600 | @item new_buf_len | ||
4601 | new size of the buffer in bytes. | ||
4602 | @end table | ||
4603 | |||
4604 | Return the pointer of the reallocated buffer or @code{NULL} on failure. | ||
4605 | On failure the old pointer remains valid. | ||
4606 | @end deftypefun | ||
4607 | |||
4608 | |||
4609 | @deftypefun {void} MHD_websocket_free (struct MHD_WebSocketStream* ws, void* buf) | ||
4610 | @cindex websocket | ||
4611 | Frees memory with the associated @code{free()} function | ||
4612 | of the websocket stream. | ||
4613 | The memory free function could be different for a websocket stream if | ||
4614 | @code{MHD_websocket_stream_init2()} has been used for initialization. | ||
4615 | |||
4616 | @table @var | ||
4617 | @item ws | ||
4618 | websocket stream; | ||
4619 | |||
4620 | @item buf | ||
4621 | buffer to free, this may be @code{NULL} then nothing happens. | ||
4622 | @end table | ||
4623 | |||
4624 | @end deftypefun | ||
4625 | |||
4626 | |||
4627 | |||
4628 | |||
4629 | |||
3349 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 4630 | @c ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
3350 | 4631 | ||
3351 | 4632 | ||
diff --git a/src/examples/websocket_chatserver_example.c b/src/examples/websocket_chatserver_example.c index 701684cc..0893279b 100644 --- a/src/examples/websocket_chatserver_example.c +++ b/src/examples/websocket_chatserver_example.c | |||
@@ -43,6 +43,12 @@ | |||
43 | See: https://github.com/coapp-packages/pthreads/issues/2 | 43 | See: https://github.com/coapp-packages/pthreads/issues/2 |
44 | */ | 44 | */ |
45 | #include "pthread_windows.h" | 45 | #include "pthread_windows.h" |
46 | |||
47 | /* | ||
48 | On Windows we will use stricmp instead of strcasecmp (strcasecmp is undefined there). | ||
49 | */ | ||
50 | #define strcasecmp stricmp | ||
51 | |||
46 | #else | 52 | #else |
47 | /* | 53 | /* |
48 | On Unix systems we can use pthread. | 54 | On Unix systems we can use pthread. |
@@ -180,429 +186,429 @@ | |||
180 | " function window_onload(event)\n" \ | 186 | " function window_onload(event)\n" \ |
181 | " {\n" \ | 187 | " {\n" \ |
182 | " // Determine the base url (for http:// this is ws:// for https:// this must be wss://)\n" \ | 188 | " // Determine the base url (for http:// this is ws:// for https:// this must be wss://)\n" \ |
183 | // " baseUrl = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' + window.location.host + '/ChatServerWebSocket';\n" \ | 189 | " baseUrl = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' + window.location.host + '/ChatServerWebSocket';\n" \ |
184 | // " chat_generate();\n" \ | 190 | " chat_generate();\n" \ |
185 | // " chat_connect();\n" \ | 191 | " chat_connect();\n" \ |
186 | // " }\n" \ | 192 | " }\n" \ |
187 | // "\n" \ | 193 | "\n" \ |
188 | // " /**\n" \ | 194 | " /**\n" \ |
189 | // " This function generates the chat using DOM\n" \ | 195 | " This function generates the chat using DOM\n" \ |
190 | // " */\n" \ | 196 | " */\n" \ |
191 | // " function chat_generate()\n" \ | 197 | " function chat_generate()\n" \ |
192 | // " {\n" \ | 198 | " {\n" \ |
193 | // " document.body.innerHTML = '';\n" \ | 199 | " document.body.innerHTML = '';\n" \ |
194 | // " let chat = document.createElement('div');\n" \ | 200 | " let chat = document.createElement('div');\n" \ |
195 | // " document.body.appendChild(chat);\n" \ | 201 | " document.body.appendChild(chat);\n" \ |
196 | // " chat.id = 'Chat';\n" \ | 202 | " chat.id = 'Chat';\n" \ |
197 | // " let messagesAndInput = document.createElement('div');\n" \ | 203 | " let messagesAndInput = document.createElement('div');\n" \ |
198 | // " chat.appendChild(messagesAndInput);\n" \ | 204 | " chat.appendChild(messagesAndInput);\n" \ |
199 | // " messagesAndInput.classList.add('MessagesAndInput');\n" \ | 205 | " messagesAndInput.classList.add('MessagesAndInput');\n" \ |
200 | // " let messages = document.createElement('div');\n" \ | 206 | " let messages = document.createElement('div');\n" \ |
201 | // " messagesAndInput.appendChild(messages);\n" \ | 207 | " messagesAndInput.appendChild(messages);\n" \ |
202 | // " messages.id = 'Messages';\n" \ | 208 | " messages.id = 'Messages';\n" \ |
203 | // " let input = document.createElement('div');\n" \ | 209 | " let input = document.createElement('div');\n" \ |
204 | // " messagesAndInput.appendChild(input);\n" \ | 210 | " messagesAndInput.appendChild(input);\n" \ |
205 | // " input.classList.add('Input');\n" \ | 211 | " input.classList.add('Input');\n" \ |
206 | // " let inputMessage = document.createElement('input');\n" \ | 212 | " let inputMessage = document.createElement('input');\n" \ |
207 | // " input.appendChild(inputMessage);\n" \ | 213 | " input.appendChild(inputMessage);\n" \ |
208 | // " inputMessage.type = 'text';\n" \ | 214 | " inputMessage.type = 'text';\n" \ |
209 | // " inputMessage.id = 'InputMessage';\n" \ | 215 | " inputMessage.id = 'InputMessage';\n" \ |
210 | // " inputMessage.disabled = true;\n" \ | 216 | " inputMessage.disabled = true;\n" \ |
211 | // " inputMessage.addEventListener('keydown', chat_onKeyDown);\n" \ | 217 | " inputMessage.addEventListener('keydown', chat_onKeyDown);\n" \ |
212 | // " let inputMessageSend = document.createElement('button');\n" \ | 218 | " let inputMessageSend = document.createElement('button');\n" \ |
213 | // " input.appendChild(inputMessageSend);\n" \ | 219 | " input.appendChild(inputMessageSend);\n" \ |
214 | // " inputMessageSend.id = 'InputMessageButton';\n" \ | 220 | " inputMessageSend.id = 'InputMessageButton';\n" \ |
215 | // " inputMessageSend.disabled = true;\n" \ | 221 | " inputMessageSend.disabled = true;\n" \ |
216 | // " inputMessageSend.innerText = 'send';\n" \ | 222 | " inputMessageSend.innerText = 'send';\n" \ |
217 | // " inputMessageSend.addEventListener('click', chat_onSendClicked);\n" \ | 223 | " inputMessageSend.addEventListener('click', chat_onSendClicked);\n" \ |
218 | // " let inputImage = document.createElement('input');\n" \ | 224 | " let inputImage = document.createElement('input');\n" \ |
219 | // " input.appendChild(inputImage);\n" \ | 225 | " input.appendChild(inputImage);\n" \ |
220 | // " inputImage.id = 'InputImage';\n" \ | 226 | " inputImage.id = 'InputImage';\n" \ |
221 | // " inputImage.type = 'file';\n" \ | 227 | " inputImage.type = 'file';\n" \ |
222 | // " inputImage.accept = 'image/*';\n" \ | 228 | " inputImage.accept = 'image/*';\n" \ |
223 | // " inputImage.style.display = 'none';\n" \ | 229 | " inputImage.style.display = 'none';\n" \ |
224 | // " inputImage.addEventListener('change', chat_onImageSelected);\n" \ | 230 | " inputImage.addEventListener('change', chat_onImageSelected);\n" \ |
225 | // " let inputImageButton = document.createElement('button');\n" \ | 231 | " let inputImageButton = document.createElement('button');\n" \ |
226 | // " input.appendChild(inputImageButton);\n" \ | 232 | " input.appendChild(inputImageButton);\n" \ |
227 | // " inputImageButton.id = 'InputImageButton';\n" \ | 233 | " inputImageButton.id = 'InputImageButton';\n" \ |
228 | // " inputImageButton.disabled = true;\n" \ | 234 | " inputImageButton.disabled = true;\n" \ |
229 | // " inputImageButton.innerText = 'image';\n" \ | 235 | " inputImageButton.innerText = 'image';\n" \ |
230 | // " inputImageButton.addEventListener('click', chat_onImageClicked);\n" \ | 236 | " inputImageButton.addEventListener('click', chat_onImageClicked);\n" \ |
231 | // " let users = document.createElement('div');\n" \ | 237 | " let users = document.createElement('div');\n" \ |
232 | // " chat.appendChild(users);\n" \ | 238 | " chat.appendChild(users);\n" \ |
233 | // " users.id = 'Users';\n" \ | 239 | " users.id = 'Users';\n" \ |
234 | // " users.addEventListener('click', chat_onUserClicked);\n" \ | 240 | " users.addEventListener('click', chat_onUserClicked);\n" \ |
235 | // " let allUsers = document.createElement('div');\n" \ | 241 | " let allUsers = document.createElement('div');\n" \ |
236 | // " users.appendChild(allUsers);\n" \ | 242 | " users.appendChild(allUsers);\n" \ |
237 | // " allUsers.classList.add('selected');\n" \ | 243 | " allUsers.classList.add('selected');\n" \ |
238 | // " allUsers.innerText = '<everyone>';\n" \ | 244 | " allUsers.innerText = '<everyone>';\n" \ |
239 | // " allUsers.setAttribute('data-user', '0');\n" \ | 245 | " allUsers.setAttribute('data-user', '0');\n" \ |
240 | // " }\n" \ | 246 | " }\n" \ |
241 | // "\n" \ | 247 | "\n" \ |
242 | // " /**\n" \ | 248 | " /**\n" \ |
243 | // " This function creates and connects a WebSocket\n" \ | 249 | " This function creates and connects a WebSocket\n" \ |
244 | // " */\n" \ | 250 | " */\n" \ |
245 | // " function chat_connect()\n" \ | 251 | " function chat_connect()\n" \ |
246 | // " {\n" \ | 252 | " {\n" \ |
247 | // " chat_addMessage(`Connecting to libmicrohttpd chat server demo (${baseUrl})...`, { type: 'system' });\n" \ | 253 | " chat_addMessage(`Connecting to libmicrohttpd chat server demo (${baseUrl})...`, { type: 'system' });\n" \ |
248 | // " socket = new WebSocket(baseUrl);\n" \ | 254 | " socket = new WebSocket(baseUrl);\n" \ |
249 | // " socket.binaryType = 'arraybuffer';\n" \ | 255 | " socket.binaryType = 'arraybuffer';\n" \ |
250 | // " socket.onopen = socket_onopen;\n" \ | 256 | " socket.onopen = socket_onopen;\n" \ |
251 | // " socket.onclose = socket_onclose;\n" \ | 257 | " socket.onclose = socket_onclose;\n" \ |
252 | // " socket.onerror = socket_onerror;\n" \ | 258 | " socket.onerror = socket_onerror;\n" \ |
253 | // " socket.onmessage = socket_onmessage;\n" \ | 259 | " socket.onmessage = socket_onmessage;\n" \ |
254 | // " }\n" \ | 260 | " }\n" \ |
255 | // "\n" \ | 261 | "\n" \ |
256 | // " /**\n" \ | 262 | " /**\n" \ |
257 | // " This function adds new text to the chat list\n" \ | 263 | " This function adds new text to the chat list\n" \ |
258 | // " */\n" \ | 264 | " */\n" \ |
259 | // " function chat_addMessage(text, options)\n" \ | 265 | " function chat_addMessage(text, options)\n" \ |
260 | // " {\n" \ | 266 | " {\n" \ |
261 | // " let type = options && options.type || 'regular';\n" \ | 267 | " let type = options && options.type || 'regular';\n" \ |
262 | // " if(!/^(?:regular|system|error|private|moderator)$/.test(type))\n" \ | 268 | " if(!/^(?:regular|system|error|private|moderator)$/.test(type))\n" \ |
263 | // " type = 'regular';\n" \ | 269 | " type = 'regular';\n" \ |
264 | // " let message = document.createElement('div');\n" \ | 270 | " let message = document.createElement('div');\n" \ |
265 | // " message.classList.add('Message');\n" \ | 271 | " message.classList.add('Message');\n" \ |
266 | // " message.classList.add(type);\n" \ | 272 | " message.classList.add(type);\n" \ |
267 | // " if(typeof(text) === 'string')\n" \ | 273 | " if(typeof(text) === 'string')\n" \ |
268 | // " {\n" \ | 274 | " {\n" \ |
269 | // " let content = document.createElement('span');\n" \ | 275 | " let content = document.createElement('span');\n" \ |
270 | // " message.appendChild(content);\n" \ | 276 | " message.appendChild(content);\n" \ |
271 | // " if(options && options.from)\n" \ | 277 | " if(options && options.from)\n" \ |
272 | // " content.innerText = `${options.from}: ${text}`;\n" \ | 278 | " content.innerText = `${options.from}: ${text}`;\n" \ |
273 | // " else\n" \ | 279 | " else\n" \ |
274 | // " content.innerText = text;\n" \ | 280 | " content.innerText = text;\n" \ |
275 | // " if(options && options.reconnect)\n" \ | 281 | " if(options && options.reconnect)\n" \ |
276 | // " {\n" \ | 282 | " {\n" \ |
277 | // " let span = document.createElement('span');\n" \ | 283 | " let span = document.createElement('span');\n" \ |
278 | // " span.appendChild(document.createTextNode(' ('));\n" \ | 284 | " span.appendChild(document.createTextNode(' ('));\n" \ |
279 | // " let reconnect = document.createElement('a');\n" \ | 285 | " let reconnect = document.createElement('a');\n" \ |
280 | // " reconnect.href = 'javascript:chat_connect()';\n" \ | 286 | " reconnect.href = 'javascript:chat_connect()';\n" \ |
281 | // " reconnect.innerText = 'reconnect';\n" \ | 287 | " reconnect.innerText = 'reconnect';\n" \ |
282 | // " span.appendChild(reconnect);\n" \ | 288 | " span.appendChild(reconnect);\n" \ |
283 | // " span.appendChild(document.createTextNode(')'));\n" \ | 289 | " span.appendChild(document.createTextNode(')'));\n" \ |
284 | // " message.appendChild(span);\n" \ | 290 | " message.appendChild(span);\n" \ |
285 | // " }\n" \ | 291 | " }\n" \ |
286 | // " }\n" \ | 292 | " }\n" \ |
287 | // " else\n" \ | 293 | " else\n" \ |
288 | // " {\n" \ | 294 | " {\n" \ |
289 | // " let content = document.createElement('span');\n" \ | 295 | " let content = document.createElement('span');\n" \ |
290 | // " message.appendChild(content);\n" \ | 296 | " message.appendChild(content);\n" \ |
291 | // " if(options && options.from)\n" \ | 297 | " if(options && options.from)\n" \ |
292 | // " {\n" \ | 298 | " {\n" \ |
293 | // " content.innerText = `${options.from}:\\n`;\n" \ | 299 | " content.innerText = `${options.from}:\\n`;\n" \ |
294 | // " }\n" \ | 300 | " }\n" \ |
295 | // " if(options && options.pictureType && text instanceof Uint8Array)\n" \ | 301 | " if(options && options.pictureType && text instanceof Uint8Array)\n" \ |
296 | // " {\n" \ | 302 | " {\n" \ |
297 | // " let img = document.createElement('img');\n" \ | 303 | " let img = document.createElement('img');\n" \ |
298 | // " content.appendChild(img);\n" \ | 304 | " content.appendChild(img);\n" \ |
299 | // " img.src = URL.createObjectURL(new Blob([ text.buffer ], { type: options.pictureType }));\n" \ | 305 | " img.src = URL.createObjectURL(new Blob([ text.buffer ], { type: options.pictureType }));\n" \ |
300 | // " }\n" \ | 306 | " }\n" \ |
301 | // " }\n" \ | 307 | " }\n" \ |
302 | // " document.getElementById('Messages').appendChild(message);\n" \ | 308 | " document.getElementById('Messages').appendChild(message);\n" \ |
303 | // " message.scrollIntoView();\n" \ | 309 | " message.scrollIntoView();\n" \ |
304 | // " }\n" \ | 310 | " }\n" \ |
305 | // "\n" \ | 311 | "\n" \ |
306 | // " /**\n" \ | 312 | " /**\n" \ |
307 | // " This is a keydown event handler, which allows that you can just press enter instead of clicking the 'send' button\n" \ | 313 | " This is a keydown event handler, which allows that you can just press enter instead of clicking the 'send' button\n" \ |
308 | // " */\n" \ | 314 | " */\n" \ |
309 | // " function chat_onKeyDown(event)\n" \ | 315 | " function chat_onKeyDown(event)\n" \ |
310 | // " {\n" \ | 316 | " {\n" \ |
311 | // " if(event.key == 'Enter')\n" \ | 317 | " if(event.key == 'Enter')\n" \ |
312 | // " chat_onSendClicked();\n" \ | 318 | " chat_onSendClicked();\n" \ |
313 | // " }\n" \ | 319 | " }\n" \ |
314 | // "\n" \ | 320 | "\n" \ |
315 | // " /**\n" \ | 321 | " /**\n" \ |
316 | // " This is the code to send a message or command, when clicking the 'send' button\n" \ | 322 | " This is the code to send a message or command, when clicking the 'send' button\n" \ |
317 | // " */\n" \ | 323 | " */\n" \ |
318 | // " function chat_onSendClicked(event)\n" \ | 324 | " function chat_onSendClicked(event)\n" \ |
319 | // " {\n" \ | 325 | " {\n" \ |
320 | // " let message = document.getElementById('InputMessage').value;\n" \ | 326 | " let message = document.getElementById('InputMessage').value;\n" \ |
321 | // " if(message.length == 0)\n" \ | 327 | " if(message.length == 0)\n" \ |
322 | // " return;\n" \ | 328 | " return;\n" \ |
323 | // " if(message.substr(0, 1) == '/')\n" \ | 329 | " if(message.substr(0, 1) == '/')\n" \ |
324 | // " {\n" \ | 330 | " {\n" \ |
325 | // " // command\n" \ | 331 | " // command\n" \ |
326 | // " let match;\n" \ | 332 | " let match;\n" \ |
327 | // " if(/^\\/disconnect\\s*$/.test(message))\n" \ | 333 | " if(/^\\/disconnect\\s*$/.test(message))\n" \ |
328 | // " {\n" \ | 334 | " {\n" \ |
329 | // " socket.close(1000);\n" \ | 335 | " socket.close(1000);\n" \ |
330 | // " }\n" \ | 336 | " }\n" \ |
331 | // " else if((match = /^\\/m\\s+(\\S+)\\s+/.exec(message)))\n" \ | 337 | " else if((match = /^\\/m\\s+(\\S+)\\s+/.exec(message)))\n" \ |
332 | // " {\n" \ | 338 | " {\n" \ |
333 | // " message = message.substr(match[0].length);\n" \ | 339 | " message = message.substr(match[0].length);\n" \ |
334 | // " let userId = chat_getUserIdByName(match[1]);\n" \ | 340 | " let userId = chat_getUserIdByName(match[1]);\n" \ |
335 | // " if(userId !== null)\n" \ | 341 | " if(userId !== null)\n" \ |
336 | // " {\n" \ | 342 | " {\n" \ |
337 | // " socket.send(`private|${userId}|${message}`);\n" \ | 343 | " socket.send(`private|${userId}|${message}`);\n" \ |
338 | // " }\n" \ | 344 | " }\n" \ |
339 | // " else\n" \ | 345 | " else\n" \ |
340 | // " {\n" \ | 346 | " {\n" \ |
341 | // " chat_addMessage(`Unknown user \"${match[1]}\" for private message: ${message}`, { type: 'error' });\n" \ | 347 | " chat_addMessage(`Unknown user \"${match[1]}\" for private message: ${message}`, { type: 'error' });\n" \ |
342 | // " }\n" \ | 348 | " }\n" \ |
343 | // " }\n" \ | 349 | " }\n" \ |
344 | // " else if((match = /^\\/ping\\s+(\\S+)\\s*$/.exec(message)))\n" \ | 350 | " else if((match = /^\\/ping\\s+(\\S+)\\s*$/.exec(message)))\n" \ |
345 | // " {\n" \ | 351 | " {\n" \ |
346 | // " let userId = chat_getUserIdByName(match[1]);\n" \ | 352 | " let userId = chat_getUserIdByName(match[1]);\n" \ |
347 | // " if(userId !== null)\n" \ | 353 | " if(userId !== null)\n" \ |
348 | // " {\n" \ | 354 | " {\n" \ |
349 | // " socket.send(`ping|${userId}|`);\n" \ | 355 | " socket.send(`ping|${userId}|`);\n" \ |
350 | // " }\n" \ | 356 | " }\n" \ |
351 | // " else\n" \ | 357 | " else\n" \ |
352 | // " {\n" \ | 358 | " {\n" \ |
353 | // " chat_addMessage(`Unknown user \"${match[1]}\" for ping`, { type: 'error' });\n" \ | 359 | " chat_addMessage(`Unknown user \"${match[1]}\" for ping`, { type: 'error' });\n" \ |
354 | // " }\n" \ | 360 | " }\n" \ |
355 | // " }\n" \ | 361 | " }\n" \ |
356 | // " else if((match = /^\\/name\\s+(\\S+)\\s*$/.exec(message)))\n" \ | 362 | " else if((match = /^\\/name\\s+(\\S+)\\s*$/.exec(message)))\n" \ |
357 | // " {\n" \ | 363 | " {\n" \ |
358 | // " socket.send(`name||${match[1]}`);\n" \ | 364 | " socket.send(`name||${match[1]}`);\n" \ |
359 | // " }\n" \ | 365 | " }\n" \ |
360 | // " else\n" \ | 366 | " else\n" \ |
361 | // " {\n" \ | 367 | " {\n" \ |
362 | // " chat_addMessage(`Unsupported command or invalid syntax: ${message}`, { type: 'error' });\n" \ | 368 | " chat_addMessage(`Unsupported command or invalid syntax: ${message}`, { type: 'error' });\n" \ |
363 | // " }\n" \ | 369 | " }\n" \ |
364 | // " }\n" \ | 370 | " }\n" \ |
365 | // " else\n" \ | 371 | " else\n" \ |
366 | // " {\n" \ | 372 | " {\n" \ |
367 | // " // regular chat message to the selected user\n" \ | 373 | " // regular chat message to the selected user\n" \ |
368 | // " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ | 374 | " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ |
369 | // " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ | 375 | " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ |
370 | // " if(selectedUserId == 0)\n" \ | 376 | " if(selectedUserId == 0)\n" \ |
371 | // " socket.send(`||${message}`);\n" \ | 377 | " socket.send(`||${message}`);\n" \ |
372 | // " else\n" \ | 378 | " else\n" \ |
373 | // " socket.send(`private|${selectedUserId}|${message}`);\n" \ | 379 | " socket.send(`private|${selectedUserId}|${message}`);\n" \ |
374 | // " }\n" \ | 380 | " }\n" \ |
375 | // " document.getElementById('InputMessage').value = '';\n" \ | 381 | " document.getElementById('InputMessage').value = '';\n" \ |
376 | // " }\n" \ | 382 | " }\n" \ |
377 | // "\n" \ | 383 | "\n" \ |
378 | // " /**\n" \ | 384 | " /**\n" \ |
379 | // " This is the event when the user hits the 'image' button\n" \ | 385 | " This is the event when the user hits the 'image' button\n" \ |
380 | // " */\n" \ | 386 | " */\n" \ |
381 | // " function chat_onImageClicked(event)\n" \ | 387 | " function chat_onImageClicked(event)\n" \ |
382 | // " {\n" \ | 388 | " {\n" \ |
383 | // " document.getElementById('InputImage').click();\n" \ | 389 | " document.getElementById('InputImage').click();\n" \ |
384 | // " }\n" \ | 390 | " }\n" \ |
385 | // "\n" \ | 391 | "\n" \ |
386 | // " /**\n" \ | 392 | " /**\n" \ |
387 | // " This is the event when the user selected an image.\n" \ | 393 | " This is the event when the user selected an image.\n" \ |
388 | // " The image will be read with the FileReader (allowed in web, because the user selected the file).\n" \ | 394 | " The image will be read with the FileReader (allowed in web, because the user selected the file).\n" \ |
389 | // " */\n" \ | 395 | " */\n" \ |
390 | // " function chat_onImageSelected(event)\n" \ | 396 | " function chat_onImageSelected(event)\n" \ |
391 | // " {\n" \ | 397 | " {\n" \ |
392 | // " let file = event.target.files[0];\n" \ | 398 | " let file = event.target.files[0];\n" \ |
393 | // " if(!file || !/^image\\//.test(file.type))\n" \ | 399 | " if(!file || !/^image\\//.test(file.type))\n" \ |
394 | // " return;\n" \ | 400 | " return;\n" \ |
395 | // " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ | 401 | " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ |
396 | // " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ | 402 | " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ |
397 | // " let reader = new FileReader();\n" \ | 403 | " let reader = new FileReader();\n" \ |
398 | // " reader.onload = function(event) {\n" \ | 404 | " reader.onload = function(event) {\n" \ |
399 | // " chat_onImageRead(event, file.type, selectedUserId);\n" \ | 405 | " chat_onImageRead(event, file.type, selectedUserId);\n" \ |
400 | // " };\n" \ | 406 | " };\n" \ |
401 | // " reader.readAsArrayBuffer(file);\n" \ | 407 | " reader.readAsArrayBuffer(file);\n" \ |
402 | // " }\n" \ | 408 | " }\n" \ |
403 | // "\n" \ | 409 | "\n" \ |
404 | // " /**\n" \ | 410 | " /**\n" \ |
405 | // " This is the event when the user selected image has been read.\n" \ | 411 | " This is the event when the user selected image has been read.\n" \ |
406 | // " This will add our chat protocol prefix and send it via the websocket.\n" \ | 412 | " This will add our chat protocol prefix and send it via the websocket.\n" \ |
407 | // " */\n" \ | 413 | " */\n" \ |
408 | // " function chat_onImageRead(event, fileType, selectedUserId)\n" \ | 414 | " function chat_onImageRead(event, fileType, selectedUserId)\n" \ |
409 | // " {\n" \ | 415 | " {\n" \ |
410 | // " let encoder = new TextEncoder();\n" \ | 416 | " let encoder = new TextEncoder();\n" \ |
411 | // " let prefix = ((selectedUserId == 0 ? '||' : `private|${selectedUserId}|`) + fileType + '|');\n" \ | 417 | " let prefix = ((selectedUserId == 0 ? '||' : `private|${selectedUserId}|`) + fileType + '|');\n" \ |
412 | // " prefix = encoder.encode(prefix);\n" \ | 418 | " prefix = encoder.encode(prefix);\n" \ |
413 | // " let byteData = new Uint8Array(event.target.result);\n" \ | 419 | " let byteData = new Uint8Array(event.target.result);\n" \ |
414 | // " let totalLength = prefix.length + byteData.length;\n" \ | 420 | " let totalLength = prefix.length + byteData.length;\n" \ |
415 | // " let resultByteData = new Uint8Array(totalLength);\n" \ | 421 | " let resultByteData = new Uint8Array(totalLength);\n" \ |
416 | // " resultByteData.set(prefix, 0);\n" \ | 422 | " resultByteData.set(prefix, 0);\n" \ |
417 | // " resultByteData.set(byteData, prefix.length);\n" \ | 423 | " resultByteData.set(byteData, prefix.length);\n" \ |
418 | // " socket.send(resultByteData);\n" \ | 424 | " socket.send(resultByteData);\n" \ |
419 | // " }\n" \ | 425 | " }\n" \ |
420 | // "\n" \ | 426 | "\n" \ |
421 | // " /**\n" \ | 427 | " /**\n" \ |
422 | // " This is the event when the user clicked a name in the user list.\n" \ | 428 | " This is the event when the user clicked a name in the user list.\n" \ |
423 | // " This is useful to send private messages or images without needing to add the /m prefix.\n" \ | 429 | " This is useful to send private messages or images without needing to add the /m prefix.\n" \ |
424 | // " */\n" \ | 430 | " */\n" \ |
425 | // " function chat_onUserClicked(event, selectedUserId)\n" \ | 431 | " function chat_onUserClicked(event, selectedUserId)\n" \ |
426 | // " {\n" \ | 432 | " {\n" \ |
427 | // " let newSelected = event.target.closest('div#Users > div');\n" \ | 433 | " let newSelected = event.target.closest('div#Users > div');\n" \ |
428 | // " if(newSelected === null)\n" \ | 434 | " if(newSelected === null)\n" \ |
429 | // " return;\n" \ | 435 | " return;\n" \ |
430 | // " for(let div of this.querySelectorAll(':scope > div.selected'))\n" \ | 436 | " for(let div of this.querySelectorAll(':scope > div.selected'))\n" \ |
431 | // " div.classList.remove('selected');\n" \ | 437 | " div.classList.remove('selected');\n" \ |
432 | // " newSelected.classList.add('selected');\n" \ | 438 | " newSelected.classList.add('selected');\n" \ |
433 | // " }\n" \ | 439 | " }\n" \ |
434 | // "\n" \ | 440 | "\n" \ |
435 | // " /**\n" \ | 441 | " /**\n" \ |
436 | // " This functions returns the current id of a user identified by its name.\n" \ | 442 | " This functions returns the current id of a user identified by its name.\n" \ |
437 | // " */\n" \ | 443 | " */\n" \ |
438 | // " function chat_getUserIdByName(name)\n" \ | 444 | " function chat_getUserIdByName(name)\n" \ |
439 | // " {\n" \ | 445 | " {\n" \ |
440 | // " let nameUpper = name.toUpperCase();\n" \ | 446 | " let nameUpper = name.toUpperCase();\n" \ |
441 | // " for(let pair of connectedUsers)\n" \ | 447 | " for(let pair of connectedUsers)\n" \ |
442 | // " {\n" \ | 448 | " {\n" \ |
443 | // " if(pair[1].toUpperCase() == nameUpper)\n" \ | 449 | " if(pair[1].toUpperCase() == nameUpper)\n" \ |
444 | // " return pair[0];\n" \ | 450 | " return pair[0];\n" \ |
445 | // " }\n" \ | 451 | " }\n" \ |
446 | // " return null;\n" \ | 452 | " return null;\n" \ |
447 | // " }\n" \ | 453 | " }\n" \ |
448 | // "\n" \ | 454 | "\n" \ |
449 | // " /**\n" \ | 455 | " /**\n" \ |
450 | // " This functions clears the entire user list (needed for reconnecting).\n" \ | 456 | " This functions clears the entire user list (needed for reconnecting).\n" \ |
451 | // " */\n" \ | 457 | " */\n" \ |
452 | // " function chat_clearUserList()\n" \ | 458 | " function chat_clearUserList()\n" \ |
453 | // " {\n" \ | 459 | " {\n" \ |
454 | // " let users = document.getElementById('Users');\n" \ | 460 | " let users = document.getElementById('Users');\n" \ |
455 | // " for(let div of users.querySelectorAll(':scope > div'))\n" \ | 461 | " for(let div of users.querySelectorAll(':scope > div'))\n" \ |
456 | // " {\n" \ | 462 | " {\n" \ |
457 | // " if(div.getAttribute('data-user') === '0')\n" \ | 463 | " if(div.getAttribute('data-user') === '0')\n" \ |
458 | // " {\n" \ | 464 | " {\n" \ |
459 | // " div.classList.add('selected');\n" \ | 465 | " div.classList.add('selected');\n" \ |
460 | // " }\n" \ | 466 | " }\n" \ |
461 | // " else\n" \ | 467 | " else\n" \ |
462 | // " {\n" \ | 468 | " {\n" \ |
463 | // " div.parentNode.removeChild(div);\n" \ | 469 | " div.parentNode.removeChild(div);\n" \ |
464 | // " }\n" \ | 470 | " }\n" \ |
465 | // " }\n" \ | 471 | " }\n" \ |
466 | // " return null;\n" \ | 472 | " return null;\n" \ |
467 | // " }\n" \ | 473 | " }\n" \ |
468 | // "\n" \ | 474 | "\n" \ |
469 | // " /**\n" \ | 475 | " /**\n" \ |
470 | // " This is the event when the socket has established a connection.\n" \ | 476 | " This is the event when the socket has established a connection.\n" \ |
471 | // " This will initialize an empty chat and enable the controls.\n" \ | 477 | " This will initialize an empty chat and enable the controls.\n" \ |
472 | // " */\n" \ | 478 | " */\n" \ |
473 | // " function socket_onopen(event)\n" \ | 479 | " function socket_onopen(event)\n" \ |
474 | // " {\n" \ | 480 | " {\n" \ |
475 | // " connectedUsers.clear();\n" \ | 481 | " connectedUsers.clear();\n" \ |
476 | // " chat_clearUserList();\n" \ | 482 | " chat_clearUserList();\n" \ |
477 | // " chat_addMessage('Connected!', { type: 'system' });\n" \ | 483 | " chat_addMessage('Connected!', { type: 'system' });\n" \ |
478 | // " document.getElementById('InputMessage').disabled = false;\n" \ | 484 | " document.getElementById('InputMessage').disabled = false;\n" \ |
479 | // " document.getElementById('InputMessageButton').disabled = false;\n" \ | 485 | " document.getElementById('InputMessageButton').disabled = false;\n" \ |
480 | // " document.getElementById('InputImageButton').disabled = false;\n" \ | 486 | " document.getElementById('InputImageButton').disabled = false;\n" \ |
481 | // " }\n" \ | 487 | " }\n" \ |
482 | // "\n" \ | 488 | "\n" \ |
483 | // " /**\n" \ | 489 | " /**\n" \ |
484 | // " This is the event when the socket has been closed.\n" \ | 490 | " This is the event when the socket has been closed.\n" \ |
485 | // " This will lock the controls.\n" \ | 491 | " This will lock the controls.\n" \ |
486 | // " */\n" \ | 492 | " */\n" \ |
487 | // " function socket_onclose(event)\n" \ | 493 | " function socket_onclose(event)\n" \ |
488 | // " {\n" \ | 494 | " {\n" \ |
489 | // " chat_addMessage('Connection closed!', { type: 'system', reconnect: true });\n" \ | 495 | " chat_addMessage('Connection closed!', { type: 'system', reconnect: true });\n" \ |
490 | // " document.getElementById('InputMessage').disabled = true;\n" \ | 496 | " document.getElementById('InputMessage').disabled = true;\n" \ |
491 | // " document.getElementById('InputMessageButton').disabled = true;\n" \ | 497 | " document.getElementById('InputMessageButton').disabled = true;\n" \ |
492 | // " document.getElementById('InputImageButton').disabled = true;\n" \ | 498 | " document.getElementById('InputImageButton').disabled = true;\n" \ |
493 | // " }\n" \ | 499 | " }\n" \ |
494 | // "\n" \ | 500 | "\n" \ |
495 | // " /**\n" \ | 501 | " /**\n" \ |
496 | // " This is the event when the socket reported an error.\n" \ | 502 | " This is the event when the socket reported an error.\n" \ |
497 | // " This will just make an output.\n" \ | 503 | " This will just make an output.\n" \ |
498 | // " In the web browser console (F12 on many browsers) will show you more detailed error information.\n" \ | 504 | " In the web browser console (F12 on many browsers) will show you more detailed error information.\n" \ |
499 | // " */\n" \ | 505 | " */\n" \ |
500 | // " function socket_onerror(event)\n" \ | 506 | " function socket_onerror(event)\n" \ |
501 | // " {\n" \ | 507 | " {\n" \ |
502 | // " console.error('WebSocket error reported: ', event);\n" \ | 508 | " console.error('WebSocket error reported: ', event);\n" \ |
503 | // " chat_addMessage('The socket reported an error!', { type: 'error' });\n" \ | 509 | " chat_addMessage('The socket reported an error!', { type: 'error' });\n" \ |
504 | // " }\n" \ | 510 | " }\n" \ |
505 | // "\n" \ | 511 | "\n" \ |
506 | // " /**\n" \ | 512 | " /**\n" \ |
507 | // " This is the event when the socket has received a message.\n" \ | 513 | " This is the event when the socket has received a message.\n" \ |
508 | // " This will parse the message and execute the corresponding command (or add the message).\n" \ | 514 | " This will parse the message and execute the corresponding command (or add the message).\n" \ |
509 | // " */\n" \ | 515 | " */\n" \ |
510 | // " function socket_onmessage(event)\n" \ | 516 | " function socket_onmessage(event)\n" \ |
511 | // " {\n" \ | 517 | " {\n" \ |
512 | // " if(typeof(event.data) === 'string')\n" \ | 518 | " if(typeof(event.data) === 'string')\n" \ |
513 | // " {\n" \ | 519 | " {\n" \ |
514 | // " // text message or command\n" \ | 520 | " // text message or command\n" \ |
515 | // " let message = event.data.split('|', 3);\n" \ | 521 | " let message = event.data.split('|', 3);\n" \ |
516 | // " switch(message[0])\n" \ | 522 | " switch(message[0])\n" \ |
517 | // " {\n" \ | 523 | " {\n" \ |
518 | // " case 'userinit':\n" \ | 524 | " case 'userinit':\n" \ |
519 | // " connectedUsers.set(message[1], message[2]);\n" \ | 525 | " connectedUsers.set(message[1], message[2]);\n" \ |
520 | // " {\n" \ | 526 | " {\n" \ |
521 | // " let users = document.getElementById('Users');\n" \ | 527 | " let users = document.getElementById('Users');\n" \ |
522 | // " let div = document.createElement('div');\n" \ | 528 | " let div = document.createElement('div');\n" \ |
523 | // " users.appendChild(div);\n" \ | 529 | " users.appendChild(div);\n" \ |
524 | // " div.innerText = message[2];\n" \ | 530 | " div.innerText = message[2];\n" \ |
525 | // " div.setAttribute('data-user', message[1]);\n" \ | 531 | " div.setAttribute('data-user', message[1]);\n" \ |
526 | // " }\n" \ | 532 | " }\n" \ |
527 | // " break;\n" \ | 533 | " break;\n" \ |
528 | // " case 'useradd':\n" \ | 534 | " case 'useradd':\n" \ |
529 | // " connectedUsers.set(message[1], message[2]);\n" \ | 535 | " connectedUsers.set(message[1], message[2]);\n" \ |
530 | // " chat_addMessage(`The user '${message[2]}' has joined our lovely chatroom.`, { type: 'moderator' });\n" \ | 536 | " chat_addMessage(`The user '${message[2]}' has joined our lovely chatroom.`, { type: 'moderator' });\n" \ |
531 | // " {\n" \ | 537 | " {\n" \ |
532 | // " let users = document.getElementById('Users');\n" \ | 538 | " let users = document.getElementById('Users');\n" \ |
533 | // " let div = document.createElement('div');\n" \ | 539 | " let div = document.createElement('div');\n" \ |
534 | // " users.appendChild(div);\n" \ | 540 | " users.appendChild(div);\n" \ |
535 | // " div.innerText = message[2];\n" \ | 541 | " div.innerText = message[2];\n" \ |
536 | // " div.setAttribute('data-user', message[1]);\n" \ | 542 | " div.setAttribute('data-user', message[1]);\n" \ |
537 | // " }\n" \ | 543 | " }\n" \ |
538 | // " break;\n" \ | 544 | " break;\n" \ |
539 | // " case 'userdel':\n" \ | 545 | " case 'userdel':\n" \ |
540 | // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has left our chatroom. We will miss you.`, { type: 'moderator' });\n" \ | 546 | " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has left our chatroom. We will miss you.`, { type: 'moderator' });\n" \ |
541 | // " connectedUsers.delete(message[1]);\n" \ | 547 | " connectedUsers.delete(message[1]);\n" \ |
542 | // " {\n" \ | 548 | " {\n" \ |
543 | // " let users = document.getElementById('Users');\n" \ | 549 | " let users = document.getElementById('Users');\n" \ |
544 | // " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ | 550 | " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ |
545 | // " if(div !== null)\n" \ | 551 | " if(div !== null)\n" \ |
546 | // " {\n" \ | 552 | " {\n" \ |
547 | // " users.removeChild(div);\n" \ | 553 | " users.removeChild(div);\n" \ |
548 | // " if(div.classList.contains('selected'))\n" \ | 554 | " if(div.classList.contains('selected'))\n" \ |
549 | // " users.querySelector('div[data-user=\\'0\\']').classList.add('selected');\n" \ | 555 | " users.querySelector('div[data-user=\\'0\\']').classList.add('selected');\n" \ |
550 | // " }\n" \ | 556 | " }\n" \ |
551 | // " }\n" \ | 557 | " }\n" \ |
552 | // " break;\n" \ | 558 | " break;\n" \ |
553 | // " case 'username':\n" \ | 559 | " case 'username':\n" \ |
554 | // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has changed his name to '${message[2]}'.`, { type: 'moderator' });\n" \ | 560 | " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has changed his name to '${message[2]}'.`, { type: 'moderator' });\n" \ |
555 | // " connectedUsers.set(message[1], message[2]);\n" \ | 561 | " connectedUsers.set(message[1], message[2]);\n" \ |
556 | // " {\n" \ | 562 | " {\n" \ |
557 | // " let users = document.getElementById('Users');\n" \ | 563 | " let users = document.getElementById('Users');\n" \ |
558 | // " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ | 564 | " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ |
559 | // " if(div !== null)\n" \ | 565 | " if(div !== null)\n" \ |
560 | // " {\n" \ | 566 | " {\n" \ |
561 | // " div.innerText = message[2];\n" \ | 567 | " div.innerText = message[2];\n" \ |
562 | // " }\n" \ | 568 | " }\n" \ |
563 | // " }\n" \ | 569 | " }\n" \ |
564 | // " break;\n" \ | 570 | " break;\n" \ |
565 | // " case 'ping':\n" \ | 571 | " case 'ping':\n" \ |
566 | // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has a ping of ${message[2]} ms.`, { type: 'moderator' });\n" \ | 572 | " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has a ping of ${message[2]} ms.`, { type: 'moderator' });\n" \ |
567 | // " break;\n" \ | 573 | " break;\n" \ |
568 | // " default:\n" \ | 574 | " default:\n" \ |
569 | // " chat_addMessage(message[2], { type: message[0], from: connectedUsers.get(message[1]) });\n" \ | 575 | " chat_addMessage(message[2], { type: message[0], from: connectedUsers.get(message[1]) });\n" \ |
570 | // " break;\n" \ | 576 | " break;\n" \ |
571 | // " }\n" \ | 577 | " }\n" \ |
572 | // " }\n" \ | 578 | " }\n" \ |
573 | // " else\n" \ | 579 | " else\n" \ |
574 | // " {\n" \ | 580 | " {\n" \ |
575 | // " // We received a binary frame, which means a picture here\n" \ | 581 | " // We received a binary frame, which means a picture here\n" \ |
576 | // " let byteData = new Uint8Array(event.data);\n" \ | 582 | " let byteData = new Uint8Array(event.data);\n" \ |
577 | // " let decoder = new TextDecoder();\n" \ | 583 | " let decoder = new TextDecoder();\n" \ |
578 | // " let message = [ ];\n" \ | 584 | " let message = [ ];\n" \ |
579 | // " // message type\n" \ | 585 | " // message type\n" \ |
580 | // " let j = 0;\n" \ | 586 | " let j = 0;\n" \ |
581 | // " let i = byteData.indexOf(0x7C, j); // | = 0x7C;\n" \ | 587 | " let i = byteData.indexOf(0x7C, j); // | = 0x7C;\n" \ |
582 | // " if(i < 0)\n" \ | 588 | " if(i < 0)\n" \ |
583 | // " return;\n" \ | 589 | " return;\n" \ |
584 | // " message.push(decoder.decode(byteData.slice(0, i)));\n" \ | 590 | " message.push(decoder.decode(byteData.slice(0, i)));\n" \ |
585 | // " // picture from\n" \ | 591 | " // picture from\n" \ |
586 | // " j = i + 1;\n" \ | 592 | " j = i + 1;\n" \ |
587 | // " i = byteData.indexOf(0x7C, j);\n" \ | 593 | " i = byteData.indexOf(0x7C, j);\n" \ |
588 | // " if(i < 0)\n" \ | 594 | " if(i < 0)\n" \ |
589 | // " return;\n" \ | 595 | " return;\n" \ |
590 | // " message.push(decoder.decode(byteData.slice(j, i)));\n" \ | 596 | " message.push(decoder.decode(byteData.slice(j, i)));\n" \ |
591 | // " // picture encoding\n" \ | 597 | " // picture encoding\n" \ |
592 | // " j = i + 1;\n" \ | 598 | " j = i + 1;\n" \ |
593 | // " i = byteData.indexOf(0x7C, j);\n" \ | 599 | " i = byteData.indexOf(0x7C, j);\n" \ |
594 | // " if(i < 0)\n" \ | 600 | " if(i < 0)\n" \ |
595 | // " return;\n" \ | 601 | " return;\n" \ |
596 | // " message.push(decoder.decode(byteData.slice(j, i)));\n" \ | 602 | " message.push(decoder.decode(byteData.slice(j, i)));\n" \ |
597 | // " // picture\n" \ | 603 | " // picture\n" \ |
598 | // " byteData = byteData.slice(i + 1);\n" \ | 604 | " byteData = byteData.slice(i + 1);\n" \ |
599 | // " chat_addMessage(byteData, { type: message[0], from: connectedUsers.get(message[1]), pictureType: message[2] });\n" \ | 605 | " chat_addMessage(byteData, { type: message[0], from: connectedUsers.get(message[1]), pictureType: message[2] });\n" \ |
600 | // " }\n" \ | 606 | " }\n" \ |
601 | // " }\n" \ | 607 | " }\n" \ |
602 | // "</script>" \ | 608 | "</script>" \ |
603 | // "</head>" \ | 609 | "</head>" \ |
604 | // "<body><noscript>Please enable JavaScript to test the libmicrohttpd Websocket chatserver demo!</noscript></body>" \ | 610 | "<body><noscript>Please enable JavaScript to test the libmicrohttpd Websocket chatserver demo!</noscript></body>" \ |
605 | // "</html>" | 611 | "</html>" |
606 | 612 | ||
607 | #define PAGE_NOT_FOUND \ | 613 | #define PAGE_NOT_FOUND \ |
608 | "404 Not Found" | 614 | "404 Not Found" |
@@ -708,7 +714,7 @@ make_blocking (MHD_socket fd) | |||
708 | #elif defined(MHD_WINSOCK_SOCKETS) | 714 | #elif defined(MHD_WINSOCK_SOCKETS) |
709 | unsigned long flags = 0; | 715 | unsigned long flags = 0; |
710 | 716 | ||
711 | ioctlsocket (fd, (int) FIONBIO, &flags); | 717 | ioctlsocket (fd, FIONBIO, &flags); |
712 | #endif /* MHD_WINSOCK_SOCKETS */ | 718 | #endif /* MHD_WINSOCK_SOCKETS */ |
713 | 719 | ||
714 | } | 720 | } |
@@ -739,7 +745,6 @@ send_all (struct ConnectedUser*cu, | |||
739 | 0); | 745 | 0); |
740 | if (0 > ret) | 746 | if (0 > ret) |
741 | { | 747 | { |
742 | int err = errno; | ||
743 | if (EAGAIN == errno) | 748 | if (EAGAIN == errno) |
744 | { | 749 | { |
745 | ret = 0; | 750 | ret = 0; |
@@ -927,7 +932,7 @@ chat_adduser (struct ConnectedUser*cu) | |||
927 | { | 932 | { |
928 | /* initialize the notification message of the new user */ | 933 | /* initialize the notification message of the new user */ |
929 | char user_index[32]; | 934 | char user_index[32]; |
930 | itoa ((int) cu->user_id, user_index, 10); | 935 | snprintf (user_index, 32, "%d", (int) cu->user_id); |
931 | size_t user_index_len = strlen (user_index); | 936 | size_t user_index_len = strlen (user_index); |
932 | size_t data_len = user_index_len + cu->user_name_len + 9; | 937 | size_t data_len = user_index_len + cu->user_name_len + 9; |
933 | char*data = (char*) malloc (data_len + 1); | 938 | char*data = (char*) malloc (data_len + 1); |
@@ -998,7 +1003,7 @@ chat_removeuser (struct ConnectedUser*cu) | |||
998 | char user_index[32]; | 1003 | char user_index[32]; |
999 | 1004 | ||
1000 | /* initialize the chat message for the removed user */ | 1005 | /* initialize the chat message for the removed user */ |
1001 | itoa ((int) cu->user_id, user_index, 10); | 1006 | snprintf (user_index, 32, "%d", (int) cu->user_id); |
1002 | size_t user_index_len = strlen (user_index); | 1007 | size_t user_index_len = strlen (user_index); |
1003 | size_t data_len = user_index_len + 9; | 1008 | size_t data_len = user_index_len + 9; |
1004 | char*data = (char*) malloc (data_len + 1); | 1009 | char*data = (char*) malloc (data_len + 1); |
@@ -1071,9 +1076,8 @@ chat_renameuser (struct ConnectedUser*cu, | |||
1071 | { | 1076 | { |
1072 | if (cu != users[i]) | 1077 | if (cu != users[i]) |
1073 | { | 1078 | { |
1074 | if ((users[i]->user_name_len == new_name_len) && (0 == stricmp ( | 1079 | if ((users[i]->user_name_len == new_name_len) && |
1075 | users[i]->user_name, | 1080 | (0 == strcasecmp (users[i]->user_name, new_name))) |
1076 | new_name))) | ||
1077 | { | 1081 | { |
1078 | pthread_mutex_unlock (&chat_mutex); | 1082 | pthread_mutex_unlock (&chat_mutex); |
1079 | return 2; | 1083 | return 2; |
@@ -1083,7 +1087,7 @@ chat_renameuser (struct ConnectedUser*cu, | |||
1083 | 1087 | ||
1084 | /* generate the notification message */ | 1088 | /* generate the notification message */ |
1085 | char user_index[32]; | 1089 | char user_index[32]; |
1086 | itoa ((int) cu->user_id, user_index, 10); | 1090 | snprintf (user_index, 32, "%d", (int) cu->user_id); |
1087 | size_t user_index_len = strlen (user_index); | 1091 | size_t user_index_len = strlen (user_index); |
1088 | size_t data_len = user_index_len + new_name_len + 10; | 1092 | size_t data_len = user_index_len + new_name_len + 10; |
1089 | char*data = (char*) malloc (data_len + 1); | 1093 | char*data = (char*) malloc (data_len + 1); |
@@ -1202,17 +1206,17 @@ connecteduser_parse_received_websocket_stream (struct ConnectedUser*cu, | |||
1202 | /* no command means regular message */ | 1206 | /* no command means regular message */ |
1203 | command = 0; | 1207 | command = 0; |
1204 | } | 1208 | } |
1205 | else if (0 == stricmp (frame_data, "private")) | 1209 | else if (0 == strcasecmp (frame_data, "private")) |
1206 | { | 1210 | { |
1207 | /* private means private message */ | 1211 | /* private means private message */ |
1208 | command = 1; | 1212 | command = 1; |
1209 | } | 1213 | } |
1210 | else if (0 == stricmp (frame_data, "name")) | 1214 | else if (0 == strcasecmp (frame_data, "name")) |
1211 | { | 1215 | { |
1212 | /* name means chat user rename */ | 1216 | /* name means chat user rename */ |
1213 | command = 2; | 1217 | command = 2; |
1214 | } | 1218 | } |
1215 | else if (0 == stricmp (frame_data, "ping")) | 1219 | else if (0 == strcasecmp (frame_data, "ping")) |
1216 | { | 1220 | { |
1217 | /* ping means a ping request */ | 1221 | /* ping means a ping request */ |
1218 | command = 3; | 1222 | command = 3; |
@@ -1263,7 +1267,7 @@ connecteduser_parse_received_websocket_stream (struct ConnectedUser*cu, | |||
1263 | * This is useful for debugging with an IDE. | 1267 | * This is useful for debugging with an IDE. |
1264 | */ | 1268 | */ |
1265 | char user_index[32]; | 1269 | char user_index[32]; |
1266 | itoa ((int) from_user_id, user_index, 10); | 1270 | snprintf (user_index, 32, "%d", (int) cu->user_id); |
1267 | size_t user_index_len = strlen (user_index); | 1271 | size_t user_index_len = strlen (user_index); |
1268 | size_t data_len = user_index_len + frame_len - j + 9; | 1272 | size_t data_len = user_index_len + frame_len - j + 9; |
1269 | char*data = (char*) malloc (data_len + 1); | 1273 | char*data = (char*) malloc (data_len + 1); |
@@ -1300,7 +1304,7 @@ connecteduser_parse_received_websocket_stream (struct ConnectedUser*cu, | |||
1300 | * The difference is the prefix "private" | 1304 | * The difference is the prefix "private" |
1301 | */ | 1305 | */ |
1302 | char user_index[32]; | 1306 | char user_index[32]; |
1303 | itoa ((int) from_user_id, user_index, 10); | 1307 | snprintf (user_index, 32, "%d", (int) cu->user_id); |
1304 | size_t user_index_len = strlen (user_index); | 1308 | size_t user_index_len = strlen (user_index); |
1305 | size_t data_len = user_index_len + frame_len - j + 9; | 1309 | size_t data_len = user_index_len + frame_len - j + 9; |
1306 | char*data = (char*) malloc (data_len + 1); | 1310 | char*data = (char*) malloc (data_len + 1); |
@@ -1538,14 +1542,10 @@ connecteduser_parse_received_websocket_stream (struct ConnectedUser*cu, | |||
1538 | char result_text[240]; | 1542 | char result_text[240]; |
1539 | strcpy (result_text, | 1543 | strcpy (result_text, |
1540 | "ping|"); | 1544 | "ping|"); |
1541 | itoa ((int) cu->user_id, | 1545 | snprintf (result_text + 5, 235, "%d", (int) cu->user_id); |
1542 | result_text + 5, | ||
1543 | 10); | ||
1544 | strcat (result_text, | 1546 | strcat (result_text, |
1545 | "|"); | 1547 | "|"); |
1546 | itoa (ping, | 1548 | snprintf (result_text + strlen (result_text), 240 - strlen (result_text), "%d", (int) ping); |
1547 | result_text + strlen (result_text), | ||
1548 | 10); | ||
1549 | chat_addmessage (0, | 1549 | chat_addmessage (0, |
1550 | 0, | 1550 | 0, |
1551 | result_text, | 1551 | result_text, |
@@ -1627,9 +1627,7 @@ connecteduser_send_messages (void*cls) | |||
1627 | ++cu->ping_counter; | 1627 | ++cu->ping_counter; |
1628 | strcpy (cu->ping_message, | 1628 | strcpy (cu->ping_message, |
1629 | "libmicrohttpdchatserverpingdata"); | 1629 | "libmicrohttpdchatserverpingdata"); |
1630 | itoa (cu->ping_counter, | 1630 | snprintf (cu->ping_message + 31, 97, "%d", (int) cu->ping_counter); |
1631 | cu->ping_message + 31, | ||
1632 | 10); | ||
1633 | cu->ping_message_len = strlen (cu->ping_message); | 1631 | cu->ping_message_len = strlen (cu->ping_message); |
1634 | char*frame_data = NULL; | 1632 | char*frame_data = NULL; |
1635 | size_t frame_len = 0; | 1633 | size_t frame_len = 0; |
@@ -1750,7 +1748,7 @@ connecteduser_receive_messages (void *cls) | |||
1750 | { | 1748 | { |
1751 | char user_name[32]; | 1749 | char user_name[32]; |
1752 | strcpy (user_name, "User"); | 1750 | strcpy (user_name, "User"); |
1753 | itoa ((int) cu->user_id, user_name + 4, 10); | 1751 | snprintf (user_name + 4, 28, "%d", (int) cu->user_id); |
1754 | cu->user_name_len = strlen (user_name); | 1752 | cu->user_name_len = strlen (user_name); |
1755 | cu->user_name = malloc (cu->user_name_len + 1); | 1753 | cu->user_name = malloc (cu->user_name_len + 1); |
1756 | if (NULL == cu->user_name) | 1754 | if (NULL == cu->user_name) |
@@ -1831,7 +1829,7 @@ connecteduser_receive_messages (void *cls) | |||
1831 | for (size_t i = 0; i < user_count; ++i) | 1829 | for (size_t i = 0; i < user_count; ++i) |
1832 | { | 1830 | { |
1833 | char user_index[32]; | 1831 | char user_index[32]; |
1834 | itoa ((int) users[i]->user_id, user_index, 10); | 1832 | snprintf (user_index, 32, "%d", (int) users[i]->user_id); |
1835 | size_t user_index_len = strlen (user_index); | 1833 | size_t user_index_len = strlen (user_index); |
1836 | struct UserInit iu; | 1834 | struct UserInit iu; |
1837 | iu.user_init_len = user_index_len + users[i]->user_name_len + 10; | 1835 | iu.user_init_len = user_index_len + users[i]->user_name_len + 10; |
@@ -1891,15 +1889,16 @@ connecteduser_receive_messages (void *cls) | |||
1891 | " /ping <user> - sends a ping to the specified user\n" \ | 1889 | " /ping <user> - sends a ping to the specified user\n" \ |
1892 | " /name <name> - changes your name to the specified name\n" \ | 1890 | " /name <name> - changes your name to the specified name\n" \ |
1893 | " /disconnect - disconnects your websocket\n\n" \ | 1891 | " /disconnect - disconnects your websocket\n\n" \ |
1894 | "All messages, which does not start a slash, are regular messages, which will be sent to selected user.\n\n" \ | 1892 | "All messages, which does not start with a slash, " \ |
1893 | "are regular messages and will be sent to the selected user.\n\n" \ | ||
1895 | "Have fun!"; | 1894 | "Have fun!"; |
1896 | int r = MHD_websocket_encode_text (cu->ws, | 1895 | MHD_websocket_encode_text (cu->ws, |
1897 | welcome_msg, | 1896 | welcome_msg, |
1898 | strlen (welcome_msg), | 1897 | strlen (welcome_msg), |
1899 | MHD_WEBSOCKET_FRAGMENTATION_NONE, | 1898 | MHD_WEBSOCKET_FRAGMENTATION_NONE, |
1900 | &frame_data, | 1899 | &frame_data, |
1901 | &frame_len, | 1900 | &frame_len, |
1902 | NULL); | 1901 | NULL); |
1903 | send_all (cu, | 1902 | send_all (cu, |
1904 | frame_data, | 1903 | frame_data, |
1905 | frame_len); | 1904 | frame_len); |
@@ -2132,7 +2131,7 @@ upgrade_handler (void *cls, | |||
2132 | * @param ptr A pointer for request specific data | 2131 | * @param ptr A pointer for request specific data |
2133 | * @return MHD_YES on success or MHD_NO on error. | 2132 | * @return MHD_YES on success or MHD_NO on error. |
2134 | */ | 2133 | */ |
2135 | static int | 2134 | static enum MHD_Result |
2136 | access_handler (void *cls, | 2135 | access_handler (void *cls, |
2137 | struct MHD_Connection *connection, | 2136 | struct MHD_Connection *connection, |
2138 | const char *url, | 2137 | const char *url, |
@@ -2185,118 +2184,88 @@ access_handler (void *cls, | |||
2185 | * Furthermore it must be a HTTP/1.1 or higher GET request. | 2184 | * Furthermore it must be a HTTP/1.1 or higher GET request. |
2186 | * See: https://tools.ietf.org/html/rfc6455#section-4.2.1 | 2185 | * See: https://tools.ietf.org/html/rfc6455#section-4.2.1 |
2187 | * | 2186 | * |
2188 | * To ease this example we skip the following checks: | 2187 | * To make this example portable we skip the Host check |
2189 | * - Whether the HTTP version is 1.1 or newer | ||
2190 | * - Whether Connection is Upgrade, because this header may | ||
2191 | * contain multiple values. | ||
2192 | * - The requested Host (because we don't know) | ||
2193 | */ | 2188 | */ |
2194 | 2189 | ||
2190 | char is_valid = 1; | ||
2191 | const char* value = NULL; | ||
2192 | char sec_websocket_accept[29]; | ||
2193 | |||
2195 | /* check whether an websocket upgrade is requested */ | 2194 | /* check whether an websocket upgrade is requested */ |
2196 | const char*value = MHD_lookup_connection_value (connection, | 2195 | if (0 != MHD_websocket_check_http_version (version)) |
2197 | MHD_HEADER_KIND, | ||
2198 | MHD_HTTP_HEADER_UPGRADE); | ||
2199 | if ((0 == value) || (0 != stricmp (value, "websocket"))) | ||
2200 | { | 2196 | { |
2201 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( | 2197 | is_valid = 0; |
2202 | PAGE_INVALID_WEBSOCKET_REQUEST), | 2198 | } |
2203 | PAGE_INVALID_WEBSOCKET_REQUEST, | 2199 | value = MHD_lookup_connection_value (connection, |
2204 | MHD_RESPMEM_PERSISTENT); | 2200 | MHD_HEADER_KIND, |
2205 | ret = MHD_queue_response (connection, | 2201 | MHD_HTTP_HEADER_CONNECTION); |
2206 | MHD_HTTP_BAD_REQUEST, | 2202 | if (0 != MHD_websocket_check_connection_header (value)) |
2207 | response); | 2203 | { |
2208 | MHD_destroy_response (response); | 2204 | is_valid = 0; |
2209 | return ret; | 2205 | } |
2206 | value = MHD_lookup_connection_value (connection, | ||
2207 | MHD_HEADER_KIND, | ||
2208 | MHD_HTTP_HEADER_UPGRADE); | ||
2209 | if (0 != MHD_websocket_check_upgrade_header (value)) | ||
2210 | { | ||
2211 | is_valid = 0; | ||
2210 | } | 2212 | } |
2211 | |||
2212 | /* check the protocol version */ | ||
2213 | value = MHD_lookup_connection_value (connection, | 2213 | value = MHD_lookup_connection_value (connection, |
2214 | MHD_HEADER_KIND, | 2214 | MHD_HEADER_KIND, |
2215 | MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); | 2215 | MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); |
2216 | if ((0 == value) || (0 != stricmp (value, "13"))) | 2216 | if (0 != MHD_websocket_check_version_header (value)) |
2217 | { | 2217 | { |
2218 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( | 2218 | is_valid = 0; |
2219 | PAGE_INVALID_WEBSOCKET_REQUEST), | ||
2220 | PAGE_INVALID_WEBSOCKET_REQUEST, | ||
2221 | MHD_RESPMEM_PERSISTENT); | ||
2222 | ret = MHD_queue_response (connection, | ||
2223 | MHD_HTTP_BAD_REQUEST, | ||
2224 | response); | ||
2225 | MHD_destroy_response (response); | ||
2226 | return ret; | ||
2227 | } | 2219 | } |
2228 | 2220 | value = MHD_lookup_connection_value (connection, | |
2229 | /* read the websocket key (required for the response) */ | 2221 | MHD_HEADER_KIND, |
2230 | value = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, | ||
2231 | MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); | 2222 | MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); |
2232 | if (0 == value) | 2223 | if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept)) |
2233 | { | 2224 | { |
2234 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( | 2225 | is_valid = 0; |
2235 | PAGE_INVALID_WEBSOCKET_REQUEST), | ||
2236 | PAGE_INVALID_WEBSOCKET_REQUEST, | ||
2237 | MHD_RESPMEM_PERSISTENT); | ||
2238 | ret = MHD_queue_response (connection, | ||
2239 | MHD_HTTP_BAD_REQUEST, | ||
2240 | response); | ||
2241 | MHD_destroy_response (response); | ||
2242 | return ret; | ||
2243 | } | 2226 | } |
2244 | 2227 | ||
2245 | /* generate the response accept header */ | 2228 | if (1 == is_valid) |
2246 | char sec_websocket_accept[29]; | ||
2247 | if (0 != MHD_websocket_create_accept (value, sec_websocket_accept)) | ||
2248 | { | 2229 | { |
2249 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( | 2230 | /* create the response for upgrade */ |
2250 | PAGE_INVALID_WEBSOCKET_REQUEST), | 2231 | response = MHD_create_response_for_upgrade (&upgrade_handler, |
2251 | PAGE_INVALID_WEBSOCKET_REQUEST, | 2232 | NULL); |
2252 | MHD_RESPMEM_PERSISTENT); | 2233 | |
2234 | /** | ||
2235 | * For the response we need at least the following headers: | ||
2236 | * 1. "Connection: Upgrade" | ||
2237 | * 2. "Upgrade: websocket" | ||
2238 | * 3. "Sec-WebSocket-Accept: <base64value>" | ||
2239 | * The value for Sec-WebSocket-Accept can be generated with MHD_websocket_create_accept_header. | ||
2240 | * It requires the value of the Sec-WebSocket-Key header of the request. | ||
2241 | * See also: https://tools.ietf.org/html/rfc6455#section-4.2.2 | ||
2242 | */ | ||
2243 | MHD_add_response_header (response, | ||
2244 | MHD_HTTP_HEADER_CONNECTION, | ||
2245 | "Upgrade"); | ||
2246 | MHD_add_response_header (response, | ||
2247 | MHD_HTTP_HEADER_UPGRADE, | ||
2248 | "websocket"); | ||
2249 | MHD_add_response_header (response, | ||
2250 | MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, | ||
2251 | sec_websocket_accept); | ||
2253 | ret = MHD_queue_response (connection, | 2252 | ret = MHD_queue_response (connection, |
2254 | MHD_HTTP_BAD_REQUEST, | 2253 | MHD_HTTP_SWITCHING_PROTOCOLS, |
2255 | response); | 2254 | response); |
2256 | MHD_destroy_response (response); | 2255 | MHD_destroy_response (response); |
2257 | return ret; | ||
2258 | } | 2256 | } |
2259 | 2257 | else | |
2260 | /* only for this example: don't accept incoming connection when we are already shutting down */ | ||
2261 | if (0 != disconnect_all) | ||
2262 | { | 2258 | { |
2259 | /* return error page */ | ||
2263 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( | 2260 | struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( |
2264 | PAGE_INVALID_WEBSOCKET_REQUEST), | 2261 | PAGE_INVALID_WEBSOCKET_REQUEST), |
2265 | PAGE_INVALID_WEBSOCKET_REQUEST, | 2262 | PAGE_INVALID_WEBSOCKET_REQUEST, |
2266 | MHD_RESPMEM_PERSISTENT); | 2263 | MHD_RESPMEM_PERSISTENT); |
2267 | ret = MHD_queue_response (connection, | 2264 | ret = MHD_queue_response (connection, |
2268 | MHD_HTTP_SERVICE_UNAVAILABLE, | 2265 | MHD_HTTP_BAD_REQUEST, |
2269 | response); | 2266 | response); |
2270 | MHD_destroy_response (response); | 2267 | MHD_destroy_response (response); |
2271 | return ret; | ||
2272 | } | 2268 | } |
2273 | |||
2274 | /* create the response for upgrade */ | ||
2275 | response = MHD_create_response_for_upgrade (&upgrade_handler, | ||
2276 | NULL); | ||
2277 | |||
2278 | /** | ||
2279 | * For the response we need at least the following headers: | ||
2280 | * 1. "Connection: Upgrade" | ||
2281 | * 2. "Upgrade: websocket" | ||
2282 | * 3. "Sec-WebSocket-Accept: <base64value>" | ||
2283 | * The value for Sec-WebSocket-Accept can be generated with MHD_websocket_create_accept. | ||
2284 | * It requires the value of the Sec-WebSocket-Key header of the request. | ||
2285 | * See also: https://tools.ietf.org/html/rfc6455#section-4.2.2 | ||
2286 | */ | ||
2287 | MHD_add_response_header (response, | ||
2288 | MHD_HTTP_HEADER_CONNECTION, | ||
2289 | "Upgrade"); | ||
2290 | MHD_add_response_header (response, | ||
2291 | MHD_HTTP_HEADER_UPGRADE, | ||
2292 | "websocket"); | ||
2293 | MHD_add_response_header (response, | ||
2294 | MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, | ||
2295 | sec_websocket_accept); | ||
2296 | ret = MHD_queue_response (connection, | ||
2297 | MHD_HTTP_SWITCHING_PROTOCOLS, | ||
2298 | response); | ||
2299 | MHD_destroy_response (response); | ||
2300 | } | 2269 | } |
2301 | else | 2270 | else |
2302 | { | 2271 | { |
@@ -2353,7 +2322,7 @@ main (int argc, | |||
2353 | MHD_OPTION_END); | 2322 | MHD_OPTION_END); |
2354 | #endif | 2323 | #endif |
2355 | 2324 | ||
2356 | if (d == NULL) | 2325 | if (NULL == d) |
2357 | return 1; | 2326 | return 1; |
2358 | (void) getc (stdin); | 2327 | (void) getc (stdin); |
2359 | 2328 | ||
diff --git a/src/include/microhttpd_ws.h b/src/include/microhttpd_ws.h index bfbd550a..f19c140d 100644 --- a/src/include/microhttpd_ws.h +++ b/src/include/microhttpd_ws.h | |||
@@ -51,29 +51,27 @@ struct MHD_WebSocketStream; | |||
51 | enum MHD_WEBSOCKET_FLAG | 51 | enum MHD_WEBSOCKET_FLAG |
52 | { | 52 | { |
53 | /** | 53 | /** |
54 | * The websocket is used by the server (default). | 54 | * The websocket stream is initialized in server mode (default). |
55 | * Thus all outgoing payload will not be "masked". | 55 | * Thus all outgoing payload will not be "masked". |
56 | * All incoming payload must be masked. | 56 | * All incoming payload must be masked. |
57 | * This cannot be used together with #MHD_WEBSOCKET_FLAG_CLIENT | 57 | * This flag cannot be used together with #MHD_WEBSOCKET_FLAG_CLIENT |
58 | */ | 58 | */ |
59 | MHD_WEBSOCKET_FLAG_SERVER = 0, | 59 | MHD_WEBSOCKET_FLAG_SERVER = 0, |
60 | /** | 60 | /** |
61 | * The websocket is used by the client | 61 | * The websocket stream is initialized in client mode. |
62 | * (not used if you provide the server). | 62 | * You will usually never use that mode in combination with libmicrohttpd, |
63 | * Thus all outgoing payload will be "masked" (XOR-ed with random values). |