diff options
Diffstat (limited to 'doc/chapters/websocket.inc')
-rw-r--r-- | doc/chapters/websocket.inc | 886 |
1 files changed, 886 insertions, 0 deletions
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 | ||