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