libmicrohttpd

HTTP/1.x server C library (MHD 1.x, stable)
Log | Files | Refs | Submodules | README | LICENSE

commit 450b8688041345e6b6e8b4925316ee6131a6cceb
parent c488cadbc17253178c418f52ba9a321816f0080b
Author: Christian Grothoff <christian@grothoff.org>
Date:   Mon, 26 Apr 2021 14:33:55 +0200

add David's WS example

Diffstat:
Msrc/Makefile.am | 9+++++----
Msrc/examples/Makefile.am | 11+++++++++++
Asrc/examples/pthread_windows.c | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/pthread_windows.h | 46++++++++++++++++++++++++++++++++++++++++++++++
Asrc/examples/websocket_chatserver_example.c | 2392+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 2706 insertions(+), 4 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am @@ -10,15 +10,16 @@ endif SUBDIRS = include microhttpd $(curltests) $(zzuftests) . -if BUILD_EXAMPLES -SUBDIRS += examples -endif - # Finally (last!) also build experimental lib... if HAVE_EXPERIMENTAL SUBDIRS += microhttpd_ws lib endif +if BUILD_EXAMPLES +SUBDIRS += examples +endif + + EXTRA_DIST = \ datadir/cert-and-key.pem \ datadir/cert-and-key-for-wireshark.pem diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am @@ -30,6 +30,11 @@ noinst_PROGRAMS = \ fileserver_example_external_select \ refuse_post_example +if HAVE_EXPERIMENTAL +noinst_PROGRAMS += \ + websocket_chatserver_example +endif + if MHD_HAVE_EPOLL noinst_PROGRAMS += \ suspend_resume_epoll @@ -123,6 +128,12 @@ chunked_example_SOURCES = \ chunked_example_LDADD = \ $(top_builddir)/src/microhttpd/libmicrohttpd.la +websocket_chatserver_example_SOURCES = \ + websocket_chatserver_example.c +websocket_chatserver_example_LDADD = \ + $(top_builddir)/src/microhttpd_ws/libmicrohttpd_ws.la \ + $(top_builddir)/src/microhttpd/libmicrohttpd.la + demo_SOURCES = \ demo.c demo_CFLAGS = \ diff --git a/src/examples/pthread_windows.c b/src/examples/pthread_windows.c @@ -0,0 +1,252 @@ +#include "pthread_windows.h" +#include <Windows.h> + +struct _pthread_t +{ + HANDLE thread; +}; + +struct _pthread_cond_t +{ + HANDLE event; +}; + +struct _pthread_mutex_t +{ + HANDLE mutex; +}; + +struct StdCallThread +{ + void *(__cdecl *start) (void *); + void *arg; +}; + +DWORD WINAPI +ThreadProc (LPVOID lpParameter) +{ + struct StdCallThread st = *((struct StdCallThread*) lpParameter); + free (lpParameter); + st.start (st.arg); + return 0; +} + + +int +pthread_create (pthread_t *pt, + const void *attr, + void *(__cdecl *start)(void *), + void *arg) +{ + pthread_t pt_ = (pthread_t) malloc (sizeof(struct _pthread_t)); + if (NULL == pt_) + return 1; + struct StdCallThread *sct; + sct = (struct StdCallThread*) malloc (sizeof(struct StdCallThread)); + if (NULL == sct) + { + free (pt_); + return 1; + } + + sct->start = start; + sct->arg = arg; + pt_->thread = CreateThread (NULL, 0, ThreadProc, sct, 0, NULL); + if (NULL == pt_->thread) + { + free (sct); + free (pt_); + return 1; + } + *pt = pt_; + + return 0; +} + + +int +pthread_detach (pthread_t pt) +{ + if (pt) + { + CloseHandle (pt->thread); + free (pt); + } + return 0; +} + + +int +pthread_join (pthread_t pt, + void **value_ptr) +{ + if (NULL == pt) + return 1; + + if (value_ptr) + { + *value_ptr = NULL; + } + WaitForSingleObject (pt->thread, INFINITE); + CloseHandle (pt->thread); + free (pt); + + return 0; +} + + +int +pthread_mutex_init (pthread_mutex_t *mutex, + const void *attr) +{ + pthread_mutex_t mutex_ = (pthread_mutex_t) malloc (sizeof(struct + _pthread_mutex_t)); + if (NULL == mutex_) + return 1; + mutex_->mutex = CreateMutex (NULL, FALSE, NULL); + if (NULL == mutex_->mutex) + { + free (mutex_); + return 1; + } + *mutex = mutex_; + + return 0; +} + + +int +pthread_mutex_destroy (pthread_mutex_t *mutex) +{ + if (NULL == mutex) + return 1; + if ((NULL == *mutex) || (PTHREAD_MUTEX_INITIALIZER == *mutex)) + return 0; + + CloseHandle ((*mutex)->mutex); + free (*mutex); + *mutex = NULL; + + return 0; +} + + +int +pthread_mutex_lock (pthread_mutex_t *mutex) +{ + if (NULL == mutex) + return 1; + if (NULL == *mutex) + return 1; + if (PTHREAD_MUTEX_INITIALIZER == *mutex) + { + int ret = pthread_mutex_init (mutex, NULL); + if (0 != ret) + return ret; + } + if (WAIT_OBJECT_0 != WaitForSingleObject ((*mutex)->mutex, INFINITE)) + return 1; + return 0; +} + + +int +pthread_mutex_unlock (pthread_mutex_t *mutex) +{ + if (NULL == mutex) + return 1; + if ((NULL == *mutex) || (PTHREAD_MUTEX_INITIALIZER == *mutex)) + return 1; + + if (0 == ReleaseMutex ((*mutex)->mutex)) + return 1; + + return 0; +} + + +int +pthread_cond_init (pthread_cond_t *cond, + const void *attr) +{ + pthread_cond_t cond_ = (pthread_cond_t) malloc (sizeof(struct + _pthread_cond_t)); + if (NULL == cond_) + return 1; + cond_->event = CreateEvent (NULL, FALSE, FALSE, NULL); + if (NULL == cond_->event) + { + free (cond_); + return 1; + } + *cond = cond_; + + return 0; +} + + +int +pthread_cond_destroy (pthread_cond_t *cond) +{ + if (NULL == cond) + return 1; + if ((NULL == *cond) || (PTHREAD_COND_INITIALIZER == *cond)) + return 1; + + CloseHandle ((*cond)->event); + free (*cond); + + return 0; +} + + +int +pthread_cond_wait (pthread_cond_t *cond, + pthread_mutex_t *mutex) +{ + if ((NULL == cond) || (NULL == mutex)) + return 1; + if ((NULL == *cond) || (NULL == *mutex)) + return 1; + if (PTHREAD_COND_INITIALIZER == *cond) + { + int ret = pthread_cond_init (cond, NULL); + if (0 != ret) + return ret; + } + if (PTHREAD_MUTEX_INITIALIZER == *mutex) + { + int ret = pthread_mutex_init (mutex, NULL); + if (0 != ret) + return ret; + } + ReleaseMutex ((*mutex)->mutex); + if (WAIT_OBJECT_0 != WaitForSingleObject ((*cond)->event, INFINITE)) + return 1; + if (WAIT_OBJECT_0 != WaitForSingleObject ((*mutex)->mutex, INFINITE)) + return 1; + + return 0; +} + + +int +pthread_cond_signal (pthread_cond_t *cond) +{ + if (NULL == cond) + return 1; + if ((NULL == *cond) || (PTHREAD_COND_INITIALIZER == *cond)) + return 1; + + if (0 == SetEvent ((*cond)->event)) + return 1; + + return 0; +} + + +int +pthread_cond_broadcast (pthread_cond_t *cond) +{ + return pthread_cond_signal (cond); +} diff --git a/src/examples/pthread_windows.h b/src/examples/pthread_windows.h @@ -0,0 +1,46 @@ +#ifndef pthread_windows_H +#define pthread_windows_H + +struct _pthread_t; +struct _pthread_cond_t; +struct _pthread_mutex_t; + +typedef struct _pthread_t *pthread_t; +typedef struct _pthread_cond_t *pthread_cond_t; +typedef struct _pthread_mutex_t *pthread_mutex_t; + +#define PTHREAD_MUTEX_INITIALIZER ((pthread_mutex_t)(size_t) -1) +#define PTHREAD_COND_INITIALIZER ((pthread_cond_t)(size_t) -1) + +int pthread_create (pthread_t * pt, + const void *attr, + void *(__cdecl * start)(void *), + void *arg); + +int pthread_detach (pthread_t pt); + +int pthread_join (pthread_t pt, + void **value_ptr); + +int pthread_mutex_init (pthread_mutex_t *mutex, + const void *attr); + +int pthread_mutex_destroy (pthread_mutex_t *mutex); + +int pthread_mutex_lock (pthread_mutex_t *mutex); + +int pthread_mutex_unlock (pthread_mutex_t *mutex); + +int pthread_cond_init (pthread_cond_t *cond, + const void *attr); + +int pthread_cond_destroy (pthread_cond_t *cond); + +int pthread_cond_wait (pthread_cond_t *cond, + pthread_mutex_t *mutex); + +int pthread_cond_signal (pthread_cond_t *cond); + +int pthread_cond_broadcast (pthread_cond_t *cond); + +#endif // !pthread_windows_H diff --git a/src/examples/websocket_chatserver_example.c b/src/examples/websocket_chatserver_example.c @@ -0,0 +1,2392 @@ +/* + This file is part of libmicrohttpd + Copyright (C) 2021 David Gausmann (and other contributing authors) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +/** + * @file websocket_chatserver_example.c + * @brief example for how to use websockets + * @author David Gausmann + * + * Access the HTTP server with your webbrowser. + * The webbrowser must support JavaScript and WebSockets. + * The websocket access will be initiated via the JavaScript on the website. + * You will get an example chat room, which uses websockets. + * For testing with multiple users, just start several instances of your webbrowser. + * + */ + +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#define _CRT_SECURE_NO_WARNINGS +#endif +#include "platform.h" +#include <microhttpd.h> +#include <microhttpd_ws.h> +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +/* + Workaround for Windows systems, because the NuGet version of pthreads is buggy. + This is a simple replacement. It doesn't offer all functions of pthread, but + has everything, what is required for this example. + See: https://github.com/coapp-packages/pthreads/issues/2 +*/ +#include "pthread_windows.h" +#else +/* + On Unix systems we can use pthread. +*/ +#include <pthread.h> +#endif + + +/* + * Specifiy with this constant whether or not to use HTTPS. + * 0 means HTTP, 1 means HTTPS. + * Please note that you must enter a valid private key/certificate pair + * in the main procedure to running this example with HTTPS. + */ +#define USE_HTTPS 0 + +/** + * This is the main website. + * The HTML, CSS and JavaScript code is all in there. + */ +#define PAGE \ + "<!DOCTYPE html>" \ + "<html>" \ + "<head>" \ + "<meta charset='UTF-8'>" \ + "<title>libmicrohttpd websocket chatserver demo</title>" \ + "<style>" \ + " html" \ + " {\n" \ + " font: 11pt sans-serif;\n" \ + " }\n" \ + " html, body" \ + " {\n" \ + " margin: 0;\n" \ + " width: 100vw;\n" \ + " height: 100vh;\n" \ + " }\n" \ + " div#Chat\n" \ + " {\n" \ + " display: flex;\n" \ + " flex-direction: row;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput\n" \ + " {\n" \ + " flex: 1 1 auto;" \ + " display: flex;\n" \ + " flex-direction: column;\n" \ + " width: calc(100vw - 20em);\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages\n" \ + " {\n" \ + " flex: 1 1 auto;" \ + " display: flex;\n" \ + " flex-direction: column;\n" \ + " justify-content: flex-start;\n" \ + " box-sizing: border-box;\n" \ + " overflow-y: scroll;\n" \ + " border: 2pt solid #888;\n" \ + " background-color: #eee;\n" \ + " height: calc(100vh - 2em);\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages > div.Message > span\n" \ + " {\n" \ + " white-space: pre\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages > div.Message.error > span\n" \ + " {\n" \ + " color: red;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages > div.Message.system > span\n" \ + " {\n" \ + " color: green;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages > div.Message.moderator > span\n" \ + " {\n" \ + " color: #808000;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div#Messages > div.Message.private > span\n" \ + " {\n" \ + " color: blue;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div.Input\n" \ + " {\n" \ + " flex: 0 0 auto;" \ + " height: 2em;" \ + " display: flex;" \ + " flex-direction: row;" \ + " background-color: #eee;\n" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div.Input > input#InputMessage\n" \ + " {\n" \ + " flex: 1 1 auto;" \ + " }\n" \ + " div#Chat > div.MessagesAndInput > div.Input > button\n" \ + " {\n" \ + " flex: 0 0 auto;" \ + " width: 5em;" \ + " margin-left: 4pt;" \ + " }\n" \ + " div#Chat > div#Users\n" \ + " {\n" \ + " flex: 0 0 auto;" \ + " width: 20em;" \ + " display: flex;\n" \ + " flex-direction: column;\n" \ + " justify-content: flex-start;\n" \ + " box-sizing: border-box;\n" \ + " overflow-y: scroll;\n" \ + " border: 2pt solid #888;\n" \ + " background-color: #eee;\n" \ + " }\n" \ + " div#Chat > div#Users > div\n" \ + " {\n" \ + " cursor: pointer;\n" \ + " user-select: none;\n" \ + " -webkit-user-select: none;\n" \ + " }\n" \ + " div#Chat > div#Users > div.selected\n" \ + " {\n" \ + " background-color: #7bf;\n" \ + " }\n" \ + "</style>" \ + "<script>\n" \ + " 'use strict'\n;" \ + "\n" \ + " let baseUrl;\n" \ + " let socket;\n" \ + " let connectedUsers = new Map();\n" \ + "\n" \ + " window.addEventListener('load', window_onload);\n" \ + "\n" \ + " /**\n" \ + " This is the main procedure which initializes the chat and connects the first socket\n" \ + " */\n" \ + " function window_onload(event)\n" \ + " {\n" \ + " // Determine the base url (for http:// this is ws:// for https:// this must be wss://)\n" \ + // " baseUrl = 'ws' + (window.location.protocol === 'https:' ? 's' : '') + '://' + window.location.host + '/ChatServerWebSocket';\n" \ + // " chat_generate();\n" \ + // " chat_connect();\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This function generates the chat using DOM\n" \ + // " */\n" \ + // " function chat_generate()\n" \ + // " {\n" \ + // " document.body.innerHTML = '';\n" \ + // " let chat = document.createElement('div');\n" \ + // " document.body.appendChild(chat);\n" \ + // " chat.id = 'Chat';\n" \ + // " let messagesAndInput = document.createElement('div');\n" \ + // " chat.appendChild(messagesAndInput);\n" \ + // " messagesAndInput.classList.add('MessagesAndInput');\n" \ + // " let messages = document.createElement('div');\n" \ + // " messagesAndInput.appendChild(messages);\n" \ + // " messages.id = 'Messages';\n" \ + // " let input = document.createElement('div');\n" \ + // " messagesAndInput.appendChild(input);\n" \ + // " input.classList.add('Input');\n" \ + // " let inputMessage = document.createElement('input');\n" \ + // " input.appendChild(inputMessage);\n" \ + // " inputMessage.type = 'text';\n" \ + // " inputMessage.id = 'InputMessage';\n" \ + // " inputMessage.disabled = true;\n" \ + // " inputMessage.addEventListener('keydown', chat_onKeyDown);\n" \ + // " let inputMessageSend = document.createElement('button');\n" \ + // " input.appendChild(inputMessageSend);\n" \ + // " inputMessageSend.id = 'InputMessageButton';\n" \ + // " inputMessageSend.disabled = true;\n" \ + // " inputMessageSend.innerText = 'send';\n" \ + // " inputMessageSend.addEventListener('click', chat_onSendClicked);\n" \ + // " let inputImage = document.createElement('input');\n" \ + // " input.appendChild(inputImage);\n" \ + // " inputImage.id = 'InputImage';\n" \ + // " inputImage.type = 'file';\n" \ + // " inputImage.accept = 'image/*';\n" \ + // " inputImage.style.display = 'none';\n" \ + // " inputImage.addEventListener('change', chat_onImageSelected);\n" \ + // " let inputImageButton = document.createElement('button');\n" \ + // " input.appendChild(inputImageButton);\n" \ + // " inputImageButton.id = 'InputImageButton';\n" \ + // " inputImageButton.disabled = true;\n" \ + // " inputImageButton.innerText = 'image';\n" \ + // " inputImageButton.addEventListener('click', chat_onImageClicked);\n" \ + // " let users = document.createElement('div');\n" \ + // " chat.appendChild(users);\n" \ + // " users.id = 'Users';\n" \ + // " users.addEventListener('click', chat_onUserClicked);\n" \ + // " let allUsers = document.createElement('div');\n" \ + // " users.appendChild(allUsers);\n" \ + // " allUsers.classList.add('selected');\n" \ + // " allUsers.innerText = '<everyone>';\n" \ + // " allUsers.setAttribute('data-user', '0');\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This function creates and connects a WebSocket\n" \ + // " */\n" \ + // " function chat_connect()\n" \ + // " {\n" \ + // " chat_addMessage(`Connecting to libmicrohttpd chat server demo (${baseUrl})...`, { type: 'system' });\n" \ + // " socket = new WebSocket(baseUrl);\n" \ + // " socket.binaryType = 'arraybuffer';\n" \ + // " socket.onopen = socket_onopen;\n" \ + // " socket.onclose = socket_onclose;\n" \ + // " socket.onerror = socket_onerror;\n" \ + // " socket.onmessage = socket_onmessage;\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This function adds new text to the chat list\n" \ + // " */\n" \ + // " function chat_addMessage(text, options)\n" \ + // " {\n" \ + // " let type = options && options.type || 'regular';\n" \ + // " if(!/^(?:regular|system|error|private|moderator)$/.test(type))\n" \ + // " type = 'regular';\n" \ + // " let message = document.createElement('div');\n" \ + // " message.classList.add('Message');\n" \ + // " message.classList.add(type);\n" \ + // " if(typeof(text) === 'string')\n" \ + // " {\n" \ + // " let content = document.createElement('span');\n" \ + // " message.appendChild(content);\n" \ + // " if(options && options.from)\n" \ + // " content.innerText = `${options.from}: ${text}`;\n" \ + // " else\n" \ + // " content.innerText = text;\n" \ + // " if(options && options.reconnect)\n" \ + // " {\n" \ + // " let span = document.createElement('span');\n" \ + // " span.appendChild(document.createTextNode(' ('));\n" \ + // " let reconnect = document.createElement('a');\n" \ + // " reconnect.href = 'javascript:chat_connect()';\n" \ + // " reconnect.innerText = 'reconnect';\n" \ + // " span.appendChild(reconnect);\n" \ + // " span.appendChild(document.createTextNode(')'));\n" \ + // " message.appendChild(span);\n" \ + // " }\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " let content = document.createElement('span');\n" \ + // " message.appendChild(content);\n" \ + // " if(options && options.from)\n" \ + // " {\n" \ + // " content.innerText = `${options.from}:\\n`;\n" \ + // " }\n" \ + // " if(options && options.pictureType && text instanceof Uint8Array)\n" \ + // " {\n" \ + // " let img = document.createElement('img');\n" \ + // " content.appendChild(img);\n" \ + // " img.src = URL.createObjectURL(new Blob([ text.buffer ], { type: options.pictureType }));\n" \ + // " }\n" \ + // " }\n" \ + // " document.getElementById('Messages').appendChild(message);\n" \ + // " message.scrollIntoView();\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is a keydown event handler, which allows that you can just press enter instead of clicking the 'send' button\n" \ + // " */\n" \ + // " function chat_onKeyDown(event)\n" \ + // " {\n" \ + // " if(event.key == 'Enter')\n" \ + // " chat_onSendClicked();\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the code to send a message or command, when clicking the 'send' button\n" \ + // " */\n" \ + // " function chat_onSendClicked(event)\n" \ + // " {\n" \ + // " let message = document.getElementById('InputMessage').value;\n" \ + // " if(message.length == 0)\n" \ + // " return;\n" \ + // " if(message.substr(0, 1) == '/')\n" \ + // " {\n" \ + // " // command\n" \ + // " let match;\n" \ + // " if(/^\\/disconnect\\s*$/.test(message))\n" \ + // " {\n" \ + // " socket.close(1000);\n" \ + // " }\n" \ + // " else if((match = /^\\/m\\s+(\\S+)\\s+/.exec(message)))\n" \ + // " {\n" \ + // " message = message.substr(match[0].length);\n" \ + // " let userId = chat_getUserIdByName(match[1]);\n" \ + // " if(userId !== null)\n" \ + // " {\n" \ + // " socket.send(`private|${userId}|${message}`);\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " chat_addMessage(`Unknown user \"${match[1]}\" for private message: ${message}`, { type: 'error' });\n" \ + // " }\n" \ + // " }\n" \ + // " else if((match = /^\\/ping\\s+(\\S+)\\s*$/.exec(message)))\n" \ + // " {\n" \ + // " let userId = chat_getUserIdByName(match[1]);\n" \ + // " if(userId !== null)\n" \ + // " {\n" \ + // " socket.send(`ping|${userId}|`);\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " chat_addMessage(`Unknown user \"${match[1]}\" for ping`, { type: 'error' });\n" \ + // " }\n" \ + // " }\n" \ + // " else if((match = /^\\/name\\s+(\\S+)\\s*$/.exec(message)))\n" \ + // " {\n" \ + // " socket.send(`name||${match[1]}`);\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " chat_addMessage(`Unsupported command or invalid syntax: ${message}`, { type: 'error' });\n" \ + // " }\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " // regular chat message to the selected user\n" \ + // " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ + // " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ + // " if(selectedUserId == 0)\n" \ + // " socket.send(`||${message}`);\n" \ + // " else\n" \ + // " socket.send(`private|${selectedUserId}|${message}`);\n" \ + // " }\n" \ + // " document.getElementById('InputMessage').value = '';\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the user hits the 'image' button\n" \ + // " */\n" \ + // " function chat_onImageClicked(event)\n" \ + // " {\n" \ + // " document.getElementById('InputImage').click();\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the user selected an image.\n" \ + // " The image will be read with the FileReader (allowed in web, because the user selected the file).\n" \ + // " */\n" \ + // " function chat_onImageSelected(event)\n" \ + // " {\n" \ + // " let file = event.target.files[0];\n" \ + // " if(!file || !/^image\\//.test(file.type))\n" \ + // " return;\n" \ + // " let selectedUser = document.querySelector('div#Users > div.selected');\n" \ + // " let selectedUserId = parseInt(selectedUser.getAttribute('data-user') || '0', 10);\n" \ + // " let reader = new FileReader();\n" \ + // " reader.onload = function(event) {\n" \ + // " chat_onImageRead(event, file.type, selectedUserId);\n" \ + // " };\n" \ + // " reader.readAsArrayBuffer(file);\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the user selected image has been read.\n" \ + // " This will add our chat protocol prefix and send it via the websocket.\n" \ + // " */\n" \ + // " function chat_onImageRead(event, fileType, selectedUserId)\n" \ + // " {\n" \ + // " let encoder = new TextEncoder();\n" \ + // " let prefix = ((selectedUserId == 0 ? '||' : `private|${selectedUserId}|`) + fileType + '|');\n" \ + // " prefix = encoder.encode(prefix);\n" \ + // " let byteData = new Uint8Array(event.target.result);\n" \ + // " let totalLength = prefix.length + byteData.length;\n" \ + // " let resultByteData = new Uint8Array(totalLength);\n" \ + // " resultByteData.set(prefix, 0);\n" \ + // " resultByteData.set(byteData, prefix.length);\n" \ + // " socket.send(resultByteData);\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the user clicked a name in the user list.\n" \ + // " This is useful to send private messages or images without needing to add the /m prefix.\n" \ + // " */\n" \ + // " function chat_onUserClicked(event, selectedUserId)\n" \ + // " {\n" \ + // " let newSelected = event.target.closest('div#Users > div');\n" \ + // " if(newSelected === null)\n" \ + // " return;\n" \ + // " for(let div of this.querySelectorAll(':scope > div.selected'))\n" \ + // " div.classList.remove('selected');\n" \ + // " newSelected.classList.add('selected');\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This functions returns the current id of a user identified by its name.\n" \ + // " */\n" \ + // " function chat_getUserIdByName(name)\n" \ + // " {\n" \ + // " let nameUpper = name.toUpperCase();\n" \ + // " for(let pair of connectedUsers)\n" \ + // " {\n" \ + // " if(pair[1].toUpperCase() == nameUpper)\n" \ + // " return pair[0];\n" \ + // " }\n" \ + // " return null;\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This functions clears the entire user list (needed for reconnecting).\n" \ + // " */\n" \ + // " function chat_clearUserList()\n" \ + // " {\n" \ + // " let users = document.getElementById('Users');\n" \ + // " for(let div of users.querySelectorAll(':scope > div'))\n" \ + // " {\n" \ + // " if(div.getAttribute('data-user') === '0')\n" \ + // " {\n" \ + // " div.classList.add('selected');\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " div.parentNode.removeChild(div);\n" \ + // " }\n" \ + // " }\n" \ + // " return null;\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the socket has established a connection.\n" \ + // " This will initialize an empty chat and enable the controls.\n" \ + // " */\n" \ + // " function socket_onopen(event)\n" \ + // " {\n" \ + // " connectedUsers.clear();\n" \ + // " chat_clearUserList();\n" \ + // " chat_addMessage('Connected!', { type: 'system' });\n" \ + // " document.getElementById('InputMessage').disabled = false;\n" \ + // " document.getElementById('InputMessageButton').disabled = false;\n" \ + // " document.getElementById('InputImageButton').disabled = false;\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the socket has been closed.\n" \ + // " This will lock the controls.\n" \ + // " */\n" \ + // " function socket_onclose(event)\n" \ + // " {\n" \ + // " chat_addMessage('Connection closed!', { type: 'system', reconnect: true });\n" \ + // " document.getElementById('InputMessage').disabled = true;\n" \ + // " document.getElementById('InputMessageButton').disabled = true;\n" \ + // " document.getElementById('InputImageButton').disabled = true;\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the socket reported an error.\n" \ + // " This will just make an output.\n" \ + // " In the web browser console (F12 on many browsers) will show you more detailed error information.\n" \ + // " */\n" \ + // " function socket_onerror(event)\n" \ + // " {\n" \ + // " console.error('WebSocket error reported: ', event);\n" \ + // " chat_addMessage('The socket reported an error!', { type: 'error' });\n" \ + // " }\n" \ + // "\n" \ + // " /**\n" \ + // " This is the event when the socket has received a message.\n" \ + // " This will parse the message and execute the corresponing command (or add the message).\n" \ + // " */\n" \ + // " function socket_onmessage(event)\n" \ + // " {\n" \ + // " if(typeof(event.data) === 'string')\n" \ + // " {\n" \ + // " // text message or command\n" \ + // " let message = event.data.split('|', 3);\n" \ + // " switch(message[0])\n" \ + // " {\n" \ + // " case 'userinit':\n" \ + // " connectedUsers.set(message[1], message[2]);\n" \ + // " {\n" \ + // " let users = document.getElementById('Users');\n" \ + // " let div = document.createElement('div');\n" \ + // " users.appendChild(div);\n" \ + // " div.innerText = message[2];\n" \ + // " div.setAttribute('data-user', message[1]);\n" \ + // " }\n" \ + // " break;\n" \ + // " case 'useradd':\n" \ + // " connectedUsers.set(message[1], message[2]);\n" \ + // " chat_addMessage(`The user '${message[2]}' has joined our lovely chatroom.`, { type: 'moderator' });\n" \ + // " {\n" \ + // " let users = document.getElementById('Users');\n" \ + // " let div = document.createElement('div');\n" \ + // " users.appendChild(div);\n" \ + // " div.innerText = message[2];\n" \ + // " div.setAttribute('data-user', message[1]);\n" \ + // " }\n" \ + // " break;\n" \ + // " case 'userdel':\n" \ + // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has left our chatroom. We will miss you.`, { type: 'moderator' });\n" \ + // " connectedUsers.delete(message[1]);\n" \ + // " {\n" \ + // " let users = document.getElementById('Users');\n" \ + // " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ + // " if(div !== null)\n" \ + // " {\n" \ + // " users.removeChild(div);\n" \ + // " if(div.classList.contains('selected'))\n" \ + // " users.querySelector('div[data-user=\\'0\\']').classList.add('selected');\n" \ + // " }\n" \ + // " }\n" \ + // " break;\n" \ + // " case 'username':\n" \ + // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has changed his name to '${message[2]}'.`, { type: 'moderator' });\n" \ + // " connectedUsers.set(message[1], message[2]);\n" \ + // " {\n" \ + // " let users = document.getElementById('Users');\n" \ + // " let div = users.querySelector(`div[data-user='${message[1]}']`);\n" \ + // " if(div !== null)\n" \ + // " {\n" \ + // " div.innerText = message[2];\n" \ + // " }\n" \ + // " }\n" \ + // " break;\n" \ + // " case 'ping':\n" \ + // " chat_addMessage(`The user '${connectedUsers.get(message[1])}' has a ping of ${message[2]} ms.`, { type: 'moderator' });\n" \ + // " break;\n" \ + // " default:\n" \ + // " chat_addMessage(message[2], { type: message[0], from: connectedUsers.get(message[1]) });\n" \ + // " break;\n" \ + // " }\n" \ + // " }\n" \ + // " else\n" \ + // " {\n" \ + // " // We received a binary frame, which means a picture here\n" \ + // " let byteData = new Uint8Array(event.data);\n" \ + // " let decoder = new TextDecoder();\n" \ + // " let message = [ ];\n" \ + // " // message type\n" \ + // " let j = 0;\n" \ + // " let i = byteData.indexOf(0x7C, j); // | = 0x7C;\n" \ + // " if(i < 0)\n" \ + // " return;\n" \ + // " message.push(decoder.decode(byteData.slice(0, i)));\n" \ + // " // picture from\n" \ + // " j = i + 1;\n" \ + // " i = byteData.indexOf(0x7C, j);\n" \ + // " if(i < 0)\n" \ + // " return;\n" \ + // " message.push(decoder.decode(byteData.slice(j, i)));\n" \ + // " // picture encoding\n" \ + // " j = i + 1;\n" \ + // " i = byteData.indexOf(0x7C, j);\n" \ + // " if(i < 0)\n" \ + // " return;\n" \ + // " message.push(decoder.decode(byteData.slice(j, i)));\n" \ + // " // picture\n" \ + // " byteData = byteData.slice(i + 1);\n" \ + // " chat_addMessage(byteData, { type: message[0], from: connectedUsers.get(message[1]), pictureType: message[2] });\n" \ + // " }\n" \ + // " }\n" \ + // "</script>" \ + // "</head>" \ + // "<body><noscript>Please enable JavaScript to test the libmicrohttpd Websocket chatserver demo!</noscript></body>" \ + // "</html>" + +#define PAGE_NOT_FOUND \ + "404 Not Found" + +#define PAGE_INVALID_WEBSOCKET_REQUEST \ + "Invalid WebSocket request!" + +/** + * This struct is used to keep the data of a connected chat user. + * It is passed to the socket-receive thread (connecteduser_receive_messages) as well as to + * the socket-send thread (connecteduser_send_messages). + * It can also be accessed via the global array users (mutex protected). + */ +struct ConnectedUser +{ + /* the TCP/IP socket for reading/writing */ + MHD_socket fd; + /* the UpgradeResponseHandle of libmicrohttpd (needed for closing the socket) */ + struct MHD_UpgradeResponseHandle *urh; + /* the websocket encode/decode stream */ + struct MHD_WebSocketStream*ws; + /* the possibly read data at the start (only used once) */ + char *extra_in; + size_t extra_in_size; + /* the unique user id (counting from 1, ids will never be re-used) */ + size_t user_id; + /* the current user name */ + char*user_name; + size_t user_name_len; + /* the zero-based index of the next message; + may be decremented when old messages are deleted */ + size_t next_message_index; + /* specifies whether the websocket shall be closed (1) or not (0) */ + int disconnect; + /* condition variable to wake up the sender of this connection */ + pthread_cond_t wake_up_sender; + /* mutex to ensure that no send actions are mixed + (sending can be done by send and recv thread; + may not be simultaneously locked with chat_mutex by the same thread) */ + pthread_mutex_t send_mutex; + /* specifies whether a ping shall be executed (1), is being executed (2) or + no ping is pending (0) */ + int ping_status; + /* the start time of the ping, if a ping is running */ + struct timespec ping_start; + /* the message used for the ping (must match the pong response)*/ + char ping_message[128]; + /* the length of the ping message (may not exceed 125) */ + size_t ping_message_len; + /* the numeric ping message suffix to detect ping messages, which are too old */ + int ping_counter; +}; + +/** + * A single message, which has been send via the chat. + * This can be text, an image or a command. + */ +struct Message +{ + /* The user id of the sender. This is 0 if it is a system message- */ + size_t from_user_id; + /* The user id of the recipient. This is 0 if every connected user shall receive it */ + size_t to_user_id; + /* The data of the message. */ + char*data; + size_t data_len; + /* Specifies wheter the data is UTF-8 encoded text (0) or binary data (1) */ + int is_binary; +}; + +/* the unique user counter for new users (only accessed by main thread) */ +size_t unique_user_id = 0; + +/* the chat data (users and messages; may be accessed by all threads, but is protected by mutex) */ +pthread_mutex_t chat_mutex; +struct ConnectedUser**users = NULL; +size_t user_count = 0; +struct Message**messages = NULL; +size_t message_count = 0; +/* specifies whether all websockets must close (1) or not (0) */ +volatile int disconnect_all = 0; +/* a counter for cleaning old messages (each 10 messages we will try to clean the list */ +int clean_count = 0; +#define CLEANUP_LIMIT 10 + +/** + * Change socket to blocking. + * + * @param fd the socket to manipulate + */ +static void +make_blocking (MHD_socket fd) +{ +#if defined(MHD_POSIX_SOCKETS) + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + return; + if ((flags & ~O_NONBLOCK) != flags) + if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) + abort (); +#elif defined(MHD_WINSOCK_SOCKETS) + unsigned long flags = 0; + + ioctlsocket (fd, FIONBIO, &flags); +#endif /* MHD_WINSOCK_SOCKETS */ + +} + + +/** + * Sends all data of the given buffer via the TCP/IP socket + * + * @param fd The TCP/IP socket which is used for sending + * @param buf The buffer with the data to send + * @param len The length in bytes of the data in the buffer + */ +static void +send_all (struct ConnectedUser*cu, + const char *buf, + size_t len) +{ + ssize_t ret; + size_t off; + + if (0 == pthread_mutex_lock (&cu->send_mutex)) + { + for (off = 0; off < len; off += ret) + { + ret = send (cu->fd, + &buf[off], + (int) (len - off), + 0); + if (0 > ret) + { + int err = errno; + if (EAGAIN == errno) + { + ret = 0; + continue; + } + break; + } + if (0 == ret) + break; + } + pthread_mutex_unlock (&cu->send_mutex); + } +} + + +/** + * Adds a new chat message to the list of messages. + * + * @param from_user_id the user id of the sender (0 means system) + * @param to_user_id the user id of the recipiend (0 means everyone) + * @param data the data to send (UTF-8 text or binary; will be copied) + * @param data_len the length of the data to send + * @param is_binary specifies whether the data is UTF-8 text (0) or binary (1) + * @param needs_lock specifies whether the caller has already locked the global chat mutex (0) or + * if this procedure needs to lock it (1) + * + * @return 0 on success, other values on error + */ +static int +chat_addmessage (size_t from_user_id, + size_t to_user_id, + char*data, + size_t data_len, + int is_binary, + int needs_lock) +{ + /* allocate the buffer and fill it with data */ + struct Message*message = (struct Message*) malloc (sizeof (struct Message)); + if (NULL == message) + return 1; + + memset (message, 0, sizeof (struct Message)); + message->from_user_id = from_user_id; + message->to_user_id = to_user_id; + message->is_binary = is_binary; + message->data_len = data_len; + message->data = malloc (data_len + 1); + if (NULL == message->data) + { + free (message); + return 1; + } + memcpy (message->data, data, data_len); + message->data[data_len] = 0; + + /* lock the global mutex if needed */ + if (0 != needs_lock) + { + if (0 != pthread_mutex_lock (&chat_mutex)) + return 1; + } + + /* add the new message to the global message list */ + size_t message_count_ = message_count + 1; + struct Message**messages_ = (struct Message**) realloc (messages, + message_count_ + * sizeof (struct + Message*)); + if (NULL == messages_) + { + free (message); + if (0 != needs_lock) + pthread_mutex_unlock (&chat_mutex); + return 1; + } + messages_[message_count] = message; + messages = messages_; + message_count = message_count_; + + /* inform the sender threads about the new message */ + for (size_t i = 0; i < user_count; ++i) + pthread_cond_signal (&users[i]->wake_up_sender); + + /* unlock the global mutex if needed */ + if (0 != needs_lock) + { + if (0 != needs_lock) + pthread_mutex_unlock (&chat_mutex); + } + return 0; +} + + +/** + * Cleans up old messages + * + * @param needs_lock specifies whether the caller has already locked the global chat mutex (0) or + * if this procedure needs to lock it (1) + * @return 0 on success, other values on error + */ +static int +chat_clearmessages (int needs_lock) +{ + /* lock the global mutex if needed */ + if (0 != needs_lock) + { + if (0 != pthread_mutex_lock (&chat_mutex)) + return 1; + } + + /* update the clean counter and check whether we need cleaning */ + ++clean_count; + if (CLEANUP_LIMIT > clean_count) + { + /* no cleanup required */ + if (0 != needs_lock) + { + pthread_mutex_unlock (&chat_mutex); + } + return 0; + } + clean_count = 0; + + /* check whether we got any messages (without them no cleaning is required */ + if (0 < message_count) + { + /* then check whether we got any connected users */ + if (0 < user_count) + { + /* determine the minimum index for the next message of all connected users */ + size_t min_message = users[0]->next_message_index; + for (size_t i = 1; i < user_count; ++i) + { + if (min_message > users[i]->next_message_index) + min_message = users[i]->next_message_index; + } + if (0 < min_message) + { + /* remove all messages with index below min_message and update + the message indices of the users */ + for (size_t i = 0; i < min_message; ++i) + { + free (messages[i]->data); + free (messages[i]); + } + for (size_t i = min_message; i < message_count; ++i) + messages[i - min_message] = messages[i]; + message_count -= min_message; + for (size_t i = 0; i < user_count; ++i) + users[i]->next_message_index -= min_message; + } + } + else + { + /* without connected users, simply remove all messages */ + for (size_t i = 0; i < message_count; ++i) + { + free (messages[i]->data); + free (messages[i]); + } + free (messages); + messages = NULL; + message_count = 0; + } + } + + /* unlock the global mutex if needed */ + if (0 != needs_lock) + { + pthread_mutex_unlock (&chat_mutex); + } + return 0; +} + + +/** + * Adds a new chat user to the global user list. + * This will be called at the start of connecteduser_receive_messages. + * + * @param cu The connected user + * @return 0 on success, other values on error + */ +static int +chat_adduser (struct ConnectedUser*cu) +{ + /* initialize the notification message of the new user */ + char user_index[32]; + itoa ((int) cu->user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + size_t data_len = user_index_len + cu->user_name_len + 9; + char*data = (char*) malloc (data_len + 1); + if (NULL == data) + return 1; + strcpy (data, "useradd|"); + strcat (data, user_index); + strcat (data, "|"); + strcat (data, cu->user_name); + + /* lock the mutex */ + if (0 != pthread_mutex_lock (&chat_mutex)) + { + free (data); + return 1; + } + /* inform the other chat users about the new user */ + if (0 != chat_addmessage (0, + 0, + data, + data_len, + 0, + 0)) + { + free (data); + pthread_mutex_unlock (&chat_mutex); + return 1; + } + free (data); + + /* add the new user to the list */ + size_t user_count_ = user_count + 1; + struct ConnectedUser**users_ = (struct ConnectedUser**) realloc (users, + user_count_ + * sizeof ( + struct + ConnectedUser + *)); + if (NULL == users_) + { + /* realloc failed */ + pthread_mutex_unlock (&chat_mutex); + return 1; + } + users_[user_count] = cu; + users = users_; + user_count = user_count_; + + /* Initialize the next message index to the current message count. */ + /* This will skip all old messages for this new connected user. */ + cu->next_message_index = message_count; + + /* unlock the mutex */ + pthread_mutex_unlock (&chat_mutex); + return 0; +} + + +/** + * Removes a chat user from the global user list. + * + * @param cu The connected user + * @return 0 on success, other values on error + */ +static int +chat_removeuser (struct ConnectedUser*cu) +{ + char user_index[32]; + + /* initialize the chat message for the removed user */ + itoa ((int) cu->user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + size_t data_len = user_index_len + 9; + char*data = (char*) malloc (data_len + 1); + if (NULL == data) + return 1; + strcpy (data, "userdel|"); + strcat (data, user_index); + strcat (data, "|"); + + /* lock the mutex */ + if (0 != pthread_mutex_lock (&chat_mutex)) + { + free (data); + return 1; + } + /* inform the other chat users that the user is gone */ + int got_error = 0; + if (0 != chat_addmessage (0, 0, data, data_len, 0, 0)) + { + free (data); + got_error = 1; + } + + /* remove the user from the list */ + int found = 0; + for (size_t i = 0; i < user_count; ++i) + { + if (cu == users[i]) + { + found = 1; + for (size_t j = i + 1; j < user_count; ++j) + { + users[j - 1] = users[j]; + } + --user_count; + break; + } + } + if (0 == found) + got_error = 1; + + /* unlock the mutex */ + pthread_mutex_unlock (&chat_mutex); + + return got_error; +} + + +/** + * Renames a chat user + * + * @param cu The connected user + * @param new_name The new user name. On success this pointer will be taken. + * @param new_name_len The length of the new name + * @return 0 on success, other values on error. 2 means name already in use. + */ +static int +chat_renameuser (struct ConnectedUser*cu, + char*new_name, + size_t new_name_len) +{ + /* lock the mutex */ + if (0 != pthread_mutex_lock (&chat_mutex)) + { + return 1; + } + + /* check whether the name is already in use */ + for (size_t i = 0; i < user_count; ++i) + { + if (cu != users[i]) + { + if ((users[i]->user_name_len == new_name_len) && (0 == stricmp ( + users[i]->user_name, + new_name))) + { + pthread_mutex_unlock (&chat_mutex); + return 2; + } + } + } + + /* generate the notification message */ + char user_index[32]; + itoa ((int) cu->user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + size_t data_len = user_index_len + new_name_len + 10; + char*data = (char*) malloc (data_len + 1); + if (NULL == data) + return 1; + strcpy (data, "username|"); + strcat (data, user_index); + strcat (data, "|"); + strcat (data, new_name); + + /* inform the other chat users about the new name */ + if (0 != chat_addmessage (0, 0, data, data_len, 0, 0)) + { + free (data); + pthread_mutex_unlock (&chat_mutex); + return 1; + } + free (data); + + /* accept the new user name */ + free (cu->user_name); + cu->user_name = new_name; + cu->user_name_len = new_name_len; + + /* unlock the mutex */ + pthread_mutex_unlock (&chat_mutex); + + return 0; +} + + +/** +* Parses received data from the TCP/IP socket with the websocket stream +* +* @param cu The connected user +* @param new_name The new user name +* @param new_name_len The length of the new name +* @return 0 on success, other values on error +*/ +static int +connecteduser_parse_received_websocket_stream (struct ConnectedUser*cu, + char*buf, + size_t buf_len) +{ + size_t buf_offset = 0; + while (buf_offset < buf_len) + { + size_t new_offset = 0; + char *frame_data = NULL; + size_t frame_len = 0; + int status = MHD_websocket_decode (cu->ws, + buf + buf_offset, + buf_len - buf_offset, + &new_offset, + &frame_data, + &frame_len); + if (0 > status) + { + /* an error occurred and the connection must be closed */ + if (NULL != frame_data) + { + /* depending on the WebSocket flag */ + /* MHD_WEBSOCKET_FLAG_GENERATE_CLOSE_FRAMES_ON_ERROR */ + /* close frames might be generated on errors */ + send_all (cu, + frame_data, + frame_len); + MHD_websocket_free (cu->ws, frame_data); + } + return 1; + } + else + { + buf_offset += new_offset; + + if (0 < status) + { + /* the frame is complete */ + switch (status) + { + case MHD_WEBSOCKET_STATUS_TEXT_FRAME: + case MHD_WEBSOCKET_STATUS_BINARY_FRAME: + /** + * a text or binary frame has been received. + * in this chat server example we use a simple protocol where + * the JavaScript added a prefix like "<command>|<to_user_id>|data". + * Some examples: + * "||test" means a regular chat message to everyone with the message "test". + * "private|1|secret" means a private chat message to user with id 1 with the message "secret". + * "name||MyNewName" means that the user requests a rename to "MyNewName" + * "ping|1|" means that the user with id 1 shall get a ping + * + * Binary data is handled here like text data. + * The difference in the data is only checked by the JavaScript. + */ + { + size_t command = 1000; + size_t from_user_id = cu->user_id; + size_t to_user_id = 0; + size_t i; + + /* parse the command */ + for (i = 0; i < frame_len; ++i) + { + if ('|' == frame_data[i]) + { + frame_data[i] = 0; + ++i; + break; + } + } + if (0 < i) + { + if (i == 1) + { + /* no command means regular message */ + command = 0; + } + else if (0 == stricmp (frame_data, "private")) + { + /* private means private message */ + command = 1; + } + else if (0 == stricmp (frame_data, "name")) + { + /* name means chat user rename */ + command = 2; + } + else if (0 == stricmp (frame_data, "ping")) + { + /* ping means a ping request */ + command = 3; + } + else + { + /* no other commands supported, so this means invalid */ + command = 1000; + } + } + + /* parse the to_user_id, if given */ + size_t j = i; + for (; j < frame_len; ++j) + { + if ('|' == frame_data[j]) + { + frame_data[j] = 0; + ++j; + break; + } + } + if (i + 1 < j) + { + to_user_id = (size_t) atoi (frame_data + i); + } + + /* decide via the command what action to do */ + if (frame_len >= j) + { + int is_binary = (MHD_WEBSOCKET_STATUS_BINARY_FRAME == status ? 1 : + 0); + switch (command) + { + case 0: + /* regular chat message */ + { + /** + * Generate the message for the message list. + * Regular chat messages get the command "regular". + * After that we add the from_user_id, followed by the content. + * The content must always be copied with memcpy instead of strcat, + * because the data (binary as well as UTF-8 encoded) is allowed + * to contain the NUL character. + * However we will add a terminating NUL character, + * which is not included in the data length + * (and thus will not be send to the recipients). + * This is useful for debugging with an IDE. + */ + char user_index[32]; + itoa ((int) from_user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + size_t data_len = user_index_len + frame_len - j + 9; + char*data = (char*) malloc (data_len + 1); + if (NULL != data) + { + strcpy (data, "regular|"); + strcat (data, user_index); + strcat (data, "|"); + size_t offset = strlen (data); + memcpy (data + offset, + frame_data + j, + frame_len - j); + data[data_len] = 0; + + /* add the chat message to the global list */ + chat_addmessage (from_user_id, + 0, + data, + data_len, + is_binary, + 1); + free (data); + } + } + break; + + case 1: + /* private chat message */ + if (0 != to_user_id) + { + /** + * Generate the message for the message list. + * This is similar to the code for regular messages above. + * The difference is the prefix "private" + */ + char user_index[32]; + itoa ((int) from_user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + size_t data_len = user_index_len + frame_len - j + 9; + char*data = (char*) malloc (data_len + 1); + if (NULL != data) + { + + strcpy (data, "private|"); + strcat (data, user_index); + strcat (data, "|"); + size_t offset = strlen (data); + memcpy (data + offset, + frame_data + j, + frame_len - j); + data[data_len] = 0; + + /* add the chat message to the global list */ + chat_addmessage (from_user_id, + to_user_id, + data, + data_len, + is_binary, + 1); + free (data); + } + } + break; + + case 2: + /* rename */ + { + /* check whether the new name is valid and allocate a new buffer for it */ + size_t new_name_len = frame_len - j; + if (0 == new_name_len) + { + chat_addmessage (0, + from_user_id, + "error||Your new name is invalid. You haven't been renamed.", + 58, + 0, + 1); + break; + } + char*new_name = (char*) malloc (new_name_len + 1); + if (NULL == new_name) + { + chat_addmessage (0, + from_user_id, + "error||Error while renaming. You haven't been renamed.", + 54, + 0, + 1); + break; + } + new_name[new_name_len] = 0; + for (size_t k = 0; k < new_name_len; ++k) + { + char c = frame_data[j + k]; + if ((32 >= c) || (c >= 127)) + { + free (new_name); + new_name = NULL; + chat_addmessage (0, + from_user_id, + "error||Your new name contains invalid characters. You haven't been renamed.", + 75, + 0, + 1); + break; + } + new_name[k] = c; + } + if (NULL == new_name) + break; + + /* rename the user */ + int rename_result = chat_renameuser (cu, + new_name, + new_name_len); + if (0 != rename_result) + { + /* the buffer will only be freed if no rename was possible */ + free (new_name); + if (2 == rename_result) + { + chat_addmessage (0, + from_user_id, + "error||Your new name is already in use by another user. You haven't been renamed.", + 81, + 0, + 1); + } + else + { + chat_addmessage (0, + from_user_id, + "error||Error while renaming. You haven't been renamed.", + 54, + 0, + 1); + } + } + } + break; + + case 3: + /* ping */ + { + if (0 == pthread_mutex_lock (&chat_mutex)) + { + /* check whether the to_user exists */ + struct ConnectedUser*ping_user = NULL; + for (size_t k = 0; k < user_count; ++k) + { + if (users[k]->user_id == to_user_id) + { + ping_user = users[k]; + break; + } + } + if (NULL == ping_user) + { + chat_addmessage (0, + from_user_id, + "error||Couldn't find the specified user for pinging.", + 52, + 0, + 0); + } + else + { + /* if pinging is requested, */ + /* we mark the user and inform the sender about this */ + if (0 == ping_user->ping_status) + { + ping_user->ping_status = 1; + pthread_cond_signal (&ping_user->wake_up_sender); + } + } + pthread_mutex_unlock (&chat_mutex); + } + else + { + chat_addmessage (0, + from_user_id, + "error||Error while pinging.", + 27, + 0, + 1); + } + } + break; + + default: + /* invalid command */ + chat_addmessage (0, + from_user_id, + "error||You sent an invalid command.", + 35, + 0, + 1); + break; + } + } + } + MHD_websocket_free (cu->ws, + frame_data); + return 0; + + case MHD_WEBSOCKET_STATUS_CLOSE_FRAME: + /* if we receive a close frame, we will respond with one */ + MHD_websocket_free (cu->ws, + frame_data); + { + char*result = NULL; + size_t result_len = 0; + int er = MHD_websocket_encode_close (cu->ws, + MHD_WEBSOCKET_CLOSEREASON_REGULAR, + NULL, + 0, + &result, + &result_len); + if (MHD_WEBSOCKET_STATUS_OK == er) + { + send_all (cu, + result, + result_len); + MHD_websocket_free (cu->ws, result); + } + } + return 1; + + case MHD_WEBSOCKET_STATUS_PING_FRAME: + /* if we receive a ping frame, we will respond */ + /* with the corresponding pong frame */ + { + char *pong = NULL; + size_t pong_len = 0; + int er = MHD_websocket_encode_pong (cu->ws, + frame_data, + frame_len, + &pong, + &pong_len); + + MHD_websocket_free (cu->ws, + frame_data); + if (MHD_WEBSOCKET_STATUS_OK == er) + { + send_all (cu, + pong, + pong_len); + MHD_websocket_free (cu->ws, + pong); + } + } + return 0; + + case MHD_WEBSOCKET_STATUS_PONG_FRAME: + /* if we receive a pong frame, */ + /* we will check whether we requested this frame and */ + /* whether it is the last requested pong */ + if (2 == cu->ping_status) + { + cu->ping_status = 0; + struct timespec now; + timespec_get (&now, TIME_UTC); + if ((cu->ping_message_len == frame_len) && + (0 == strcmp (frame_data, + cu->ping_message))) + { + int ping = (int) (((int64_t) (now.tv_sec + - cu->ping_start.tv_sec)) * 1000 + + ((int64_t) (now.tv_nsec + - cu->ping_start.tv_nsec)) + / 1000000); + char result_text[240]; + strcpy (result_text, + "ping|"); + itoa ((int) cu->user_id, + result_text + 5, + 10); + strcat (result_text, + "|"); + itoa (ping, + result_text + strlen (result_text), + 10); + chat_addmessage (0, + 0, + result_text, + strlen (result_text), + 0, + 1); + } + } + MHD_websocket_free (cu->ws, + frame_data); + return 0; + + default: + /* This case should really never happen, */ + /* because there are only five types of (finished) websocket frames. */ + /* If it is ever reached, it means that there is memory corruption. */ + MHD_websocket_free (cu->ws, + frame_data); + return 1; + } + } + } + } + + return 0; +} + + +/** + * Sends messages from the message list over the TCP/IP socket + * after encoding it with the websocket stream. + * This is also used for server-side actions, + * because the thread for receiving messages waits for + * incoming data and cannot be woken up. + * But the sender thread can be woken up easily. + * + * @param cls The connected user + * @return Always NULL + */ +static void * +connecteduser_send_messages (void*cls) +{ + struct ConnectedUser *cu = cls; + + /* the main loop of sending messages requires to lock the mutex */ + if (0 == pthread_mutex_lock (&chat_mutex)) + { + for (;;) + { + /* loop while not all messages processed */ + int all_messages_read = 0; + while (0 == all_messages_read) + { + if (1 == disconnect_all) + { + /* the application closes and want that we disconnect all users */ + struct MHD_UpgradeResponseHandle*urh = cu->urh; + if (NULL != urh) + { + /* Close the TCP/IP socket. */ + /* This will also wake-up the waiting receive-thread for this connected user. */ + cu->urh = NULL; + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + } + pthread_mutex_unlock (&chat_mutex); + return NULL; + } + else if (1 == cu->disconnect) + { + /* The sender thread shall close. */ + /* This is only requested by the receive thread, so we can just leave. */ + pthread_mutex_unlock (&chat_mutex); + return NULL; + } + else if (1 == cu->ping_status) + { + /* A pending ping is requested */ + ++cu->ping_counter; + strcpy (cu->ping_message, + "libmicrohttpdchatserverpingdata"); + itoa (cu->ping_counter, + cu->ping_message + 31, + 10); + cu->ping_message_len = strlen (cu->ping_message); + char*frame_data = NULL; + size_t frame_len = 0; + int er = MHD_websocket_encode_ping (cu->ws, + cu->ping_message, + cu->ping_message_len, + &frame_data, + &frame_len); + if (MHD_WEBSOCKET_STATUS_OK == er) + { + cu->ping_status = 2; + timespec_get (&cu->ping_start, TIME_UTC); + + /* send the data via the TCP/IP socket and */ + /* unlock the mutex while sending */ + pthread_mutex_unlock (&chat_mutex); + send_all (cu, + frame_data, + frame_len); + if (0 != pthread_mutex_lock (&chat_mutex)) + { + return NULL; + } + } + MHD_websocket_free (cu->ws, frame_data); + } + else if (cu->next_message_index < message_count) + { + /* a chat message or command is pending */ + char*frame_data = NULL; + size_t frame_len = 0; + int er = 0; + { + struct Message*msg = messages[cu->next_message_index]; + if ((0 == msg->to_user_id) || + (cu->user_id == msg->to_user_id) || + (cu->user_id == msg->from_user_id) ) + { + if (0 == msg->is_binary) + { + er = MHD_websocket_encode_text (cu->ws, + msg->data, + msg->data_len, + MHD_WEBSOCKET_FRAGMENTATION_NONE, + &frame_data, + &frame_len, + NULL); + } + else + { + er = MHD_websocket_encode_binary (cu->ws, + msg->data, + msg->data_len, + MHD_WEBSOCKET_FRAGMENTATION_NONE, + &frame_data, + &frame_len); + } + } + } + ++cu->next_message_index; + + /* send the data via the TCP/IP socket and */ + /* unlock the mutex while sending */ + pthread_mutex_unlock (&chat_mutex); + if (MHD_WEBSOCKET_STATUS_OK == er) + { + send_all (cu, + frame_data, + frame_len); + } + MHD_websocket_free (cu->ws, + frame_data); + if (0 != pthread_mutex_lock (&chat_mutex)) + { + return NULL; + } + /* check whether there are still pending messages */ + all_messages_read = (cu->next_message_index < message_count) ? 0 : 1; + } + else + { + all_messages_read = 1; + } + } + /* clear old messages */ + chat_clearmessages (0); + + /* Wait for wake up. */ + /* This will automatically unlock the mutex while waiting and */ + /* lock the mutex after waiting */ + pthread_cond_wait (&cu->wake_up_sender, &chat_mutex); + } + } + + return NULL; +} + + +/** + * Receives messages from the TCP/IP socket and + * initializes the connected user. + * + * @param cls The connected user + * @return Always NULL + */ +static void * +connecteduser_receive_messages (void *cls) +{ + struct ConnectedUser *cu = cls; + char buf[128]; + ssize_t got; + int result; + + /* make the socket blocking */ + make_blocking (cu->fd); + + /* generate the user name */ + { + char user_name[32]; + strcpy (user_name, "User"); + itoa ((int) cu->user_id, user_name + 4, 10); + cu->user_name_len = strlen (user_name); + cu->user_name = malloc (cu->user_name_len + 1); + if (NULL == cu->user_name) + { + free (cu->extra_in); + free (cu); + MHD_upgrade_action (cu->urh, + MHD_UPGRADE_ACTION_CLOSE); + return NULL; + } + strcpy (cu->user_name, user_name); + } + + /* initialize the wake-up-sender condition variable */ + if (0 != pthread_cond_init (&cu->wake_up_sender, NULL)) + { + MHD_upgrade_action (cu->urh, + MHD_UPGRADE_ACTION_CLOSE); + free (cu->user_name); + free (cu->extra_in); + free (cu); + return NULL; + } + + /* initialize the send mutex */ + if (0 != pthread_mutex_init (&cu->send_mutex, NULL)) + { + MHD_upgrade_action (cu->urh, + MHD_UPGRADE_ACTION_CLOSE); + pthread_cond_destroy (&cu->wake_up_sender); + free (cu->user_name); + free (cu->extra_in); + free (cu); + return NULL; + } + + /* add the user to the chat user list */ + chat_adduser (cu); + + /* initialize the web socket stream for encoding/decoding */ + result = MHD_websocket_stream_init (&cu->ws, + MHD_WEBSOCKET_FLAG_SERVER + | MHD_WEBSOCKET_FLAG_NO_FRAGMENTS, + 0); + if (MHD_WEBSOCKET_STATUS_OK != result) + { + chat_removeuser (cu); + pthread_cond_destroy (&cu->wake_up_sender); + pthread_mutex_destroy (&cu->send_mutex); + MHD_upgrade_action (cu->urh, + MHD_UPGRADE_ACTION_CLOSE); + free (cu->user_name); + free (cu->extra_in); + free (cu); + return NULL; + } + + /* send a list of all currently connected users (bypassing the messaging system) */ + { + struct UserInit + { + char*user_init; + size_t user_init_len; + }; + struct UserInit*init_users = NULL; + size_t init_users_len = 0; + + /* first collect all users without sending (so the mutex isn't locked too long) */ + if (0 == pthread_mutex_lock (&chat_mutex)) + { + if (0 < user_count) + { + init_users = (struct UserInit*) malloc (user_count * sizeof (struct + UserInit)); + if (NULL != init_users) + { + init_users_len = user_count; + for (size_t i = 0; i < user_count; ++i) + { + char user_index[32]; + itoa ((int) users[i]->user_id, user_index, 10); + size_t user_index_len = strlen (user_index); + struct UserInit iu; + iu.user_init_len = user_index_len + users[i]->user_name_len + 10; + iu.user_init = (char*) malloc (iu.user_init_len + 1); + if (NULL != iu.user_init) + { + strcpy (iu.user_init, "userinit|"); + strcat (iu.user_init, user_index); + strcat (iu.user_init, "|"); + if (0 < users[i]->user_name_len) + strcat (iu.user_init, users[i]->user_name); + } + init_users[i] = iu; + } + } + } + pthread_mutex_unlock (&chat_mutex); + } + + /* then send all users to the connected client */ + for (size_t i = 0; i < init_users_len; ++i) + { + char *frame_data = NULL; + size_t frame_len = 0; + if ((0 < init_users[i].user_init_len) && (NULL != + init_users[i].user_init) ) + { + int status = MHD_websocket_encode_text (cu->ws, + init_users[i].user_init, + init_users[i].user_init_len, + MHD_WEBSOCKET_FRAGMENTATION_NONE, + &frame_data, + &frame_len, + NULL); + if (MHD_WEBSOCKET_STATUS_OK == status) + { + send_all (cu, + frame_data, + frame_len); + MHD_websocket_free (cu->ws, + frame_data); + } + free (init_users[i].user_init); + } + } + free (init_users); + } + + /* send the welcome message to the user (bypassing the messaging system) */ + { + char *frame_data = NULL; + size_t frame_len = 0; + const char *welcome_msg = "moderator||" \ + "Welcome to the libmicrohttpd WebSocket chatserver example.\n" \ + "Supported commands are:\n" \ + " /m <user> <text> - sends a private message to the specified user\n" \ + " /ping <user> - sends a ping to the specified user\n" \ + " /name <name> - changes your name to the specified name\n" \ + " /disconnect - disconnects your websocket\n\n" \ + "All messages, which does not start a slash, are regular messages, which will be sent to selected user.\n\n" \ + "Have fun!"; + int r = MHD_websocket_encode_text (cu->ws, + welcome_msg, + strlen (welcome_msg), + MHD_WEBSOCKET_FRAGMENTATION_NONE, + &frame_data, + &frame_len, + NULL); + send_all (cu, + frame_data, + frame_len); + MHD_websocket_free (cu->ws, + frame_data); + } + + /* start the message-send thread */ + pthread_t pt; + if (0 != pthread_create (&pt, + NULL, + &connecteduser_send_messages, + cu)) + abort (); + + /* start by parsing extra data MHD may have already read, if any */ + if (0 != cu->extra_in_size) + { + if (0 != connecteduser_parse_received_websocket_stream (cu, + cu->extra_in, + cu->extra_in_size)) + { + chat_removeuser (cu); + if (0 == pthread_mutex_lock (&chat_mutex)) + { + cu->disconnect = 1; + pthread_cond_signal (&cu->wake_up_sender); + pthread_mutex_unlock (&chat_mutex); + pthread_join (pt, NULL); + } + struct MHD_UpgradeResponseHandle*urh = cu->urh; + if (NULL != urh) + { + cu->urh = NULL; + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + } + pthread_cond_destroy (&cu->wake_up_sender); + pthread_mutex_destroy (&cu->send_mutex); + MHD_websocket_stream_free (cu->ws); + free (cu->user_name); + free (cu->extra_in); + free (cu); + return NULL; + } + free (cu->extra_in); + cu->extra_in = NULL; + } + + /* the main loop for receiving data */ + while (1) + { + got = recv (cu->fd, + buf, + sizeof (buf), + 0); + if (0 >= got) + { + /* the TCP/IP socket has been closed */ + break; + } + if (0 < got) + { + if (0 != connecteduser_parse_received_websocket_stream (cu, buf, + (size_t) got)) + { + /* A websocket protocol error occurred */ + chat_removeuser (cu); + if (0 == pthread_mutex_lock (&chat_mutex)) + { + cu->disconnect = 1; + pthread_cond_signal (&cu->wake_up_sender); + pthread_mutex_unlock (&chat_mutex); + pthread_join (pt, NULL); + } + struct MHD_UpgradeResponseHandle*urh = cu->urh; + if (NULL != urh) + { + cu->urh = NULL; + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + } + pthread_cond_destroy (&cu->wake_up_sender); + pthread_mutex_destroy (&cu->send_mutex); + MHD_websocket_stream_free (cu->ws); + free (cu->user_name); + free (cu); + return NULL; + } + } + } + + /* cleanup */ + chat_removeuser (cu); + if (0 == pthread_mutex_lock (&chat_mutex)) + { + cu->disconnect = 1; + pthread_cond_signal (&cu->wake_up_sender); + pthread_mutex_unlock (&chat_mutex); + pthread_join (pt, NULL); + } + struct MHD_UpgradeResponseHandle*urh = cu->urh; + if (NULL != urh) + { + cu->urh = NULL; + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + } + pthread_cond_destroy (&cu->wake_up_sender); + pthread_mutex_destroy (&cu->send_mutex); + MHD_websocket_stream_free (cu->ws); + free (cu->user_name); + free (cu); + + return NULL; +} + + +/** + * Function called after a protocol "upgrade" response was sent + * successfully and the socket should now be controlled by some + * protocol other than HTTP. + * + * Any data already received on the socket will be made available in + * @e extra_in. This can happen if the application sent extra data + * before MHD send the upgrade response. The application should + * treat data from @a extra_in as if it had read it from the socket. + * + * Note that the application must not close() @a sock directly, + * but instead use #MHD_upgrade_action() for special operations + * on @a sock. + * + * Data forwarding to "upgraded" @a sock will be started as soon + * as this function return. + * + * Except when in 'thread-per-connection' mode, implementations + * of this function should never block (as it will still be called + * from within the main event loop). + * + * @param cls closure, whatever was given to #MHD_create_response_for_upgrade(). + * @param connection original HTTP connection handle, + * giving the function a last chance + * to inspect the original HTTP request + * @param con_cls last value left in `con_cls` of the `MHD_AccessHandlerCallback` + * @param extra_in if we happened to have read bytes after the + * HTTP header already (because the client sent + * more than the HTTP header of the request before + * we sent the upgrade response), + * these are the extra bytes already read from @a sock + * by MHD. The application should treat these as if + * it had read them from @a sock. + * @param extra_in_size number of bytes in @a extra_in + * @param sock socket to use for bi-directional communication + * with the client. For HTTPS, this may not be a socket + * that is directly connected to the client and thus certain + * operations (TCP-specific setsockopt(), getsockopt(), etc.) + * may not work as expected (as the socket could be from a + * socketpair() or a TCP-loopback). The application is expected + * to perform read()/recv() and write()/send() calls on the socket. + * The application may also call shutdown(), but must not call + * close() directly. + * @param urh argument for #MHD_upgrade_action()s on this @a connection. + * Applications must eventually use this callback to (indirectly) + * perform the close() action on the @a sock. + */ +static void +upgrade_handler (void *cls, + struct MHD_Connection *connection, + void *con_cls, + const char *extra_in, + size_t extra_in_size, + MHD_socket fd, + struct MHD_UpgradeResponseHandle *urh) +{ + struct ConnectedUser *cu; + pthread_t pt; + (void) cls; /* Unused. Silent compiler warning. */ + (void) connection; /* Unused. Silent compiler warning. */ + (void) con_cls; /* Unused. Silent compiler warning. */ + + /* This callback must return as soon as possible. */ + + /* allocate new connected user */ + cu = malloc (sizeof (struct ConnectedUser)); + if (NULL == cu) + abort (); + memset (cu, 0, sizeof (struct ConnectedUser)); + if (0 != extra_in_size) + { + cu->extra_in = malloc (extra_in_size); + if (NULL == cu->extra_in) + abort (); + memcpy (cu->extra_in, + extra_in, + extra_in_size); + } + cu->extra_in_size = extra_in_size; + cu->fd = fd; + cu->urh = urh; + cu->user_id = ++unique_user_id; + cu->user_name = NULL; + cu->user_name_len = 0; + + /* create thread for the new connected user */ + if (0 != pthread_create (&pt, + NULL, + &connecteduser_receive_messages, + cu)) + abort (); + pthread_detach (pt); +} + + +/** + * Function called by the MHD_daemon when the client trys to access a page. + * + * This is used to provide the main page + * (in this example HTML + CSS + JavaScript is all in the same file) + * and to initialize a websocket connection. + * The rules for the initialization of a websocket connection + * are listed near the URL check of "/ChatServerWebSocket". + * + * @param cls closure, whatever was given to #MHD_start_daemon(). + * @param connection The HTTP connection handle + * @param url The requested URL + * @param method The request method (typically "GET") + * @param version The HTTP version + * @param upload_data Given upload data for POST requests + * @param upload_data_size The size of the upload data + * @param ptr A pointer for request specific data + * @return MHD_YES on success or MHD_NO on error. + */ +static int +access_handler (void *cls, + struct MHD_Connection *connection, + const char *url, + const char *method, + const char *version, + const char *upload_data, + size_t *upload_data_size, + void **ptr) +{ + static int aptr; + struct MHD_Response *response; + int ret; + (void) cls; /* Unused. Silent compiler warning. */ + (void) version; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + *ptr = NULL; /* reset when done */ + if (0 == strcmp (url, "/")) + { + /* Default page for visiting the server */ + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE), + PAGE, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + } + else if (0 == strcmp (url, "/ChatServerWebSocket")) + { + /** + * The path for the chat has been accessed. + * For a valid WebSocket request, at least five headers are required: + * 1. "Host: <name>" + * 2. "Connection: Upgrade" + * 3. "Upgrade: websocket" + * 4. "Sec-WebSocket-Version: 13" + * 5. "Sec-WebSocket-Key: <base64 encoded value>" + * Values are compared in a case-insensitive manner. + * Furthermore it must be a HTTP/1.1 or higher GET request. + * See: https://tools.ietf.org/html/rfc6455#section-4.2.1 + * + * To ease this example we skip the following checks: + * - Whether the HTTP version is 1.1 or newer + * - Whether Connection is Upgrade, because this header may + * contain multiple values. + * - The requested Host (because we don't know) + */ + + /* check whether an websocket upgrade is requested */ + const char*value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_UPGRADE); + if ((0 == value) || (0 != stricmp (value, "websocket"))) + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + return ret; + } + + /* check the protocol version */ + value = MHD_lookup_connection_value (connection, + MHD_HEADER_KIND, + MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION); + if ((0 == value) || (0 != stricmp (value, "13"))) + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + return ret; + } + + /* read the websocket key (required for the response) */ + value = MHD_lookup_connection_value (connection, MHD_HEADER_KIND, + MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY); + if (0 == value) + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + return ret; + } + + /* generate the response accept header */ + char sec_websocket_accept[29]; + if (0 != MHD_websocket_create_accept (value, sec_websocket_accept)) + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + return ret; + } + + /* only for this example: don't accept incoming connection when we are already shutting down */ + if (0 != disconnect_all) + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_SERVICE_UNAVAILABLE, + response); + MHD_destroy_response (response); + return ret; + } + + /* create the response for upgrade */ + response = MHD_create_response_for_upgrade (&upgrade_handler, + NULL); + + /** + * For the response we need at least the following headers: + * 1. "Connection: Upgrade" + * 2. "Upgrade: websocket" + * 3. "Sec-WebSocket-Accept: <base64value>" + * The value for Sec-WebSocket-Accept can be generated with MHD_websocket_create_accept. + * It requires the value of the Sec-WebSocket-Key header of the reqeust. + * See also: https://tools.ietf.org/html/rfc6455#section-4.2.2 + */ + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "Upgrade"); + MHD_add_response_header (response, + MHD_HTTP_HEADER_UPGRADE, + "websocket"); + MHD_add_response_header (response, + MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + sec_websocket_accept); + ret = MHD_queue_response (connection, + MHD_HTTP_SWITCHING_PROTOCOLS, + response); + MHD_destroy_response (response); + } + else + { + struct MHD_Response*response = MHD_create_response_from_buffer (strlen ( + PAGE_NOT_FOUND), + PAGE_NOT_FOUND, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_NOT_FOUND, + response); + MHD_destroy_response (response); + } + return ret; +} + + +/** + * The main routine for this example + * + * This starts the daemon and waits for a key hit. + * After this it will shutdown the daemon. + */ +int +main (int argc, + char *const *argv) +{ + (void) argc; /* Unused. Silent compiler warning. */ + (void) argv; /* Unused. Silent compiler warning. */ + struct MHD_Daemon *d; + + if (0 != pthread_mutex_init (&chat_mutex, NULL)) + return 1; + +#if USE_HTTPS == 1 + const char private_key[] = "TODO: Enter your key in PEM format here"; + const char certificate[] = "TODO: Enter your certificate in PEM format here"; + d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO + | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG + | MHD_USE_TLS, + 443, + NULL, NULL, + &access_handler, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, + MHD_OPTION_HTTPS_MEM_KEY, private_key, + MHD_OPTION_HTTPS_MEM_CERT, certificate, + MHD_OPTION_END); +#else + d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO + | MHD_USE_INTERNAL_POLLING_THREAD | MHD_USE_ERROR_LOG, + 80, + NULL, NULL, + &access_handler, NULL, + MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 120, + MHD_OPTION_END); +#endif + + if (d == NULL) + return 1; + (void) getc (stdin); + + if (0 == pthread_mutex_lock (&chat_mutex)) + { + disconnect_all = 1; + for (size_t i = 0; i < user_count; ++i) + pthread_cond_signal (&users[i]->wake_up_sender); + pthread_mutex_unlock (&chat_mutex); + } + sleep (2); + if (0 == pthread_mutex_lock (&chat_mutex)) + { + for (size_t i = 0; i < user_count; ++i) + { + struct MHD_UpgradeResponseHandle*urh = users[i]->urh; + if (NULL != urh) + { + users[i]->urh = NULL; + MHD_upgrade_action (users[i]->urh, + MHD_UPGRADE_ACTION_CLOSE); + } + } + pthread_mutex_unlock (&chat_mutex); + } + sleep (2); + + /* usually we should wait here in a safe way for all threads to disconnect, */ + /* but we skip this in the example */ + + pthread_mutex_destroy (&chat_mutex); + + MHD_stop_daemon (d); + + return 0; +}