1 /*
2 This file is part of libmicrohttpd
3 Copyright (C) 2021 David Gausmann (and other contributing authors)
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19 /**
20 * @file websocket_chatserver_example.c
21 * @brief example for how to use websockets
22 * @author David Gausmann
23 *
24 * Access the HTTP server with your webbrowser.
25 * The webbrowser must support JavaScript and WebSockets.
26 * The websocket access will be initiated via the JavaScript on the website.
27 * You will get an example chat room, which uses websockets.
28 * For testing with multiple users, just start several instances of your webbrowser.
29 *
30 */
31
32 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
33 #define _CRT_SECURE_NO_WARNINGS
34 #endif
35 #include "platform.h"
36 #include <microhttpd.h>
37 #include <microhttpd_ws.h>
38 #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
39 /*
40 Workaround for Windows systems, because the NuGet version of pthreads is buggy.
41 This is a simple replacement. It doesn't offer all functions of pthread, but
42 has everything, what is required for this example.
43 See: https://github.com/coapp-packages/pthreads/issues/2
44 */
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
52 #else
53 /*
54 On Unix systems we can use pthread.
55 */
56 #include <pthread.h>
57 #endif
58
59
60 /*
61 * Specify with this constant whether or not to use HTTPS.
62 * 0 means HTTP, 1 means HTTPS.
63 * Please note that you must enter a valid private key/certificate pair
64 * in the main procedure to running this example with HTTPS.
65 */
66 #define USE_HTTPS 0
67
68 /**
69 * This is the main website.
70 * The HTML, CSS and JavaScript code is all in there.
71 */
72 #define PAGE \
73 "<!DOCTYPE html>" \
74 "<html>" \
75 "<head>" \
76 "<meta charset='UTF-8'>" \
77 "<title>libmicrohttpd websocket chatserver demo</title>" \
78 "<style>" \
79 " html" \
80 " {\n" \
81 " font: 11pt sans-serif;\n" \
82 " }\n" \
83 " html, body" \
84 " {\n" \
85 " margin: 0;\n" \
86 " width: 100vw;\n" \
87 " height: 100vh;\n" \
88 " }\n" \
89 " div#Chat\n" \
90 " {\n" \
91 " display: flex;\n" \
92 " flex-direction: row;\n" \
93 " }\n" \
94 " div#Chat > div.MessagesAndInput\n" \
95 " {\n" \
96 " flex: 1 1 auto;" \
97 " display: flex;\n" \
98 " flex-direction: column;\n" \
99 " width: calc(100vw - 20em);\n" \
100 " }\n" \
101 " div#Chat > div.MessagesAndInput > div#Messages\n" \
102 " {\n" \
103 " flex: 1 1 auto;" \
104 " display: flex;\n" \
105 " flex-direction: column;\n" \
106 " justify-content: flex-start;\n" \
107 " box-sizing: border-box;\n" \
108 " overflow-y: scroll;\n" \
109 " border: 2pt solid #888;\n" \
110 " background-color: #eee;\n" \
111 " height: calc(100vh - 2em);\n" \
112 " }\n" \
113 " div#Chat > div.MessagesAndInput > div#Messages > div.Message > span\n" \
114 " {\n" \
115 " white-space: pre\n" \
116 " }\n" \
117 " div#Chat > div.MessagesAndInput > div#Messages > div.Message.error > span\n" \
118 " {\n" \
119 " color: red;\n" \
120 " }\n" \
121 " div#Chat > div.MessagesAndInput > div#Messages > div.Message.system > span\n" \
122 " {\n" \
123 " color: green;\n" \
124 " }\n" \
125 " div#Chat > div.MessagesAndInput > div#Messages > div.Message.moderator > span\n" \
126 " {\n" \
127 " color: #808000;\n" \
128 " }\n" \
129 " div#Chat > div.MessagesAndInput > div#Messages > div.Message.private > span\n" \
130 " {\n" \
131 " color: blue;\n" \
132 " }\n" \
133 " div#Chat > div.MessagesAndInput > div.Input\n" \
134 " {\n" \
135 " flex: 0 0 auto;" \
136 " height: 2em;" \
137 " display: flex;" \
138 " flex-direction: row;" \
139 " background-color: #eee;\n" \
140 " }\n" \
141 " div#Chat > div.MessagesAndInput > div.Input > input#InputMessage\n" \
142 " {\n" \
143 " flex: 1 1 auto;" \
144 " }\n" \
145 " div#Chat > div.MessagesAndInput > div.Input > button\n" \
146 " {\n" \
147 " flex: 0 0 auto;" \
148 " width: 5em;" \
149 " margin-left: 4pt;" \
150 " }\n" \
151 " div#Chat > div#Users\n" \
152 " {\n" \
153 " flex: 0 0 auto;" \
154 " width: 20em;" \
155 " display: flex;\n" \
156 " flex-direction: column;\n" \
157 " justify-content: flex-start;\n" \
158 " box-sizing: border-box;\n" \
159 " overflow-y: scroll;\n" \
160 " border: 2pt solid #888;\n" \
161 " background-color: #eee;\n" \
162 " }\n" \
163 " div#Chat > div#Users > div\n" \
164 " {\n" \
165 " cursor: pointer;\n" \
166 " user-select: none;\n" \
167 " -webkit-user-select: none;\n" \
168 " }\n" \
169 " div#Chat > div#Users > div.selected\n" \
170 " {\n" \
171 " background-color: #7bf;\n" \
172 " }\n" \
173 "</style>" \
174 "<script>\n" \
175 " 'use strict'\n;" \
176 "\n" \
177 " let baseUrl;\n" \
178 " let socket;\n" \
179 " let connectedUsers = new Map();\n" \
180 "\n" \
181 " window.addEventListener('load', window_onload);\n" \
182 "\n" \
183 " /**\n" \
184 " This is the main procedure which initializes the chat and connects the first socket\n" \
185 " */\n" \
186 " function window_onload(event)\n" \
187 " {\n" \
188 " /* Determine the base url (for http:/" "/ this is ws:/" "/ for https:/" \
189 "/ this must be wss:/" "/) */\n" \
190 " baseUrl = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + ':/" "/' + window.location.host + '/ChatServerWebSocket';\n" \
191 " chat_generate();\n" \
192 " chat_connect();\n" \
193 " }\n" \
194 "\n" \
195 " /**\n" \
196 " This function generates the chat using DOM\n" \
197 " */\n" \
198 " function chat_generate()\n" \
199 " {\n" \
200 " document.body.innerHTML = '';\n" \
201 " let chat = document.createElement('div');\n" \
202 " document.body.appendChild(chat);\n" \
203 " chat.id = 'Chat';\n" \
204 " let messagesAndInput = document.createElement('div');\n" \
205 " chat.appendChild(messagesAndInput);\n" \
206 " messagesAndInput.classList.add('MessagesAndInput');\n" \
207 " let messages = document.createElement('div');\n" \
208 " messagesAndInput.appendChild(messages);\n" \
209 " messages.id = 'Messages';\n" \
210 " let input = document.createElement('div');\n" \
211 " messagesAndInput.appendChild(input);\n" \
212 " input.classList.add('Input');\n" \
213 " let inputMessage = document.createElement('input');\n" \
214 " input.appendChild(inputMessage);\n" \
215 " inputMessage.type = 'text';\n" \
216 " inputMessage.id = 'InputMessage';\n" \
217 " inputMessage.disabled = true;\n" \
218 " inputMessage.addEventListener('keydown', chat_onKeyDown);\n" \
219 " let inputMessageSend = document.createElement('button');\n" \
220 " input.appendChild(inputMessageSend);\n" \
221 " inputMessageSend.id = 'InputMessageButton';\n" \
222 " inputMessageSend.disabled = true;\n" \
223 " inputMessageSend.innerText = 'send';\n" \
224 " inputMessageSend.addEventListener('click', chat_onSendClicked);\n" \
225 " let inputImage = document.createElement('input');\n" \
226 " input.appendChild(inputImage);\n" \
227 " inputImage.id = 'InputImage';\n" \
228 " inputImage.type = 'file';\n" \
229 " inputImage.accept = 'image /*';\n" \
230 " inputImage.style.display = 'none';\n" \
231 " inputImage.addEventListener('change', chat_onImageSelected);\n" \
232 " let inputImageButton = document.createElement('button');\n" \
233 " input.appendChild(inputImageButton);\n" \
234 " inputImageButton.id = 'InputImageButton';\n" \
235 " inputImageButton.disabled = true;\n" \
236 " inputImageButton.innerText = 'image';\n" \
237 " inputImageButton.addEventListener('click', chat_onImageClicked);\n" \
238 " let users = document.createElement('div');\n" \
239 " chat.appendChild(users);\n" \
240 " users.id = 'Users';\n" \
241 " users.addEventListener('click', chat_onUserClicked);\n" \
242 " let allUsers = document.createElement('div');\n" \
243 " users.appendChild(allUsers);\n" \
244 " allUsers.classList.add('selected');\n" \
245 " allUsers.innerText = '<everyone>';\n" \
246 " allUsers.setAttribute('data-user', '0');\n" \
247 " }\n" \
248 "\n" \
249 " /**\n" \
250 " This function creates and connects a WebSocket\n" \
251 " */\n" \
252 " function chat_connect()\n" \
253 " {\n" \
254 " chat_addMessage(`Connecting to libmicrohttpd chat server demo (${baseUrl})...`, { type: 'system' });\n" \
255 " socket = new WebSocket(baseUrl);\n" \
256 " socket.binaryType = 'arraybuffer';\n" \
257 " socket.onopen = socket_onopen;\n" \
258 " socket.onclose = socket_onclose;\n" \
259 " socket.onerror = socket_onerror;\n" \
260 " socket.onmessage = socket_onmessage;\n" \
261 " }\n" \
262 "\n" \
263 " /**\n" \
264 " This function adds new text to the chat list\n" \
265 " */\n" \
266 " function chat_addMessage(text, options)\n" \
267 " {\n" \
268 " let type = options && options.type || 'regular';\n" \
269 " if(!/^(?:regular|system|error|private|moderator)$/.test(type))\n" \
270 " type = 'regular';\n" \
271 " let message = document.createElement('div');\n" \
272 " message.classList.add('Message');\n" \
273 " message.classList.add(type);\n" \
274 " if(typeof(text) === 'string')\n" \
275 " {\n" \
276 " let content = document.createElement('span');\n" \
277 " message.appendChild(content);\n" \
278 " if(options && options.from)\n" \
279 " content.innerText = `${options.from}: ${text}`;\n" \
280 " else\n" \
281 " content.innerText = text;\n" \
282 " if(options && options.reconnect)\n" \
283 " {\n" \
284 " let span = document.createElement('span');\n" \
285 " span.appendChild(document.createTextNode(' ('));\n" \
286 " let reconnect = document.createElement('a');\n" \
287 " reconnect.href = 'javascript:chat_connect()';\n" \
288 " reconnect.innerText = 'reconnect';\n" \
289 " span.appendChild(reconnect);\n" \
290 " span.appendChild(document.createTextNode(')'));\n" \
291 " message.appendChild(span);\n" \
292 " }\n" \
293 " }\n" \
294 " else\n" \
295 " {\n" \
296 " let content = document.createElement('span');\n" \
297 " message.appendChild(content);\n" \
298 " if(options && options.from)\n" \
299 " {\n" \
300 " content.innerText = `${options.from}:\\n`;\n" \
301 " }\n" \
302 " if(options && options.pictureType && text instanceof Uint8Array)\n" \
303 " {\n" \
304 " let img = document.createElement('img');\n" \
305 " content.appendChild(img);\n" \
306 " img.src = URL.createObjectURL(new Blob([ text.buffer ], { type: options.pictureType }));\n" \
307 " }\n" \
308 " }\n" \
309 " document.getElementById('Messages').appendChild(message);\n" \
310 " message.scrollIntoView();\n" \
311 " }\n" \
312 "\n" \
313 " /**\n" \
314 " This is a keydown event handler, which allows that you can just press enter instead of clicking the 'send' button\n" \
315 " */\n" \
316 " function chat_onKeyDown(event)\n" \
317 " {\n" \
318 " if(event.key == 'Enter')\n" \
319 " chat_onSendClicked();\n" \
320 " }\n" \
321 "\n" \
322 " /**\n" \
323 " This is the code to send a message or command, when clicking the 'send' button\n" \
324 " */\n" \
325 " function chat_onSendClicked(event)\n" \
326 " {\n" \
327 " let message = document.getElementById('InputMessage').value;\n" \
328 " if(message.length == 0)\n" \
329 " return;\n" \
330 " if(message.substr(0, 1) == '/')\n" \
331 " {\n" \
332 " /* command */ \n" \
333 " let match;\n" \
334 " if(/^\\/disconnect\\s*$/.test(message))\n" \
335 " {\n" \
336 " socket.close(1000);\n" \
337 " }\n" \
338 " else if((match = /^\\/m\\s+(\\S+)\\s+/.exec(message)))\n" \
339 " {\n" \
340 " message = message.substr(match[0].length);\n" \
341 " let userId = chat_getUserIdByName(match[1]);\n" \
342 " if(userId !== null)\n" \
343 " {\n" \
344 " socket.send(`private|${userId}|${message}`);\n" \
345 " }\n" \
346 " else\n" \
347 " {\n" \
348 " chat_addMessage(`Unknown user \"${match[1]}\" for private message: ${message}`, { type: 'error' });\n" \
349 " }\n" \
350 " }\n" \
351 " else if((match = /^\\/ping\\s+(\\S+)\\s*$/.exec(message)))\n" \
352 " {\n" \
353 " let userId = chat_getUserIdByName(match[1]);\n" \
354 " if(userId !== null)\n" \
355 " {\n" \
356 " socket.send(`ping|${userId}|`);\n" \
357 " }\n" \
358 " else\n" \
359 " {\n" \
360 " chat_addMessage(`Unknown user \"${match[1]}\" for ping`, { type: 'error' });\n" \
361 " }\n" \
362 " }\n" \
363 " else if((match = /^\\/name\\s+(\\S+)\\s*$/.exec(message)))\n" \
364 " {\n" \
365 " socket.send(`name||${match[1]}`);\n" \
366 " }\n" \
367 " else\n" \
368 " {\n" \
369 " chat_addMessage(`Unsupported command or invalid syntax: ${message}`, { type: 'error' });\n" \
370 " }\n" \
371 " }\n" \
372 " else\n" \
373 " {\n" \
374 " /* regular chat message to the selected user */ \n" \
375 " let selectedUser = document.querySelector('div#Users > div.selected');\n" \
376 " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \
377 " if(selectedUserId == 0)\n" \
378 " socket.send(`||${message}`);\n" \
379 " else\n" \
380 " socket.send(`private|${selectedUserId}|${message}`);\n" \
381 " }\n" \
382 " document.getElementById('InputMessage').value = '';\n" \
383 " }\n" \
384 "\n" \
385 " /**\n" \
386 " This is the event when the user hits the 'image' button\n" \
387 " */\n" \
388 " function chat_onImageClicked(event)\n" \
389 " {\n" \
390 " document.getElementById('InputImage').click();\n" \
391 " }\n" \
392 "\n" \
393 " /**\n" \
394 " This is the event when the user selected an image.\n" \
395 " The image will be read with the FileReader (allowed in web, because the user selected the file).\n" \
396 " */\n" \
397 " function chat_onImageSelected(event)\n" \
398 " {\n" \
399 " let file = event.target.files[0];\n" \
400 " if(!file || !/^image\\/" "/.test(file.type))\n" \
401 " return;\n" \
402 " let selectedUser = document.querySelector('div#Users > div.selected');\n" \
403 " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \
404 " let reader = new FileReader();\n" \
405 " reader.onload = function(event) {\n" \
406 " chat_onImageRead(event, file.type, selectedUserId);\n" \
407 " };\n" \
408 " reader.readAsArrayBuffer(file);\n" \
409 " }\n" \
410 "\n" \
411 " /**\n" \
412 " This is the event when the user selected image has been read.\n" \
413 " This will add our chat protocol prefix and send it via the websocket.\n" \
414 " */\n" \
415 " function chat_onImageRead(event, fileType, selectedUserId)\n" \
416 " {\n" \
417 " let encoder = new TextEncoder();\n" \
418 " let prefix = ((selectedUserId == 0 ? '||' : `private|${selectedUserId}|`) + fileType + '|');\n" \
419 " prefix = encoder.encode(prefix);\n" \
420 " let byteData = new Uint8Array(event.target.result);\n" \
421 " let totalLength = prefix.length + byteData.length;\n" \
422 " let resultByteData = new Uint8Array(totalLength);\n" \
423 " resultByteData.set(prefix, 0);\n" \
424 " resultByteData.set(byteData, prefix.length);\n" \
425 " socket.send(resultByteData);\n" \
426 " }\n" \
427 "\n" \
428 " /**\n" \
429 " This is the event when the user clicked a name in the user list.\n" \
430 " This is useful to send private messages or images without needing to add the /m prefix.\n" \
431 " */\n" \
432 " function chat_onUserClicked(event, selectedUserId)\n" \
433 " {\n" \
434 " let newSelected = event.target.closest('div#Users > div');\n" \
435 " if(newSelected === null)\n" \
436 " return;\n" \
437 " for(let div of this.querySelectorAll(':scope > div.selected'))\n" \
438 " div.classList.remove('selected');\n" \
439 " newSelected.classList.add('selected');\n" \
440 " }\n" \
441 "\n" \
442 " /**\n" \
443 " This functions returns the current id of a user identified by its name.\n" \
444 " */\n" \
445 " function chat_getUserIdByName(name)\n" \
446 " {\n" \
447 " let nameUpper = name.toUpperCase();\n" \
448 " for(let pair of connectedUsers)\n" \
449 " {\n" \
450 " if(pair[1].toUpperCase() == nameUpper)\n" \
451 " return pair[0];\n" \
452 " }\n" \
453 " return null;\n" \
454 " }\n" \
455 "\n" \
456 " /**\n" \
457 " This functions clears the entire user list (needed for reconnecting).\n" \
458 " */\n" \
459 " function chat_clearUserList()\n" \
460 " {\n" \
461 " let users = document.getElementById('Users');\n" \
462 " for(let div of users.querySelectorAll(':scope > div'))\n" \
463 " {\n" \
464 " if(div.getAttribute('data-user') === '0')\n" \
465 " {\n" \
466 " div.classList.add('selected');\n" \
467 " }\n" \
468 " else\n" \
469 " {\n" \
470 " div.parentNode.removeChild(div);\n" \
471 " }\n" \
472 " }\n" \
473 " return null;\n" \
474 " }\n" \
475 "\n" \
476 " /**\n" \
477 " This is the event when the socket has established a connection.\n" \
478 " This will initialize an empty chat and enable the controls.\n" \
479 " */\n" \
480 " function socket_onopen(event)\n" \
481 " {\n" \
482 " connectedUsers.clear();\n" \
483 " chat_clearUserList();\n" \
484 " chat_addMessage('Connected!', { type: 'system' });\n" \
485 " document.getElementById('InputMessage').disabled = false;\n" \
486 " document.getElementById('InputMessageButton').disabled = false;\n" \
487 " document.getElementById('InputImageButton').disabled = false;\n" \
488 " }\n" \
489 "\n" \
490 " /**\n" \
491 " This is the event when the socket has been closed.\n" \
492 " This will lock the controls.\n" \
493 " */\n" \
494 " function socket_onclose(event)\n" \
495 " {\n" \
496 " chat_addMessage('Connection closed!', { type: 'system', reconnect: true });\n" \
497 " document.getElementById('InputMessage').disabled = true;\n" \
498 " document.getElementById('InputMessageButton').disabled = true;\n" \
499 " document.getElementById('InputImageButton').disabled = true;\n" \
500 " }\n" \
501 "\n" \
502 " /**\n" \
503 " This is the event when the socket reported an error.\n" \
504 " This will just make an output.\n" \
505 " In the web browser console (F12 on many browsers) will show you more detailed error information.\n" \
506 " */\n" \
507 " function socket_onerror(event)\n" \
508 " {\n" \
509 " console.error('WebSocket error reported: ', event);\n" \
510 " chat_addMessage('The socket reported an error!', { type: 'error' });\n" \
511 " }\n" \
512 "\n" \
513 " /**\n" \
514 " This is the event when the socket has received a message.\n" \
515 " This will parse the message and execute the corresponding command (or add the message).\n" \
516 " */\n" \
517 " function socket_onmessage(event)\n" \
518 " {\n" \
519 " if(typeof(event.data) === 'string')\n" \
520 " {\n" \
521 " /* text message or command */ \n" \
522 " let message = event.data.split('|', 3);\n" \
523 " switch(message[0])\n" \
524 " {\n" \
525 " case 'userinit':\n" \
526 " connectedUsers.set(message[1], message[2]);\n" \
527 " {\n" \
528 " let users = document.getElementById('Users');\n" \
529 " let div = document.createElement('div');\n" \
530 " users.appendChild(div);\n" \
531 " div.innerText = message[2];\n" \
532 " div.setAttribute('data-user', message[1]);\n" \
533 " }\n" \
534 " break;\n" \
535 " case 'useradd':\n" \
536 " connectedUsers.set(message[1], message[2]);\n" \
537 " chat_addMessage(`The user '${message[2]}' has joined our lovely chatroom.`, { type: 'moderator' });\n" \
538 " {\n" \
539 " let users = document.getElementById('Users');\n" \
540 " let div = document.createElement('div');\n" \
541 " users.appendChild(div);\n" \
542 " div.innerText = message[2];\n" \
543 " div.setAttribute('data-user', message[1]);\n" \
544 " }\n" \
545 " break;\n" \
546 " case 'userdel':\n" \
547 " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has left our chatroom. We will miss you.`, { type: 'moderator' });\n" \
548 " connectedUsers.delete(message[1]);\n" \
549 " {\n" \
550 " let users = document.getElementById('Users');\n" \
551 " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \
552 " if(div !== null)\n" \
553 " {\n" \
554 " users.removeChild(div);\n" \
555 " if(div.classList.contains('selected'))\n" \
556 " users.querySelector('div[data-user=\\'0\\']').classList.add('selected');\n" \
557 " }\n" \
558 " }\n" \
559 " break;\n" \
560 " case 'username':\n" \
561 " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has changed his name to '${message[2]}'.`, { type: 'moderator' });\n" \
562 " connectedUsers.set(message[1], message[2]);\n" \
563 " {\n" \
564 " let users = document.getElementById('Users');\n" \
565 " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \
566 " if(div !== null)\n" \
567 " {\n" \
568 " div.innerText = message[2];\n" \
569 " }\n" \
570 " }\n" \
571 " break;\n" \
572 " case 'ping':\n" \
573 " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has a ping of ${message[2]} ms.`, { type: 'moderator' });\n" \
574 " break;\n" \
575 " default:\n" \
576 " chat_addMessage(message[2], { type: message[0], from: connectedUsers.get(message[1]) });\n" \
577 " break;\n" \
578 " }\n" \
579 " }\n" \
580 " else\n" \
581 " {\n" \
582 " /* We received a binary frame, which means a picture here */ \n" \
583 " let byteData = new Uint8Array(event.data);\n" \
584 " let decoder = new TextDecoder();\n" \
585 " let message = [ ];\n" \
586 " /* message type */ \n" \
587 " let j = 0;\n" \
588 " let i = byteData.indexOf(0x7C, j); /* | = 0x7C;*/ \n"\
589 " if(i < 0)\n" \
590 " return;\n" \
591 " message.push(decoder.decode(byteData.slice(0, i)));\n" \
592 " /* picture from */ \n" \
593 " j = i + 1;\n" \
594 " i = byteData.indexOf(0x7C, j);\n" \
595 " if(i < 0)\n" \
596 " return;\n" \
597 " message.push(decoder.decode(byteData.slice(j, i)));\n" \
598 " /* picture encoding */ \n" \
599 " j = i + 1;\n" \
600 " i = byteData.indexOf(0x7C, j);\n" \
601 " if(i < 0)\n" \
602 " return;\n" \
603 " message.push(decoder.decode(byteData.slice(j, i)));\n" \
604 " /* picture */ \n" \
605 " byteData = byteData.slice(i + 1);\n" \
606 " chat_addMessage(byteData, { type: message[0], from: connectedUsers.get(message[1]), pictureType: message[2] });\n" \
607 " }\n" \
608 " }\n" \
609 "</script>" \
610 "</head>" \
611 "<body><noscript>Please enable JavaScript to test the libmicrohttpd Websocket chatserver demo!</noscript></body>" \
612 "</html>"
613
614 #define PAGE_NOT_FOUND \
615 "404 Not Found"
616
617 #define PAGE_INVALID_WEBSOCKET_REQUEST \
618 "Invalid WebSocket request!"
619
620 /**
621 * This struct is used to keep the data of a connected chat user.
622 * It is passed to the socket-receive thread (connecteduser_receive_messages) as well as to
623 * the socket-send thread (connecteduser_send_messages).
624 * It can also be accessed via the global array users (mutex protected).
625 */
626 struct ConnectedUser
627 {
628 /* the TCP/IP socket for reading/writing */
629 MHD_socket fd;
630 /* the UpgradeResponseHandle of libmicrohttpd (needed for closing the socket) */
631 struct MHD_UpgradeResponseHandle *urh;
632 /* the websocket encode/decode stream */
633 struct MHD_WebSocketStream *ws;
634 /* the possibly read data at the start (only used once) */
635 char *extra_in;
636 size_t extra_in_size;
637 /* the unique user id (counting from 1, ids will never be re-used) */
638 size_t user_id;
639 /* the current user name */
640 char *user_name;
641 size_t user_name_len;
642 /* the zero-based index of the next message;
643 may be decremented when old messages are deleted */
644 size_t next_message_index;
645 /* specifies whether the websocket shall be closed (1) or not (0) */
646 int disconnect;
647 /* condition variable to wake up the sender of this connection */
648 pthread_cond_t wake_up_sender;
649 /* mutex to ensure that no send actions are mixed
650 (sending can be done by send and recv thread;
651 may not be simultaneously locked with chat_mutex by the same thread) */
652 pthread_mutex_t send_mutex;
653 /* specifies whether a ping shall be executed (1), is being executed (2) or
654 no ping is pending (0) */
655 int ping_status;
656 /* the start time of the ping, if a ping is running */
657 struct timespec ping_start;
658 /* the message used for the ping (must match the pong response)*/
659 char ping_message[128];
660 /* the length of the ping message (may not exceed 125) */
661 size_t ping_message_len;
662 /* the numeric ping message suffix to detect ping messages, which are too old */
663 int ping_counter;
664 };
665
666 /**
667 * A single message, which has been send via the chat.
668 * This can be text, an image or a command.
669 */
670 struct Message
671 {
672 /* The user id of the sender. This is 0 if it is a system message- */
673 size_t from_user_id;
674 /* The user id of the recipient. This is 0 if every connected user shall receive it */
675 size_t to_user_id;
676 /* The data of the message. */
677 char *data;
678 size_t data_len;
679 /* Specifies whether the data is UTF-8 encoded text (0) or binary data (1) */
680 int is_binary;
681 };
682
683 /* the unique user counter for new users (only accessed by main thread) */
684 size_t unique_user_id = 0;
685
686 /* the chat data (users and messages; may be accessed by all threads, but is protected by mutex) */
687 pthread_mutex_t chat_mutex;
688 struct ConnectedUser **users = NULL;
689 size_t user_count = 0;
690 struct Message **messages = NULL;
691 size_t message_count = 0;
692 /* specifies whether all websockets must close (1) or not (0) */
693 volatile int disconnect_all = 0;
694 /* a counter for cleaning old messages (each 10 messages we will try to clean the list */
695 int clean_count = 0;
696 #define CLEANUP_LIMIT 10
697
698 /**
699 * Change socket to blocking.
700 *
701 * @param fd the socket to manipulate
702 */
703 static void
make_blocking(MHD_socket fd)704 make_blocking (MHD_socket fd)
705 {
706 #if defined(MHD_POSIX_SOCKETS)
707 int flags;
708
709 flags = fcntl (fd, F_GETFL);
710 if (-1 == flags)
711 abort ();
712 if ((flags & ~O_NONBLOCK) != flags)
713 if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
714 abort ();
715 #elif defined(MHD_WINSOCK_SOCKETS)
716 unsigned long flags = 0;
717
718 if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
719 abort ();
720 #endif /* MHD_WINSOCK_SOCKETS */
721 }
722
723
724 /**
725 * Sends all data of the given buffer via the TCP/IP socket
726 *
727 * @param fd The TCP/IP socket which is used for sending
728 * @param buf The buffer with the data to send
729 * @param len The length in bytes of the data in the buffer
730 */
731 static void
send_all(struct ConnectedUser * cu,const char * buf,size_t len)732 send_all (struct ConnectedUser *cu,
733 const char *buf,
734 size_t len)
735 {
736 ssize_t ret;
737 size_t off;
738
739 if (0 == pthread_mutex_lock (&cu->send_mutex))
740 {
741 for (off = 0; off < len; off += ret)
742 {
743 ret = send (cu->fd,
744 &buf[off],
745 (int) (len - off),
746 0);
747 if (0 > ret)
748 {
749 if (EAGAIN == errno)
750 {
751 ret = 0;
752 continue;
753 }
754 break;
755 }
756 if (0 == ret)
757 break;
758 }
759 pthread_mutex_unlock (&cu->send_mutex);
760 }
761 }
762
763
764 /**
765 * Adds a new chat message to the list of messages.
766 *
767 * @param from_user_id the user id of the sender (0 means system)
768 * @param to_user_id the user id of the recipiend (0 means everyone)
769 * @param data the data to send (UTF-8 text or binary; will be copied)
770 * @param data_len the length of the data to send
771 * @param is_binary specifies whether the data is UTF-8 text (0) or binary (1)
772 * @param needs_lock specifies whether the caller has already locked the global chat mutex (0) or
773 * if this procedure needs to lock it (1)
774 *
775 * @return 0 on success, other values on error
776 */
777 static int
chat_addmessage(size_t from_user_id,size_t to_user_id,char * data,size_t data_len,int is_binary,int needs_lock)778 chat_addmessage (size_t from_user_id,
779 size_t to_user_id,
780 char *data,
781 size_t data_len,
782 int is_binary,
783 int needs_lock)
784 {
785 /* allocate the buffer and fill it with data */
786 struct Message *message = (struct Message *) malloc (sizeof (struct Message));
787 if (NULL == message)
788 return 1;
789
790 memset (message, 0, sizeof (struct Message));
791 message->from_user_id = from_user_id;
792 message->to_user_id = to_user_id;
793 message->is_binary = is_binary;
794 message->data_len = data_len;
795 message->data = malloc (data_len + 1);
796 if (NULL == message->data)
797 {
798 free (message);
799 return 1;
800 }
801 memcpy (message->data, data, data_len);
802 message->data[data_len] = 0;
803
804 /* lock the global mutex if needed */
805 if (0 != needs_lock)
806 {
807 if (0 != pthread_mutex_lock (&chat_mutex))
808 return 1;
809 }
810
811 /* add the new message to the global message list */
812 size_t message_count_ = message_count + 1;
813 struct Message **messages_ = (struct Message **) realloc (messages,
814 message_count_
815 * sizeof (struct
816 Message *));
817 if (NULL == messages_)
818 {
819 free (message);
820 if (0 != needs_lock)
821 pthread_mutex_unlock (&chat_mutex);
822 return 1;
823 }
824 messages_[message_count] = message;
825 messages = messages_;
826 message_count = message_count_;
827
828 /* inform the sender threads about the new message */
829 for (size_t i = 0; i < user_count; ++i)
830 pthread_cond_signal (&users[i]->wake_up_sender);
831
832 /* unlock the global mutex if needed */
833 if (0 != needs_lock)
834 {
835 if (0 != needs_lock)
836 pthread_mutex_unlock (&chat_mutex);
837 }
838 return 0;
839 }
840
841
842 /**
843 * Cleans up old messages
844 *
845 * @param needs_lock specifies whether the caller has already locked the global chat mutex (0) or
846 * if this procedure needs to lock it (1)
847 * @return 0 on success, other values on error
848 */
849 static int
chat_clearmessages(int needs_lock)850 chat_clearmessages (int needs_lock)
851 {
852 /* lock the global mutex if needed */
853 if (0 != needs_lock)
854 {
855 if (0 != pthread_mutex_lock (&chat_mutex))
856 return 1;
857 }
858
859 /* update the clean counter and check whether we need cleaning */
860 ++clean_count;
861 if (CLEANUP_LIMIT > clean_count)
862 {
863 /* no cleanup required */
864 if (0 != needs_lock)
865 {
866 pthread_mutex_unlock (&chat_mutex);
867 }
868 return 0;
869 }
870 clean_count = 0;
871
872 /* check whether we got any messages (without them no cleaning is required */
873 if (0 < message_count)
874 {
875 /* then check whether we got any connected users */
876 if (0 < user_count)
877 {
878 /* determine the minimum index for the next message of all connected users */
879 size_t min_message = users[0]->next_message_index;
880 for (size_t i = 1; i < user_count; ++i)
881 {
882 if (min_message > users[i]->next_message_index)
883 min_message = users[i]->next_message_index;
884 }
885 if (0 < min_message)
886 {
887 /* remove all messages with index below min_message and update
888 the message indices of the users */
889 for (size_t i = 0; i < min_message; ++i)
890 {
891 free (messages[i]->data);
892 free (messages[i]);
893 }
894 for (size_t i = min_message; i < message_count; ++i)
895 messages[i - min_message] = messages[i];
896 message_count -= min_message;
897 for (size_t i = 0; i < user_count; ++i)
898 users[i]->next_message_index -= min_message;
899 }
900 }
901 else
902 {
903 /* without connected users, simply remove all messages */
904 for (size_t i = 0; i < message_count; ++i)
905 {
906 free (messages[i]->data);
907 free (messages[i]);
908 }
909 free (messages);
910 messages = NULL;
911 message_count = 0;
912 }
913 }
914
915 /* unlock the global mutex if needed */
916 if (0 != needs_lock)
917 {
918 pthread_mutex_unlock (&chat_mutex);
919 }
920 return 0;
921 }
922
923
924 /**
925 * Adds a new chat user to the global user list.
926 * This will be called at the start of connecteduser_receive_messages.
927 *
928 * @param cu The connected user
929 * @return 0 on success, other values on error
930 */
931 static int
chat_adduser(struct ConnectedUser * cu)932 chat_adduser (struct ConnectedUser *cu)
933 {
934 /* initialize the notification message of the new user */
935 char user_index[32];
936 snprintf (user_index, 32, "%d", (int) cu->user_id);
937 size_t user_index_len = strlen (user_index);
938 size_t data_len = user_index_len + cu->user_name_len + 9;
939 char *data = (char *) malloc (data_len + 1);
940 if (NULL == data)
941 return 1;
942 strcpy (data, "useradd|");
943 strcat (data, user_index);
944 strcat (data, "|");
945 strcat (data, cu->user_name);
946
947 /* lock the mutex */
948 if (0 != pthread_mutex_lock (&chat_mutex))
949 {
950 free (data);
951 return 1;
952 }
953 /* inform the other chat users about the new user */
954 if (0 != chat_addmessage (0,
955 0,
956 data,
957 data_len,
958 0,
959 0))
960 {
961 free (data);
962 pthread_mutex_unlock (&chat_mutex);
963 return 1;
964 }
965 free (data);
966
967 /* add the new user to the list */
968 size_t user_count_ = user_count + 1;
969 struct ConnectedUser **users_ = (struct ConnectedUser **) realloc (users,
970 user_count_
971 * sizeof (
972 struct
973 ConnectedUser
974 *));
975 if (NULL == users_)
976 {
977 /* realloc failed */
978 pthread_mutex_unlock (&chat_mutex);
979 return 1;
980 }
981 users_[user_count] = cu;
982 users = users_;
983 user_count = user_count_;
984
985 /* Initialize the next message index to the current message count. */
986 /* This will skip all old messages for this new connected user. */
987 cu->next_message_index = message_count;
988
989 /* unlock the mutex */
990 pthread_mutex_unlock (&chat_mutex);
991 return 0;
992 }
993
994
995 /**
996 * Removes a chat user from the global user list.
997 *
998 * @param cu The connected user
999 * @return 0 on success, other values on error
1000 */
1001 static int
chat_removeuser(struct ConnectedUser * cu)1002 chat_removeuser (struct ConnectedUser *cu)
1003 {
1004 char user_index[32];
1005
1006 /* initialize the chat message for the removed user */
1007 snprintf (user_index, 32, "%d", (int) cu->user_id);
1008 size_t user_index_len = strlen (user_index);
1009 size_t data_len = user_index_len + 9;
1010 char *data = (char *) malloc (data_len + 1);
1011 if (NULL == data)
1012 return 1;
1013 strcpy (data, "userdel|");
1014 strcat (data, user_index);
1015 strcat (data, "|");
1016
1017 /* lock the mutex */
1018 if (0 != pthread_mutex_lock (&chat_mutex))
1019 {
1020 free (data);
1021 return 1;
1022 }
1023 /* inform the other chat users that the user is gone */
1024 int got_error = 0;
1025 if (0 != chat_addmessage (0, 0, data, data_len, 0, 0))
1026 {
1027 free (data);
1028 got_error = 1;
1029 }
1030
1031 /* remove the user from the list */
1032 int found = 0;
1033 for (size_t i = 0; i < user_count; ++i)
1034 {
1035 if (cu == users[i])
1036 {
1037 found = 1;
1038 for (size_t j = i + 1; j < user_count; ++j)
1039 {
1040 users[j - 1] = users[j];
1041 }
1042 --user_count;
1043 break;
1044 }
1045 }
1046 if (0 == found)
1047 got_error = 1;
1048
1049 /* unlock the mutex */
1050 pthread_mutex_unlock (&chat_mutex);
1051
1052 return got_error;
1053 }
1054
1055
1056 /**
1057 * Renames a chat user
1058 *
1059 * @param cu The connected user
1060 * @param new_name The new user name. On success this pointer will be taken.
1061 * @param new_name_len The length of the new name
1062 * @return 0 on success, other values on error. 2 means name already in use.
1063 */
1064 static int
chat_renameuser(struct ConnectedUser * cu,char * new_name,size_t new_name_len)1065 chat_renameuser (struct ConnectedUser *cu,
1066 char *new_name,
1067 size_t new_name_len)
1068 {
1069 /* lock the mutex */
1070 if (0 != pthread_mutex_lock (&chat_mutex))
1071 {
1072 return 1;
1073 }
1074
1075 /* check whether the name is already in use */
1076 for (size_t i = 0; i < user_count; ++i)
1077 {
1078 if (cu != users[i])
1079 {
1080 if ((users[i]->user_name_len == new_name_len) &&
1081 (0 == strcasecmp (users[i]->user_name, new_name)))
1082 {
1083 pthread_mutex_unlock (&chat_mutex);
1084 return 2;
1085 }
1086 }
1087 }
1088
1089 /* generate the notification message */
1090 char user_index[32];
1091 snprintf (user_index, 32, "%d", (int) cu->user_id);
1092 size_t user_index_len = strlen (user_index);
1093 size_t data_len = user_index_len + new_name_len + 10;
1094 char *data = (char *) malloc (data_len + 1);
1095 if (NULL == data)
1096 return 1;
1097 strcpy (data, "username|");
1098 strcat (data, user_index);
1099 strcat (data, "|");
1100 strcat (data, new_name);
1101
1102 /* inform the other chat users about the new name */
1103 if (0 != chat_addmessage (0, 0, data, data_len, 0, 0))
1104 {
1105 free (data);
1106 pthread_mutex_unlock (&chat_mutex);
1107 return 1;
1108 }
1109 free (data);
1110
1111 /* accept the new user name */
1112 free (cu->user_name);
1113 cu->user_name = new_name;
1114 cu->user_name_len = new_name_len;
1115
1116 /* unlock the mutex */
1117 pthread_mutex_unlock (&chat_mutex);
1118
1119 return 0;
1120 }
1121
1122
1123 /**
1124 * Parses received data from the TCP/IP socket with the websocket stream
1125 *
1126 * @param cu The connected user
1127 * @param new_name The new user name
1128 * @param new_name_len The length of the new name
1129 * @return 0 on success, other values on error
1130 */
1131 static int
connecteduser_parse_received_websocket_stream(struct ConnectedUser * cu,char * buf,size_t buf_len)1132 connecteduser_parse_received_websocket_stream (struct ConnectedUser *cu,
1133 char *buf,
1134 size_t buf_len)
1135 {
1136 size_t buf_offset = 0;
1137 while (buf_offset < buf_len)
1138 {
1139 size_t new_offset = 0;
1140 char *frame_data = NULL;
1141 size_t frame_len = 0;
1142 int status = MHD_websocket_decode (cu->ws,
1143 buf + buf_offset,
1144 buf_len - buf_offset,
1145 &new_offset,
1146 &frame_data,
1147 &frame_len);
1148 if (0 > status)
1149 {
1150 /* an error occurred and the connection must be closed */
1151 if (NULL != frame_data)
1152 {
1153 /* depending on the WebSocket flag */
1154 /* MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR */
1155 /* close frames might be generated on errors */
1156 send_all (cu,
1157 frame_data,
1158 frame_len);
1159 MHD_websocket_free (cu->ws, frame_data);
1160 }
1161 return 1;
1162 }
1163 else
1164 {
1165 buf_offset += new_offset;
1166
1167 if (0 < status)
1168 {
1169 /* the frame is complete */
1170 switch (status)
1171 {
1172 case MHD_WEBSOCKET_STATUS_TEXT_FRAME:
1173 case MHD_WEBSOCKET_STATUS_BINARY_FRAME:
1174 /**
1175 * a text or binary frame has been received.
1176 * in this chat server example we use a simple protocol where
1177 * the JavaScript added a prefix like "<command>|<to_user_id>|data".
1178 * Some examples:
1179 * "||test" means a regular chat message to everyone with the message "test".
1180 * "private|1|secret" means a private chat message to user with id 1 with the message "secret".
1181 * "name||MyNewName" means that the user requests a rename to "MyNewName"
1182 * "ping|1|" means that the user with id 1 shall get a ping
1183 *
1184 * Binary data is handled here like text data.
1185 * The difference in the data is only checked by the JavaScript.
1186 */
1187 {
1188 size_t command = 1000;
1189 size_t from_user_id = cu->user_id;
1190 size_t to_user_id = 0;
1191 size_t i;
1192
1193 /* parse the command */
1194 for (i = 0; i < frame_len; ++i)
1195 {
1196 if ('|' == frame_data[i])
1197 {
1198 frame_data[i] = 0;
1199 ++i;
1200 break;
1201 }
1202 }
1203 if (0 < i)
1204 {
1205 if (i == 1)
1206 {
1207 /* no command means regular message */
1208 command = 0;
1209 }
1210 else if (0 == strcasecmp (frame_data, "private"))
1211 {
1212 /* private means private message */
1213 command = 1;
1214 }
1215 else if (0 == strcasecmp (frame_data, "name"))
1216 {
1217 /* name means chat user rename */
1218 command = 2;
1219 }
1220 else if (0 == strcasecmp (frame_data, "ping"))
1221 {
1222 /* ping means a ping request */
1223 command = 3;
1224 }
1225 else
1226 {
1227 /* no other commands supported, so this means invalid */
1228 command = 1000;
1229 }
1230 }
1231
1232 /* parse the to_user_id, if given */
1233 size_t j = i;
1234 for (; j < frame_len; ++j)
1235 {
1236 if ('|' == frame_data[j])
1237 {
1238 frame_data[j] = 0;
1239 ++j;
1240 break;
1241 }
1242 }
1243 if (i + 1 < j)
1244 {
1245 to_user_id = (size_t) atoi (frame_data + i);
1246 }
1247
1248 /* decide via the command what action to do */
1249 if (frame_len >= j)
1250 {
1251 int is_binary = (MHD_WEBSOCKET_STATUS_BINARY_FRAME == status ? 1 :
1252 0);
1253 switch (command)
1254 {
1255 case 0:
1256 /* regular chat message */
1257 {
1258 /**
1259 * Generate the message for the message list.
1260 * Regular chat messages get the command "regular".
1261 * After that we add the from_user_id, followed by the content.
1262 * The content must always be copied with memcpy instead of strcat,
1263 * because the data (binary as well as UTF-8 encoded) is allowed
1264 * to contain the NUL character.
1265 * However we will add a terminating NUL character,
1266 * which is not included in the data length
1267 * (and thus will not be send to the recipients).
1268 * This is useful for debugging with an IDE.
1269 */
1270 char user_index[32];
1271 snprintf (user_index, 32, "%d", (int) cu->user_id);
1272 size_t user_index_len = strlen (user_index);
1273 size_t data_len = user_index_len + frame_len - j + 9;
1274 char *data = (char *) malloc (data_len + 1);
1275 if (NULL != data)
1276 {
1277 strcpy (data, "regular|");
1278 strcat (data, user_index);
1279 strcat (data, "|");
1280 size_t offset = strlen (data);
1281 memcpy (data + offset,
1282 frame_data + j,
1283 frame_len - j);
1284 data[data_len] = 0;
1285
1286 /* add the chat message to the global list */
1287 chat_addmessage (from_user_id,
1288 0,
1289 data,
1290 data_len,
1291 is_binary,
1292 1);
1293 free (data);
1294 }
1295 }
1296 break;
1297
1298 case 1:
1299 /* private chat message */
1300 if (0 != to_user_id)
1301 {
1302 /**
1303 * Generate the message for the message list.
1304 * This is similar to the code for regular messages above.
1305 * The difference is the prefix "private"
1306 */
1307 char user_index[32];
1308 snprintf (user_index, 32, "%d", (int) cu->user_id);
1309 size_t user_index_len = strlen (user_index);
1310 size_t data_len = user_index_len + frame_len - j + 9;
1311 char *data = (char *) malloc (data_len + 1);
1312 if (NULL != data)
1313 {
1314
1315 strcpy (data, "private|");
1316 strcat (data, user_index);
1317 strcat (data, "|");
1318 size_t offset = strlen (data);
1319 memcpy (data + offset,
1320 frame_data + j,
1321 frame_len - j);
1322 data[data_len] = 0;
1323
1324 /* add the chat message to the global list */
1325 chat_addmessage (from_user_id,
1326 to_user_id,
1327 data,
1328 data_len,
1329 is_binary,
1330 1);
1331 free (data);
1332 }
1333 }
1334 break;
1335
1336 case 2:
1337 /* rename */
1338 {
1339 /* check whether the new name is valid and allocate a new buffer for it */
1340 size_t new_name_len = frame_len - j;
1341 if (0 == new_name_len)
1342 {
1343 chat_addmessage (0,
1344 from_user_id,
1345 "error||Your new name is invalid. You haven't been renamed.",
1346 58,
1347 0,
1348 1);
1349 break;
1350 }
1351 char *new_name = (char *) malloc (new_name_len + 1);
1352 if (NULL == new_name)
1353 {
1354 chat_addmessage (0,
1355 from_user_id,
1356 "error||Error while renaming. You haven't been renamed.",
1357 54,
1358 0,
1359 1);
1360 break;
1361 }
1362 new_name[new_name_len] = 0;
1363 for (size_t k = 0; k < new_name_len; ++k)
1364 {
1365 char c = frame_data[j + k];
1366 if ((32 >= c) || (c >= 127))
1367 {
1368 free (new_name);
1369 new_name = NULL;
1370 chat_addmessage (0,
1371 from_user_id,
1372 "error||Your new name contains invalid characters. You haven't been renamed.",
1373 75,
1374 0,
1375 1);
1376 break;
1377 }
1378 new_name[k] = c;
1379 }
1380 if (NULL == new_name)
1381 break;
1382
1383 /* rename the user */
1384 int rename_result = chat_renameuser (cu,
1385 new_name,
1386 new_name_len);
1387 if (0 != rename_result)
1388 {
1389 /* the buffer will only be freed if no rename was possible */
1390 free (new_name);
1391 if (2 == rename_result)
1392 {
1393 chat_addmessage (0,
1394 from_user_id,
1395 "error||Your new name is already in use by another user. You haven't been renamed.",
1396 81,
1397 0,
1398 1);
1399 }
1400 else
1401 {
1402 chat_addmessage (0,
1403 from_user_id,
1404 "error||Error while renaming. You haven't been renamed.",
1405 54,
1406 0,
1407 1);
1408 }
1409 }
1410 }
1411 break;
1412
1413 case 3:
1414 /* ping */
1415 {
1416 if (0 == pthread_mutex_lock (&chat_mutex))
1417 {
1418 /* check whether the to_user exists */
1419 struct ConnectedUser *ping_user = NULL;
1420 for (size_t k = 0; k < user_count; ++k)
1421 {
1422 if (users[k]->user_id == to_user_id)
1423 {
1424 ping_user = users[k];
1425 break;
1426 }
1427 }
1428 if (NULL == ping_user)
1429 {
1430 chat_addmessage (0,
1431 from_user_id,
1432 "error||Couldn't find the specified user for pinging.",
1433 52,
1434 0,
1435 0);
1436 }
1437 else
1438 {
1439 /* if pinging is requested, */
1440 /* we mark the user and inform the sender about this */
1441 if (0 == ping_user->ping_status)
1442 {
1443 ping_user->ping_status = 1;
1444 pthread_cond_signal (&ping_user->wake_up_sender);
1445 }
1446 }
1447 pthread_mutex_unlock (&chat_mutex);
1448 }
1449 else
1450 {
1451 chat_addmessage (0,
1452 from_user_id,
1453 "error||Error while pinging.",
1454 27,
1455 0,
1456 1);
1457 }
1458 }
1459 break;
1460
1461 default:
1462 /* invalid command */
1463 chat_addmessage (0,
1464 from_user_id,
1465 "error||You sent an invalid command.",
1466 35,
1467 0,
1468 1);
1469 break;
1470 }
1471 }
1472 }
1473 MHD_websocket_free (cu->ws,
1474 frame_data);
1475 return 0;
1476
1477 case MHD_WEBSOCKET_STATUS_CLOSE_FRAME:
1478 /* if we receive a close frame, we will respond with one */
1479 MHD_websocket_free (cu->ws,
1480 frame_data);
1481 {
1482 char *result = NULL;
1483 size_t result_len = 0;
1484 int er = MHD_websocket_encode_close (cu->ws,
1485 MHD_WEBSOCKET_CLOSEREASON_REGULAR,
1486 NULL,
1487 0,
1488 &result,
1489 &result_len);
1490 if (MHD_WEBSOCKET_STATUS_OK == er)
1491 {
1492 send_all (cu,
1493 result,
1494 result_len);
1495 MHD_websocket_free (cu->ws, result);
1496 }
1497 }
1498 return 1;
1499
1500 case MHD_WEBSOCKET_STATUS_PING_FRAME:
1501 /* if we receive a ping frame, we will respond */
1502 /* with the corresponding pong frame */
1503 {
1504 char *pong = NULL;
1505 size_t pong_len = 0;
1506 int er = MHD_websocket_encode_pong (cu->ws,
1507 frame_data,
1508 frame_len,
1509 &pong,
1510 &pong_len);
1511
1512 MHD_websocket_free (cu->ws,
1513 frame_data);
1514 if (MHD_WEBSOCKET_STATUS_OK == er)
1515 {
1516 send_all (cu,
1517 pong,
1518 pong_len);
1519 MHD_websocket_free (cu->ws,
1520 pong);
1521 }
1522 }
1523 return 0;
1524
1525 case MHD_WEBSOCKET_STATUS_PONG_FRAME:
1526 /* if we receive a pong frame, */
1527 /* we will check whether we requested this frame and */
1528 /* whether it is the last requested pong */
1529 if (2 == cu->ping_status)
1530 {
1531 cu->ping_status = 0;
1532 struct timespec now;
1533 timespec_get (&now, TIME_UTC);
1534 if ((cu->ping_message_len == frame_len) &&
1535 (0 == strcmp (frame_data,
1536 cu->ping_message)))
1537 {
1538 int ping = (int) (((int64_t) (now.tv_sec
1539 - cu->ping_start.tv_sec)) * 1000
1540 + ((int64_t) (now.tv_nsec
1541 - cu->ping_start.tv_nsec))
1542 / 1000000);
1543 char result_text[240];
1544 strcpy (result_text,
1545 "ping|");
1546 snprintf (result_text + 5, 235, "%d", (int) cu->user_id);
1547 strcat (result_text,
1548 "|");
1549 snprintf (result_text + strlen (result_text), 240 - strlen (
1550 result_text), "%d", (int) ping);
1551 chat_addmessage (0,
1552 0,
1553 result_text,
1554 strlen (result_text),
1555 0,
1556 1);
1557 }
1558 }
1559 MHD_websocket_free (cu->ws,
1560 frame_data);
1561 return 0;
1562
1563 default:
1564 /* This case should really never happen, */
1565 /* because there are only five types of (finished) websocket frames. */
1566 /* If it is ever reached, it means that there is memory corruption. */
1567 MHD_websocket_free (cu->ws,
1568 frame_data);
1569 return 1;
1570 }
1571 }
1572 }
1573 }
1574
1575 return 0;
1576 }
1577
1578
1579 /**
1580 * Sends messages from the message list over the TCP/IP socket
1581 * after encoding it with the websocket stream.
1582 * This is also used for server-side actions,
1583 * because the thread for receiving messages waits for
1584 * incoming data and cannot be woken up.
1585 * But the sender thread can be woken up easily.
1586 *
1587 * @param cls The connected user
1588 * @return Always NULL
1589 */
1590 static void *
connecteduser_send_messages(void * cls)1591 connecteduser_send_messages (void *cls)
1592 {
1593 struct ConnectedUser *cu = cls;
1594
1595 /* the main loop of sending messages requires to lock the mutex */
1596 if (0 == pthread_mutex_lock (&chat_mutex))
1597 {
1598 for (;;)
1599 {
1600 /* loop while not all messages processed */
1601 int all_messages_read = 0;
1602 while (0 == all_messages_read)
1603 {
1604 if (1 == disconnect_all)
1605 {
1606 /* the application closes and want that we disconnect all users */
1607 struct MHD_UpgradeResponseHandle *urh = cu->urh;
1608 if (NULL != urh)
1609 {
1610 /* Close the TCP/IP socket. */
1611 /* This will also wake-up the waiting receive-thread for this connected user. */
1612 cu->urh = NULL;
1613 MHD_upgrade_action (urh,
1614 MHD_UPGRADE_ACTION_CLOSE);
1615 }
1616 pthread_mutex_unlock (&chat_mutex);
1617 return NULL;
1618 }
1619 else if (1 == cu->disconnect)
1620 {
1621 /* The sender thread shall close. */
1622 /* This is only requested by the receive thread, so we can just leave. */
1623 pthread_mutex_unlock (&chat_mutex);
1624 return NULL;
1625 }
1626 else if (1 == cu->ping_status)
1627 {
1628 /* A pending ping is requested */
1629 ++cu->ping_counter;
1630 strcpy (cu->ping_message,
1631 "libmicrohttpdchatserverpingdata");
1632 snprintf (cu->ping_message + 31, 97, "%d", (int) cu->ping_counter);
1633 cu->ping_message_len = strlen (cu->ping_message);
1634 char *frame_data = NULL;
1635 size_t frame_len = 0;
1636 int er = MHD_websocket_encode_ping (cu->ws,
1637 cu->ping_message,
1638 cu->ping_message_len,
1639 &frame_data,
1640 &frame_len);
1641 if (MHD_WEBSOCKET_STATUS_OK == er)
1642 {
1643 cu->ping_status = 2;
1644 timespec_get (&cu->ping_start, TIME_UTC);
1645
1646 /* send the data via the TCP/IP socket and */
1647 /* unlock the mutex while sending */
1648 pthread_mutex_unlock (&chat_mutex);
1649 send_all (cu,
1650 frame_data,
1651 frame_len);
1652 if (0 != pthread_mutex_lock (&chat_mutex))
1653 {
1654 return NULL;
1655 }
1656 }
1657 MHD_websocket_free (cu->ws, frame_data);
1658 }
1659 else if (cu->next_message_index < message_count)
1660 {
1661 /* a chat message or command is pending */
1662 char *frame_data = NULL;
1663 size_t frame_len = 0;
1664 int er = 0;
1665 {
1666 struct Message *msg = messages[cu->next_message_index];
1667 if ((0 == msg->to_user_id) ||
1668 (cu->user_id == msg->to_user_id) ||
1669 (cu->user_id == msg->from_user_id) )
1670 {
1671 if (0 == msg->is_binary)
1672 {
1673 er = MHD_websocket_encode_text (cu->ws,
1674 msg->data,
1675 msg->data_len,
1676 MHD_WEBSOCKET_FRAGMENTATION_NONE,
1677 &frame_data,
1678 &frame_len,
1679 NULL);
1680 }
1681 else
1682 {
1683 er = MHD_websocket_encode_binary (cu->ws,
1684 msg->data,
1685 msg->data_len,
1686 MHD_WEBSOCKET_FRAGMENTATION_NONE,
1687 &frame_data,
1688 &frame_len);
1689 }
1690 }
1691 }
1692 ++cu->next_message_index;
1693
1694 /* send the data via the TCP/IP socket and */
1695 /* unlock the mutex while sending */
1696 pthread_mutex_unlock (&chat_mutex);
1697 if (MHD_WEBSOCKET_STATUS_OK == er)
1698 {
1699 send_all (cu,
1700 frame_data,
1701 frame_len);
1702 }
1703 MHD_websocket_free (cu->ws,
1704 frame_data);
1705 if (0 != pthread_mutex_lock (&chat_mutex))
1706 {
1707 return NULL;
1708 }
1709 /* check whether there are still pending messages */
1710 all_messages_read = (cu->next_message_index < message_count) ? 0 : 1;
1711 }
1712 else
1713 {
1714 all_messages_read = 1;
1715 }
1716 }
1717 /* clear old messages */
1718 chat_clearmessages (0);
1719
1720 /* Wait for wake up. */
1721 /* This will automatically unlock the mutex while waiting and */
1722 /* lock the mutex after waiting */
1723 pthread_cond_wait (&cu->wake_up_sender, &chat_mutex);
1724 }
1725 }
1726
1727 return NULL;
1728 }
1729
1730
1731 /**
1732 * Receives messages from the TCP/IP socket and
1733 * initializes the connected user.
1734 *
1735 * @param cls The connected user
1736 * @return Always NULL
1737 */
1738 static void *
connecteduser_receive_messages(void * cls)1739 connecteduser_receive_messages (void *cls)
1740 {
1741 struct ConnectedUser *cu = cls;
1742 char buf[128];
1743 ssize_t got;
1744 int result;
1745
1746 /* make the socket blocking */
1747 make_blocking (cu->fd);
1748
1749 /* generate the user name */
1750 {
1751 char user_name[32];
1752 strcpy (user_name, "User");
1753 snprintf (user_name + 4, 28, "%d", (int) cu->user_id);
1754 cu->user_name_len = strlen (user_name);
1755 cu->user_name = malloc (cu->user_name_len + 1);
1756 if (NULL == cu->user_name)
1757 {
1758 free (cu->extra_in);
1759 free (cu);
1760 MHD_upgrade_action (cu->urh,
1761 MHD_UPGRADE_ACTION_CLOSE);
1762 return NULL;
1763 }
1764 strcpy (cu->user_name, user_name);
1765 }
1766
1767 /* initialize the wake-up-sender condition variable */
1768 if (0 != pthread_cond_init (&cu->wake_up_sender, NULL))
1769 {
1770 MHD_upgrade_action (cu->urh,
1771 MHD_UPGRADE_ACTION_CLOSE);
1772 free (cu->user_name);
1773 free (cu->extra_in);
1774 free (cu);
1775 return NULL;
1776 }
1777
1778 /* initialize the send mutex */
1779 if (0 != pthread_mutex_init (&cu->send_mutex, NULL))
1780 {
1781 MHD_upgrade_action (cu->urh,
1782 MHD_UPGRADE_ACTION_CLOSE);
1783 pthread_cond_destroy (&cu->wake_up_sender);
1784 free (cu->user_name);
1785 free (cu->extra_in);
1786 free (cu);
1787 return NULL;
1788 }
1789
1790 /* add the user to the chat user list */
1791 chat_adduser (cu);
1792
1793 /* initialize the web socket stream for encoding/decoding */
1794 result = MHD_websocket_stream_init (&cu->ws,
1795 MHD_WEBSOCKET_FLAG_SERVER
1796 | MHD_WEBSOCKET_FLAG_NO_FRAGMENTS,
1797 0);
1798 if (MHD_WEBSOCKET_STATUS_OK != result)
1799 {
1800 chat_removeuser (cu);
1801 pthread_cond_destroy (&cu->wake_up_sender);
1802 pthread_mutex_destroy (&cu->send_mutex);
1803 MHD_upgrade_action (cu->urh,
1804 MHD_UPGRADE_ACTION_CLOSE);
1805 free (cu->user_name);
1806 free (cu->extra_in);
1807 free (cu);
1808 return NULL;
1809 }
1810
1811 /* send a list of all currently connected users (bypassing the messaging system) */
1812 {
1813 struct UserInit
1814 {
1815 char *user_init;
1816 size_t user_init_len;
1817 };
1818 struct UserInit *init_users = NULL;
1819 size_t init_users_len = 0;
1820
1821 /* first collect all users without sending (so the mutex isn't locked too long) */
1822 if (0 == pthread_mutex_lock (&chat_mutex))
1823 {
1824 if (0 < user_count)
1825 {
1826 init_users = (struct UserInit *) malloc (user_count * sizeof (struct
1827 UserInit));
1828 if (NULL != init_users)
1829 {
1830 init_users_len = user_count;
1831 for (size_t i = 0; i < user_count; ++i)
1832 {
1833 char user_index[32];
1834 snprintf (user_index, 32, "%d", (int) users[i]->user_id);
1835 size_t user_index_len = strlen (user_index);
1836 struct UserInit iu;
1837 iu.user_init_len = user_index_len + users[i]->user_name_len + 10;
1838 iu.user_init = (char *) malloc (iu.user_init_len + 1);
1839 if (NULL != iu.user_init)
1840 {
1841 strcpy (iu.user_init, "userinit|");
1842 strcat (iu.user_init, user_index);
1843 strcat (iu.user_init, "|");
1844 if (0 < users[i]->user_name_len)
1845 strcat (iu.user_init, users[i]->user_name);
1846 }
1847 init_users[i] = iu;
1848 }
1849 }
1850 }
1851 pthread_mutex_unlock (&chat_mutex);
1852 }
1853
1854 /* then send all users to the connected client */
1855 for (size_t i = 0; i < init_users_len; ++i)
1856 {
1857 char *frame_data = NULL;
1858 size_t frame_len = 0;
1859 if ((0 < init_users[i].user_init_len) && (NULL !=
1860 init_users[i].user_init) )
1861 {
1862 int status = MHD_websocket_encode_text (cu->ws,
1863 init_users[i].user_init,
1864 init_users[i].user_init_len,
1865 MHD_WEBSOCKET_FRAGMENTATION_NONE,
1866 &frame_data,
1867 &frame_len,
1868 NULL);
1869 if (MHD_WEBSOCKET_STATUS_OK == status)
1870 {
1871 send_all (cu,
1872 frame_data,
1873 frame_len);
1874 MHD_websocket_free (cu->ws,
1875 frame_data);
1876 }
1877 free (init_users[i].user_init);
1878 }
1879 }
1880 free (init_users);
1881 }
1882
1883 /* send the welcome message to the user (bypassing the messaging system) */
1884 {
1885 char *frame_data = NULL;
1886 size_t frame_len = 0;
1887 const char *welcome_msg = "moderator||" \
1888 "Welcome to the libmicrohttpd WebSocket chatserver example.\n" \
1889 "Supported commands are:\n" \
1890 " /m <user> <text> - sends a private message to the specified user\n" \
1891 " /ping <user> - sends a ping to the specified user\n" \
1892 " /name <name> - changes your name to the specified name\n" \
1893 " /disconnect - disconnects your websocket\n\n" \
1894 "All messages, which does not start with a slash, " \
1895 "are regular messages and will be sent to the selected user.\n\n" \
1896 "Have fun!";
1897 MHD_websocket_encode_text (cu->ws,
1898 welcome_msg,
1899 strlen (welcome_msg),
1900 MHD_WEBSOCKET_FRAGMENTATION_NONE,
1901 &frame_data,
1902 &frame_len,
1903 NULL);
1904 send_all (cu,
1905 frame_data,
1906 frame_len);
1907 MHD_websocket_free (cu->ws,
1908 frame_data);
1909 }
1910
1911 /* start the message-send thread */
1912 pthread_t pt;
1913 if (0 != pthread_create (&pt,
1914 NULL,
1915 &connecteduser_send_messages,
1916 cu))
1917 abort ();
1918
1919 /* start by parsing extra data MHD may have already read, if any */
1920 if (0 != cu->extra_in_size)
1921 {
1922 if (0 != connecteduser_parse_received_websocket_stream (cu,
1923 cu->extra_in,
1924 cu->extra_in_size))
1925 {
1926 chat_removeuser (cu);
1927 if (0 == pthread_mutex_lock (&chat_mutex))
1928 {
1929 cu->disconnect = 1;
1930 pthread_cond_signal (&cu->wake_up_sender);
1931 pthread_mutex_unlock (&chat_mutex);
1932 pthread_join (pt, NULL);
1933 }
1934 struct MHD_UpgradeResponseHandle *urh = cu->urh;
1935 if (NULL != urh)
1936 {
1937 cu->urh = NULL;
1938 MHD_upgrade_action (urh,
1939 MHD_UPGRADE_ACTION_CLOSE);
1940 }
1941 pthread_cond_destroy (&cu->wake_up_sender);
1942 pthread_mutex_destroy (&cu->send_mutex);
1943 MHD_websocket_stream_free (cu->ws);
1944 free (cu->user_name);
1945 free (cu->extra_in);
1946 free (cu);
1947 return NULL;
1948 }
1949 free (cu->extra_in);
1950 cu->extra_in = NULL;
1951 }
1952
1953 /* the main loop for receiving data */
1954 while (1)
1955 {
1956 got = recv (cu->fd,
1957 buf,
1958 sizeof (buf),
1959 0);
1960 if (0 >= got)
1961 {
1962 /* the TCP/IP socket has been closed */
1963 break;
1964 }
1965 if (0 < got)
1966 {
1967 if (0 != connecteduser_parse_received_websocket_stream (cu, buf,
1968 (size_t) got))
1969 {
1970 /* A websocket protocol error occurred */
1971 chat_removeuser (cu);
1972 if (0 == pthread_mutex_lock (&chat_mutex))
1973 {
1974 cu->disconnect = 1;
1975 pthread_cond_signal (&cu->wake_up_sender);
1976 pthread_mutex_unlock (&chat_mutex);
1977 pthread_join (pt, NULL);
1978 }
1979 struct MHD_UpgradeResponseHandle *urh = cu->urh;
1980 if (NULL != urh)
1981 {
1982 cu->urh = NULL;
1983 MHD_upgrade_action (urh,
1984 MHD_UPGRADE_ACTION_CLOSE);
1985 }
1986 pthread_cond_destroy (&cu->wake_up_sender);
1987 pthread_mutex_destroy (&cu->send_mutex);
1988 MHD_websocket_stream_free (cu->ws);
1989 free (cu->user_name);
1990 free (cu);
1991 return NULL;
1992 }
1993 }
1994 }
1995
1996 /* cleanup */
1997 chat_removeuser (cu);
1998 if (0 == pthread_mutex_lock (&chat_mutex))
1999 {
2000 cu->disconnect = 1;
2001 pthread_cond_signal (&cu->wake_up_sender);
2002 pthread_mutex_unlock (&chat_mutex);
2003 pthread_join (pt, NULL);
2004 }
2005 struct MHD_UpgradeResponseHandle *urh = cu->urh;
2006 if (NULL != urh)
2007 {
2008 cu->urh = NULL;
2009 MHD_upgrade_action (urh,
2010 MHD_UPGRADE_ACTION_CLOSE);
2011 }
2012 pthread_cond_destroy (&cu->wake_up_sender);
2013 pthread_mutex_destroy (&cu->send_mutex);
2014 MHD_websocket_stream_free (cu->ws);
2015 free (cu->user_name);
2016 free (cu);
2017
2018 return NULL;
2019 }
2020
2021
2022 /**
2023 * Function called after a protocol "upgrade" response was sent
2024 * successfully and the socket should now be controlled by some
2025 * protocol other than HTTP.
2026 *
2027 * Any data already received on the socket will be made available in
2028 * @e extra_in. This can happen if the application sent extra data
2029 * before MHD send the upgrade response. The application should
2030 * treat data from @a extra_in as if it had read it from the socket.
2031 *
2032 * Note that the application must not close() @a sock directly,
2033 * but instead use #MHD_upgrade_action() for special operations
2034 * on @a sock.
2035 *
2036 * Data forwarding to "upgraded" @a sock will be started as soon
2037 * as this function return.
2038 *
2039 * Except when in 'thread-per-connection' mode, implementations
2040 * of this function should never block (as it will still be called
2041 * from within the main event loop).
2042 *
2043 * @param cls closure, whatever was given to #MHD_create_response_for_upgrade().
2044 * @param connection original HTTP connection handle,
2045 * giving the function a last chance
2046 * to inspect the original HTTP request
2047 * @param con_cls last value left in `con_cls` of the `MHD_AccessHandlerCallback`
2048 * @param extra_in if we happened to have read bytes after the
2049 * HTTP header already (because the client sent
2050 * more than the HTTP header of the request before
2051 * we sent the upgrade response),
2052 * these are the extra bytes already read from @a sock
2053 * by MHD. The application should treat these as if
2054 * it had read them from @a sock.
2055 * @param extra_in_size number of bytes in @a extra_in
2056 * @param sock socket to use for bi-directional communication
2057 * with the client. For HTTPS, this may not be a socket
2058 * that is directly connected to the client and thus certain
2059 * operations (TCP-specific setsockopt(), getsockopt(), etc.)
2060 * may not work as expected (as the socket could be from a
2061 * socketpair() or a TCP-loopback). The application is expected
2062 * to perform read()/recv() and write()/send() calls on the socket.
2063 * The application may also call shutdown(), but must not call
2064 * close() directly.
2065 * @param urh argument for #MHD_upgrade_action()s on this @a connection.
2066 * Applications must eventually use this callback to (indirectly)
2067 * perform the close() action on the @a sock.
2068 */
2069 static void
upgrade_handler(void * cls,struct MHD_Connection * connection,void * con_cls,const char * extra_in,size_t extra_in_size,MHD_socket fd,struct MHD_UpgradeResponseHandle * urh)2070 upgrade_handler (void *cls,
2071 struct MHD_Connection *connection,
2072 void *con_cls,
2073 const char *extra_in,
2074 size_t extra_in_size,
2075 MHD_socket fd,
2076 struct MHD_UpgradeResponseHandle *urh)
2077 {
2078 struct ConnectedUser *cu;
2079 pthread_t pt;
2080 (void) cls; /* Unused. Silent compiler warning. */
2081 (void) connection; /* Unused. Silent compiler warning. */
2082 (void) con_cls; /* Unused. Silent compiler warning. */
2083
2084 /* This callback must return as soon as possible. */
2085
2086 /* allocate new connected user */
2087 cu = malloc (sizeof (struct ConnectedUser));
2088 if (NULL == cu)
2089 abort ();
2090 memset (cu, 0, sizeof (struct ConnectedUser));
2091 if (0 != extra_in_size)
2092 {
2093 cu->extra_in = malloc (extra_in_size);
2094 if (NULL == cu->extra_in)
2095 abort ();
2096 memcpy (cu->extra_in,
2097 extra_in,
2098 extra_in_size);
2099 }
2100 cu->extra_in_size = extra_in_size;
2101 cu->fd = fd;
2102 cu->urh = urh;
2103 cu->user_id = ++unique_user_id;
2104 cu->user_name = NULL;
2105 cu->user_name_len = 0;
2106
2107 /* create thread for the new connected user */
2108 if (0 != pthread_create (&pt,
2109 NULL,
2110 &connecteduser_receive_messages,
2111 cu))
2112 abort ();
2113 pthread_detach (pt);
2114 }
2115
2116
2117 /**
2118 * Function called by the MHD_daemon when the client tries to access a page.
2119 *
2120 * This is used to provide the main page
2121 * (in this example HTML + CSS + JavaScript is all in the same file)
2122 * and to initialize a websocket connection.
2123 * The rules for the initialization of a websocket connection
2124 * are listed near the URL check of "/ChatServerWebSocket".
2125 *
2126 * @param cls closure, whatever was given to #MHD_start_daemon().
2127 * @param connection The HTTP connection handle
2128 * @param url The requested URL
2129 * @param method The request method (typically "GET")
2130 * @param version The HTTP version
2131 * @param upload_data Given upload data for POST requests
2132 * @param upload_data_size The size of the upload data
2133 * @param ptr A pointer for request specific data
2134 * @return MHD_YES on success or MHD_NO on error.
2135 */
2136 static enum MHD_Result
access_handler(void * cls,struct MHD_Connection * connection,const char * url,const char * method,const char * version,const char * upload_data,size_t * upload_data_size,void ** ptr)2137 access_handler (void *cls,
2138 struct MHD_Connection *connection,
2139 const char *url,
2140 const char *method,
2141 const char *version,
2142 const char *upload_data,
2143 size_t *upload_data_size,
2144 void **ptr)
2145 {
2146 static int aptr;
2147 struct MHD_Response *response;
2148 int ret;
2149 (void) cls; /* Unused. Silent compiler warning. */
2150 (void) version; /* Unused. Silent compiler warning. */
2151 (void) upload_data; /* Unused. Silent compiler warning. */
2152 (void) upload_data_size; /* Unused. Silent compiler warning. */
2153
2154 if (0 != strcmp (method, "GET"))
2155 return MHD_NO; /* unexpected method */
2156 if (&aptr != *ptr)
2157 {
2158 /* do never respond on first call */
2159 *ptr = &aptr;
2160 return MHD_YES;
2161 }
2162 *ptr = NULL; /* reset when done */
2163 if (0 == strcmp (url, "/"))
2164 {
2165 /* Default page for visiting the server */
2166 struct MHD_Response *response = MHD_create_response_from_buffer (strlen (
2167 PAGE),
2168 PAGE,
2169 MHD_RESPMEM_PERSISTENT);
2170 ret = MHD_queue_response (connection,
2171 MHD_HTTP_OK,
2172 response);
2173 MHD_destroy_response (response);
2174 }
2175 else if (0 == strcmp (url, "/ChatServerWebSocket"))
2176 {
2177 /**
2178 * The path for the chat has been accessed.
2179 * For a valid WebSocket request, at least five headers are required:
2180 * 1. "Host: <name>"
2181 * 2. "Connection: Upgrade"
2182 * 3. "Upgrade: websocket"
2183 * 4. "Sec-WebSocket-Version: 13"
2184 * 5. "Sec-WebSocket-Key: <base64 encoded value>"
2185 * Values are compared in a case-insensitive manner.
2186 * Furthermore it must be a HTTP/1.1 or higher GET request.
2187 * See: https://tools.ietf.org/html/rfc6455#section-4.2.1
2188 *
2189 * To make this example portable we skip the Host check
2190 */
2191
2192 char is_valid = 1;
2193 const char *value = NULL;
2194 char sec_websocket_accept[29];
2195
2196 /* check whether an websocket upgrade is requested */
2197 if (0 != MHD_websocket_check_http_version (version))
2198 {
2199 is_valid = 0;
2200 }
2201 value = MHD_lookup_connection_value (connection,
2202 MHD_HEADER_KIND,
2203 MHD_HTTP_HEADER_CONNECTION);
2204 if (0 != MHD_websocket_check_connection_header (value))
2205 {
2206 is_valid = 0;
2207 }
2208 value = MHD_lookup_connection_value (connection,
2209 MHD_HEADER_KIND,
2210 MHD_HTTP_HEADER_UPGRADE);
2211 if (0 != MHD_websocket_check_upgrade_header (value))
2212 {
2213 is_valid = 0;
2214 }
2215 value = MHD_lookup_connection_value (connection,
2216 MHD_HEADER_KIND,
2217 MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
2218 if (0 != MHD_websocket_check_version_header (value))
2219 {
2220 is_valid = 0;
2221 }
2222 value = MHD_lookup_connection_value (connection,
2223 MHD_HEADER_KIND,
2224 MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
2225 if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
2226 {
2227 is_valid = 0;
2228 }
2229
2230 if (1 == is_valid)
2231 {
2232 /* create the response for upgrade */
2233 response = MHD_create_response_for_upgrade (&upgrade_handler,
2234 NULL);
2235
2236 /**
2237 * For the response we need at least the following headers:
2238 * 1. "Connection: Upgrade"
2239 * 2. "Upgrade: websocket"
2240 * 3. "Sec-WebSocket-Accept: <base64value>"
2241 * The value for Sec-WebSocket-Accept can be generated with MHD_websocket_create_accept_header.
2242 * It requires the value of the Sec-WebSocket-Key header of the request.
2243 * See also: https://tools.ietf.org/html/rfc6455#section-4.2.2
2244 */
2245 MHD_add_response_header (response,
2246 MHD_HTTP_HEADER_CONNECTION,
2247 "Upgrade");
2248 MHD_add_response_header (response,
2249 MHD_HTTP_HEADER_UPGRADE,
2250 "websocket");
2251 MHD_add_response_header (response,
2252 MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
2253 sec_websocket_accept);
2254 ret = MHD_queue_response (connection,
2255 MHD_HTTP_SWITCHING_PROTOCOLS,
2256 response);
2257 MHD_destroy_response (response);
2258 }
2259 else
2260 {
2261 /* return error page */
2262 struct MHD_Response *response = MHD_create_response_from_buffer (strlen (
2263 PAGE_INVALID_WEBSOCKET_REQUEST),
2264 PAGE_INVALID_WEBSOCKET_REQUEST,
2265 MHD_RESPMEM_PERSISTENT);
2266 ret = MHD_queue_response (connection,
2267 MHD_HTTP_BAD_REQUEST,
2268 response);
2269 MHD_destroy_response (response);
2270 }
2271 }
2272 else
2273 {
2274 struct MHD_Response *response = MHD_create_response_from_buffer (strlen (
2275 PAGE_NOT_FOUND),
2276 PAGE_NOT_FOUND,
2277 MHD_RESPMEM_PERSISTENT);
2278 ret = MHD_queue_response (connection,
2279 MHD_HTTP_NOT_FOUND,
2280 response);
2281 MHD_destroy_response (response);
2282 }
2283 return ret;
2284 }
2285
2286
2287 /**
2288 * The main routine for this example
2289 *
2290 * This starts the daemon and waits for a key hit.
2291 * After this it will shutdown the daemon.
2292 */
2293 int
main(int argc,char * const * argv)2294 main (int argc,
2295 char *const *argv)
2296 {
2297 (void) argc; /* Unused. Silent compiler warning. */
2298 (void) argv; /* Unused. Silent compiler warning. */
2299 struct MHD_Daemon *d;
2300
2301 if (0 != pthread_mutex_init (&chat_mutex, NULL))
2302 return 1;
2303
2304 #if USE_HTTPS == 1
2305 const char private_key[] = "TODO: Enter your key in PEM format here";
2306 const char certificate[] = "TODO: Enter your certificate in PEM format here";
2307 d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO
2308 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG
2309 | MHD_USE_TLS,
2310 443,
2311 NULL, NULL,
2312 &access_handler, NULL,
2313 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
2314 MHD_OPTION_HTTPS_MEM_KEY, private_key,
2315 MHD_OPTION_HTTPS_MEM_CERT, certificate,
2316 MHD_OPTION_END);
2317 #else
2318 d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO
2319 | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG,
2320 80,
2321 NULL, NULL,
2322 &access_handler, NULL,
2323 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120,
2324 MHD_OPTION_END);
2325 #endif
2326
2327 if (NULL == d)
2328 return 1;
2329 (void) getc (stdin);
2330
2331 if (0 == pthread_mutex_lock (&chat_mutex))
2332 {
2333 disconnect_all = 1;
2334 for (size_t i = 0; i < user_count; ++i)
2335 pthread_cond_signal (&users[i]->wake_up_sender);
2336 pthread_mutex_unlock (&chat_mutex);
2337 }
2338 sleep (2);
2339 if (0 == pthread_mutex_lock (&chat_mutex))
2340 {
2341 for (size_t i = 0; i < user_count; ++i)
2342 {
2343 struct MHD_UpgradeResponseHandle *urh = users[i]->urh;
2344 if (NULL != urh)
2345 {
2346 users[i]->urh = NULL;
2347 MHD_upgrade_action (users[i]->urh,
2348 MHD_UPGRADE_ACTION_CLOSE);
2349 }
2350 }
2351 pthread_mutex_unlock (&chat_mutex);
2352 }
2353 sleep (2);
2354
2355 /* usually we should wait here in a safe way for all threads to disconnect, */
2356 /* but we skip this in the example */
2357
2358 pthread_mutex_destroy (&chat_mutex);
2359
2360 MHD_stop_daemon (d);
2361
2362 return 0;
2363 }
2364