diff options
author | Christian Grothoff <christian@grothoff.org> | 2021-04-26 14:33:55 +0200 |
---|---|---|
committer | Christian Grothoff <christian@grothoff.org> | 2021-04-26 14:33:55 +0200 |
commit | 450b8688041345e6b6e8b4925316ee6131a6cceb (patch) | |
tree | c7bb2c471978641bd20154b8fb63cc4ce2826d25 | |
parent | c488cadbc17253178c418f52ba9a321816f0080b (diff) | |
download | libmicrohttpd-450b8688041345e6b6e8b4925316ee6131a6cceb.tar.gz libmicrohttpd-450b8688041345e6b6e8b4925316ee6131a6cceb.zip |
add David's WS example
-rw-r--r-- | src/Makefile.am | 9 | ||||
-rw-r--r-- | src/examples/Makefile.am | 11 | ||||
-rw-r--r-- | src/examples/pthread_windows.c | 252 | ||||
-rw-r--r-- | src/examples/pthread_windows.h | 46 | ||||
-rw-r--r-- | src/examples/websocket_chatserver_example.c | 2392 |
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 | ||
11 | SUBDIRS = include microhttpd $(curltests) $(zzuftests) . | 11 | SUBDIRS = include microhttpd $(curltests) $(zzuftests) . |
12 | 12 | ||
13 | if BUILD_EXAMPLES | ||
14 | SUBDIRS += examples | ||
15 | endif | ||
16 | |||
17 | # Finally (last!) also build experimental lib... | 13 | # Finally (last!) also build experimental lib... |
18 | if HAVE_EXPERIMENTAL | 14 | if HAVE_EXPERIMENTAL |
19 | SUBDIRS += microhttpd_ws lib | 15 | SUBDIRS += microhttpd_ws lib |
20 | endif | 16 | endif |
21 | 17 | ||
18 | if BUILD_EXAMPLES | ||
19 | SUBDIRS += examples | ||
20 | endif | ||
21 | |||
22 | |||
22 | EXTRA_DIST = \ | 23 | EXTRA_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 | ||
33 | if HAVE_EXPERIMENTAL | ||
34 | noinst_PROGRAMS += \ | ||
35 | websocket_chatserver_example | ||
36 | endif | ||
37 | |||
33 | if MHD_HAVE_EPOLL | 38 | if MHD_HAVE_EPOLL |
34 | noinst_PROGRAMS += \ | 39 | noinst_PROGRAMS += \ |
35 | suspend_resume_epoll | 40 | suspend_resume_epoll |
@@ -123,6 +128,12 @@ chunked_example_SOURCES = \ | |||
123 | chunked_example_LDADD = \ | 128 | chunked_example_LDADD = \ |
124 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | 129 | $(top_builddir)/src/microhttpd/libmicrohttpd.la |
125 | 130 | ||
131 | websocket_chatserver_example_SOURCES = \ | ||
132 | websocket_chatserver_example.c | ||
133 | websocket_chatserver_example_LDADD = \ | ||
134 | $(top_builddir)/src/microhttpd_ws/libmicrohttpd_ws.la \ | ||
135 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | ||
136 | |||
126 | demo_SOURCES = \ | 137 | demo_SOURCES = \ |
127 | demo.c | 138 | demo.c |
128 | demo_CFLAGS = \ | 139 | demo_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 | |||
4 | struct _pthread_t | ||
5 | { | ||
6 | HANDLE thread; | ||
7 | }; | ||
8 | |||
9 | struct _pthread_cond_t | ||
10 | { | ||
11 | HANDLE event; | ||
12 | }; | ||
13 | |||
14 | struct _pthread_mutex_t | ||
15 | { | ||
16 | HANDLE mutex; | ||
17 | }; | ||
18 | |||
19 | struct StdCallThread | ||
20 | { | ||
21 | void *(__cdecl *start) (void *); | ||
22 | void *arg; | ||
23 | }; | ||
24 | |||
25 | DWORD WINAPI | ||
26 | ThreadProc (LPVOID lpParameter) | ||
27 | { | ||
28 | struct StdCallThread st = *((struct StdCallThread*) lpParameter); | ||
29 | free (lpParameter); | ||
30 | st.start (st.arg); | ||
31 | return 0; | ||
32 | } | ||
33 | |||
34 | |||
35 | int | ||
36 | pthread_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 | |||
67 | int | ||
68 | pthread_detach (pthread_t pt) | ||
69 | { | ||
70 | if (pt) | ||
71 | { | ||
72 | CloseHandle (pt->thread); | ||
73 | free (pt); | ||
74 | } | ||
75 | return 0; | ||
76 | } | ||
77 | |||
78 | |||
79 | int | ||
80 | pthread_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 | |||
98 | int | ||
99 | pthread_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 | |||
118 | int | ||
119 | pthread_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 | |||
134 | int | ||
135 | pthread_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 | |||
153 | int | ||
154 | pthread_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 | |||
168 | int | ||
169 | pthread_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 | |||
188 | int | ||
189 | pthread_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 | |||
203 | int | ||
204 | pthread_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 | |||
233 | int | ||
234 | pthread_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 | |||
248 | int | ||
249 | pthread_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 | |||
4 | struct _pthread_t; | ||
5 | struct _pthread_cond_t; | ||
6 | struct _pthread_mutex_t; | ||
7 | |||
8 | typedef struct _pthread_t *pthread_t; | ||
9 | typedef struct _pthread_cond_t *pthread_cond_t; | ||
10 | typedef 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 | |||
15 | int pthread_create (pthread_t * pt, | ||
16 | const void *attr, | ||
17 | void *(__cdecl * start)(void *), | ||
18 | void *arg); | ||
19 | |||
20 | int pthread_detach (pthread_t pt); | ||
21 | |||
22 | int pthread_join (pthread_t pt, | ||
23 | void **value_ptr); | ||
24 | |||
25 | int pthread_mutex_init (pthread_mutex_t *mutex, | ||
26 | const void *attr); | ||
27 | |||
28 | int pthread_mutex_destroy (pthread_mutex_t *mutex); | ||
29 | |||
30 | int pthread_mutex_lock (pthread_mutex_t *mutex); | ||
31 | |||
32 | int pthread_mutex_unlock (pthread_mutex_t *mutex); | ||
33 | |||
34 | int pthread_cond_init (pthread_cond_t *cond, | ||
35 | const void *attr); | ||
36 | |||
37 | int pthread_cond_destroy (pthread_cond_t *cond); | ||
38 | |||
39 | int pthread_cond_wait (pthread_cond_t *cond, | ||
40 | pthread_mutex_t *mutex); | ||
41 | |||
42 | int pthread_cond_signal (pthread_cond_t *cond); | ||
43 | |||
44 | int 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 | */ | ||
619 | struct 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 | */ | ||
663 | struct 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) */ | ||
677 | size_t unique_user_id = 0; | ||
678 | |||
679 | /* the chat data (users and messages; may be accessed by all threads, but is protected by mutex) */ | ||
680 | pthread_mutex_t chat_mutex; | ||
681 | struct ConnectedUser**users = NULL; | ||
682 | size_t user_count = 0; | ||
683 | struct Message**messages = NULL; | ||
684 | size_t message_count = 0; | ||
685 | /* specifies whether all websockets must close (1) or not (0) */ | ||
686 | volatile int disconnect_all = 0; | ||
687 | /* a counter for cleaning old messages (each 10 messages we will try to clean the list */ | ||
688 | int clean_count = 0; | ||
689 | #define CLEANUP_LIMIT 10 | ||
690 | |||
691 | /** | ||
692 | * Change socket to blocking. | ||
693 | * | ||
694 | * @param fd the socket to manipulate | ||
695 | */ | ||
696 | static void | ||
697 | make_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 | */ | ||
724 | static void | ||
725 | send_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 | */ | ||
771 | static int | ||
772 | chat_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 | */ | ||
843 | static int | ||
844 | chat_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 | */ | ||
925 | static int | ||
926 | chat_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 | */ | ||
995 | static int | ||
996 | chat_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 | */ | ||
1058 | static int | ||
1059 | chat_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 | */ | ||
1126 | static int | ||
1127 | connecteduser_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 | */ | ||
1588 | static void * | ||
1589 | connecteduser_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 | */ | ||
1738 | static void * | ||
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 | 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 | */ | ||
2068 | static void | ||
2069 | upgrade_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 | */ | ||
2135 | static int | ||
2136 | access_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 | */ | ||
2322 | int | ||
2323 | main (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 | } | ||