commit 593bac45c20821f8101dde98779e9e5319dd2849
parent 8a163c3f73fc7d283c5d7fd0232053b02d1bf040
Author: Christian Grothoff <christian@grothoff.org>
Date: Tue, 16 Sep 2025 11:08:31 +0200
remove websocket examples
Diffstat:
3 files changed, 1 insertion(+), 3317 deletions(-)
diff --git a/src/examples/Makefile.am b/src/examples/Makefile.am
@@ -39,11 +39,6 @@ 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
@@ -87,8 +82,7 @@ endif
if HAVE_POSIX_THREADS
if ENABLE_UPGRADE
noinst_PROGRAMS += \
- upgrade_example \
- websocket_threaded_example
+ upgrade_example
endif
endif
@@ -125,14 +119,6 @@ upgrade_example_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
$(PTHREAD_LIBS)
-websocket_threaded_example_SOURCES = \
- websocket_threaded_example.c
-websocket_threaded_example_CFLAGS = \
- $(PTHREAD_CFLAGS) $(AM_CFLAGS)
-websocket_threaded_example_LDADD = \
- $(top_builddir)/src/microhttpd/libmicrohttpd.la \
- $(PTHREAD_LIBS)
-
timeout_SOURCES = \
timeout.c
timeout_LDADD = \
@@ -149,12 +135,6 @@ json_echo_LDADD = \
$(top_builddir)/src/microhttpd/libmicrohttpd.la \
-ljansson
-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/websocket_chatserver_example.c b/src/examples/websocket_chatserver_example.c
@@ -1,2352 +0,0 @@
-/*
- 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 <errno.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"
-
-/*
- On Windows we will use stricmp instead of strcasecmp (strcasecmp is undefined there).
-*/
-#define strcasecmp stricmp
-
-#else
-/*
- On Unix systems we can use pthread.
-*/
-#include <pthread.h>
-#endif
-
-
-/*
- * Specify 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 corresponding 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 whether 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)
- abort ();
- if ((flags & ~O_NONBLOCK) != flags)
- if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
- abort ();
-#elif defined(MHD_WINSOCK_SOCKETS)
- unsigned long flags = 0;
-
- if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
- abort ();
-#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))
- abort ();
- for (off = 0; off < len; off += ret)
- {
- ret = send (cu->fd,
- &buf[off],
- (int) (len - off),
- 0);
- if (0 > ret)
- {
- if (EAGAIN == errno)
- {
- ret = 0;
- continue;
- }
- break;
- }
- if (0 == ret)
- break;
- }
- if (0 != pthread_mutex_unlock (&cu->send_mutex))
- abort ();
-}
-
-
-/**
- * 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))
- abort ();
- }
-
- /* 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)
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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)
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- }
- 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))
- abort ();
- }
-
- /* update the clean counter and check whether we need cleaning */
- ++clean_count;
- if (CLEANUP_LIMIT > clean_count)
- {
- /* no cleanup required */
- if (0 != needs_lock)
- {
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- }
- 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)
- {
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- }
- 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];
- snprintf (user_index, 32, "%d", (int) cu->user_id);
- 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))
- abort ();
- /* inform the other chat users about the new user */
- if (0 != chat_addmessage (0,
- 0,
- data,
- data_len,
- 0,
- 0))
- {
- free (data);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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 */
- snprintf (user_index, 32, "%d", (int) cu->user_id);
- 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))
- abort ();
- /* 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
-
- 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))
- abort ();
-
- /* 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 == strcasecmp (users[i]->user_name, new_name)))
- {
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- return 2;
- }
- }
- }
-
- /* generate the notification message */
- char user_index[32];
- snprintf (user_index, 32, "%d", (int) cu->user_id);
- 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);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
-
- 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 == strcasecmp (frame_data, "private"))
- {
- /* private means private message */
- command = 1;
- }
- else if (0 == strcasecmp (frame_data, "name"))
- {
- /* name means chat user rename */
- command = 2;
- }
- else if (0 == strcasecmp (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];
- snprintf (user_index, 32, "%d", (int) cu->user_id);
- 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];
- snprintf (user_index, 32, "%d", (int) cu->user_id);
- 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))
- abort ();
- /* 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);
- }
- }
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- }
- 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|");
- snprintf (result_text + 5, 235, "%d", (int) cu->user_id);
- strcat (result_text,
- "|");
- snprintf (result_text + strlen (result_text), 240
- - strlen (result_text), "%d", (int) ping);
- 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))
- abort ();
- 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);
- }
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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. */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- return NULL;
- }
- else if (1 == cu->ping_status)
- {
- /* A pending ping is requested */
- ++cu->ping_counter;
- strcpy (cu->ping_message,
- "libmicrohttpdchatserverpingdata");
- snprintf (cu->ping_message + 31, 97, "%d", (int) cu->ping_counter);
- 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- send_all (cu,
- frame_data,
- frame_len);
- if (0 != pthread_mutex_lock (&chat_mutex))
- abort ();
- }
- 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 */
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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))
- abort ();
- /* 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");
- snprintf (user_name + 4, 28, "%d", (int) cu->user_id);
- 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))
- abort ();
- 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];
- snprintf (user_index, 32, "%d", (int) users[i]->user_id);
- 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;
- }
- }
- }
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
-
- /* 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 with a slash, " \
- "are regular messages and will be sent to the selected user.\n\n" \
- "Have fun!";
- 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))
- abort ();
- cu->disconnect = 1;
- pthread_cond_signal (&cu->wake_up_sender);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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))
- abort ();
- cu->disconnect = 1;
- pthread_cond_signal (&cu->wake_up_sender);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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))
- abort ();
- cu->disconnect = 1;
- pthread_cond_signal (&cu->wake_up_sender);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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 req_cls last value left in `req_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 *req_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) req_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 tries 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 req_cls A pointer for request specific data
- * @return MHD_YES on success or MHD_NO on error.
- */
-static enum MHD_Result
-access_handler (void *cls,
- struct MHD_Connection *connection,
- const char *url,
- const char *method,
- const char *version,
- const char *upload_data,
- size_t *upload_data_size,
- void **req_cls)
-{
- 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 != *req_cls)
- {
- /* do never respond on first call */
- *req_cls = &aptr;
- return MHD_YES;
- }
- *req_cls = NULL; /* reset when done */
- if (0 == strcmp (url, "/"))
- {
- /* Default page for visiting the server */
- struct MHD_Response *response;
- response = MHD_create_response_from_buffer_static (strlen (PAGE),
- PAGE);
- 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 make this example portable we skip the Host check
- */
-
- char is_valid = 1;
- const char *value = NULL;
- char sec_websocket_accept[29];
-
- /* check whether an websocket upgrade is requested */
- if (0 != MHD_websocket_check_http_version (version))
- {
- is_valid = 0;
- }
- value = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONNECTION);
- if (0 != MHD_websocket_check_connection_header (value))
- {
- is_valid = 0;
- }
- value = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_UPGRADE);
- if (0 != MHD_websocket_check_upgrade_header (value))
- {
- is_valid = 0;
- }
- value = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
- if (0 != MHD_websocket_check_version_header (value))
- {
- is_valid = 0;
- }
- value = MHD_lookup_connection_value (connection,
- MHD_HEADER_KIND,
- MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY);
- if (0 != MHD_websocket_create_accept_header (value, sec_websocket_accept))
- {
- is_valid = 0;
- }
-
- if (1 == is_valid)
- {
- /* 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_header.
- * It requires the value of the Sec-WebSocket-Key header of the request.
- * See also: https://tools.ietf.org/html/rfc6455#section-4.2.2
- */
- 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
- {
- /* return error page */
- struct MHD_Response *response;
- response =
- MHD_create_response_from_buffer_static ( \
- strlen (PAGE_INVALID_WEBSOCKET_REQUEST),
- PAGE_INVALID_WEBSOCKET_REQUEST);
- ret = MHD_queue_response (connection,
- MHD_HTTP_BAD_REQUEST,
- response);
- MHD_destroy_response (response);
- }
- }
- else
- {
- struct MHD_Response *response;
- response = MHD_create_response_from_buffer_static (strlen (PAGE_NOT_FOUND),
- PAGE_NOT_FOUND);
- 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 (NULL == d)
- return 1;
- (void) getc (stdin);
-
- if (0 != pthread_mutex_lock (&chat_mutex))
- abort ();
- disconnect_all = 1;
- for (size_t i = 0; i < user_count; ++i)
- pthread_cond_signal (&users[i]->wake_up_sender);
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- sleep (2);
- if (0 != pthread_mutex_lock (&chat_mutex))
- abort ();
- 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);
- }
- }
- if (0 != pthread_mutex_unlock (&chat_mutex))
- abort ();
- 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;
-}
diff --git a/src/examples/websocket_threaded_example.c b/src/examples/websocket_threaded_example.c
@@ -1,944 +0,0 @@
-/*
- This file is part of libmicrohttpd
- Copyright (C) 2020 Christian Grothoff, Silvio Clecio (and other
- contributing authors)
- Copyright (C) 2020-2022 Evgeny Grin (Karlson2k)
-
- 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_threaded_example.c
- * @brief example for how to provide a tiny threaded websocket server
- * @author Silvio Clecio (silvioprog)
- * @author Karlson2k (Evgeny Grin)
- */
-
-/* TODO: allow to send large messages. */
-
-#include "platform.h"
-#include <errno.h>
-#include <pthread.h>
-#include <microhttpd.h>
-
-#define CHAT_PAGE \
- "<html>\n" \
- "<head>\n" \
- "<title>WebSocket chat</title>\n" \
- "<script>\n" \
- "document.addEventListener('DOMContentLoaded', function() {\n" \
- " const ws = new WebSocket('ws:/" "/' + window.location.host);\n" \
- " const btn = document.getElementById('send');\n" \
- " const msg = document.getElementById('msg');\n" \
- " const log = document.getElementById('log');\n" \
- " ws.onopen = function() {\n" \
- " log.value += 'Connected\\n';\n" \
- " };\n" \
- " ws.onclose = function() {\n" \
- " log.value += 'Disconnected\\n';\n" \
- " };\n" \
- " ws.onmessage = function(ev) {\n" \
- " log.value += ev.data + '\\n';\n" \
- " };\n" \
- " btn.onclick = function() {\n" \
- " log.value += '<You>: ' + msg.value + '\\n';\n" \
- " ws.send(msg.value);\n" \
- " };\n" \
- " msg.onkeyup = function(ev) {\n" \
- " if (ev.keyCode === 13) {\n" \
- " ev.preventDefault();\n" \
- " ev.stopPropagation();\n" \
- " btn.click();\n" \
- " msg.value = '';\n" \
- " }\n" \
- " };\n" \
- "});\n" \
- "</script>\n" \
- "</head>\n" \
- "<body>\n" \
- "<input type='text' id='msg' autofocus/>\n" \
- "<input type='button' id='send' value='Send' /><br /><br />\n" \
- "<textarea id='log' rows='20' cols='28'></textarea>\n" \
- "</body>\n" \
- "</html>"
-#define BAD_REQUEST_PAGE \
- "<html>\n" \
- "<head>\n" \
- "<title>WebSocket chat</title>\n" \
- "</head>\n" \
- "<body>\n" \
- "Bad Request\n" \
- "</body>\n" \
- "</html>\n"
-#define UPGRADE_REQUIRED_PAGE \
- "<html>\n" \
- "<head>\n" \
- "<title>WebSocket chat</title>\n" \
- "</head>\n" \
- "<body>\n" \
- "Upgrade required\n" \
- "</body>\n" \
- "</html>\n"
-
-#define WS_SEC_WEBSOCKET_VERSION "13"
-#define WS_UPGRADE_VALUE "websocket"
-#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
-#define WS_GUID_LEN 36
-#define WS_KEY_LEN 24
-#define WS_KEY_GUID_LEN ((WS_KEY_LEN) + (WS_GUID_LEN))
-#define WS_FIN 128
-#define WS_OPCODE_TEXT_FRAME 1
-#define WS_OPCODE_CON_CLOSE_FRAME 8
-
-#define MAX_CLIENTS 10
-
-static MHD_socket CLIENT_SOCKS[MAX_CLIENTS];
-
-static pthread_mutex_t MUTEX = PTHREAD_MUTEX_INITIALIZER;
-
-struct WsData
-{
- struct MHD_UpgradeResponseHandle *urh;
- MHD_socket sock;
-};
-
-
-/********** begin SHA-1 **********/
-
-
-#define SHA1HashSize 20
-
-#define SHA1CircularShift(bits, word) \
- (((word) << (bits)) | ((word) >> (32 - (bits))))
-
-enum SHA1_RESULT
-{
- SHA1_RESULT_SUCCESS = 0,
- SHA1_RESULT_NULL = 1,
- SHA1_RESULT_STATE_ERROR = 2
-};
-
-struct SHA1Context
-{
- uint32_t intermediate_hash[SHA1HashSize / 4];
- uint32_t length_low;
- uint32_t length_high;
- int_least16_t message_block_index;
- unsigned char message_block[64];
- int computed;
- int corrupted;
-};
-
-static void
-SHA1ProcessMessageBlock (struct SHA1Context *context)
-{
- const uint32_t K[] = { 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
- int i;
- uint32_t temp;
- uint32_t W[80];
- uint32_t A, B, C, D, E;
-
- for (i = 0; i < 16; i++)
- {
- W[i] = ((uint32_t) context->message_block[i * 4]) << 24;
- W[i] |= ((uint32_t) context->message_block[i * 4 + 1]) << 16;
- W[i] |= ((uint32_t) context->message_block[i * 4 + 2]) << 8;
- W[i] |= context->message_block[i * 4 + 3];
- }
- for (i = 16; i < 80; i++)
- {
- W[i]
- = SHA1CircularShift (1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
- }
- A = context->intermediate_hash[0];
- B = context->intermediate_hash[1];
- C = context->intermediate_hash[2];
- D = context->intermediate_hash[3];
- E = context->intermediate_hash[4];
- for (i = 0; i < 20; i++)
- {
- temp = SHA1CircularShift (5, A) + ((B & C) | ((~B) & D)) + E + W[i]
- + K[0];
- E = D;
- D = C;
- C = SHA1CircularShift (30, B);
- B = A;
- A = temp;
- }
- for (i = 20; i < 40; i++)
- {
- temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[1];
- E = D;
- D = C;
- C = SHA1CircularShift (30, B);
- B = A;
- A = temp;
- }
- for (i = 40; i < 60; i++)
- {
- temp = SHA1CircularShift (5, A) + ((B & C) | (B & D) | (C & D)) + E
- + W[i] + K[2];
- E = D;
- D = C;
- C = SHA1CircularShift (30, B);
- B = A;
- A = temp;
- }
- for (i = 60; i < 80; i++)
- {
- temp = SHA1CircularShift (5, A) + (B ^ C ^ D) + E + W[i] + K[3];
- E = D;
- D = C;
- C = SHA1CircularShift (30, B);
- B = A;
- A = temp;
- }
- context->intermediate_hash[0] += A;
- context->intermediate_hash[1] += B;
- context->intermediate_hash[2] += C;
- context->intermediate_hash[3] += D;
- context->intermediate_hash[4] += E;
- context->message_block_index = 0;
-}
-
-
-static void
-SHA1PadMessage (struct SHA1Context *context)
-{
- if (context->message_block_index > 55)
- {
- context->message_block[context->message_block_index++] = 0x80;
- while (context->message_block_index < 64)
- {
- context->message_block[context->message_block_index++] = 0;
- }
- SHA1ProcessMessageBlock (context);
- while (context->message_block_index < 56)
- {
- context->message_block[context->message_block_index++] = 0;
- }
- }
- else
- {
- context->message_block[context->message_block_index++] = 0x80;
- while (context->message_block_index < 56)
- {
- context->message_block[context->message_block_index++] = 0;
- }
- }
- context->message_block[56] = (unsigned char) (context->length_high >> 24);
- context->message_block[57] = (unsigned char) (context->length_high >> 16);
- context->message_block[58] = (unsigned char) (context->length_high >> 8);
- context->message_block[59] = (unsigned char) (context->length_high);
- context->message_block[60] = (unsigned char) (context->length_low >> 24);
- context->message_block[61] = (unsigned char) (context->length_low >> 16);
- context->message_block[62] = (unsigned char) (context->length_low >> 8);
- context->message_block[63] = (unsigned char) (context->length_low);
- SHA1ProcessMessageBlock (context);
-}
-
-
-static enum SHA1_RESULT
-SHA1Reset (struct SHA1Context *context)
-{
- if (! context)
- {
- return SHA1_RESULT_NULL;
- }
- context->length_low = 0;
- context->length_high = 0;
- context->message_block_index = 0;
- context->intermediate_hash[0] = 0x67452301;
- context->intermediate_hash[1] = 0xEFCDAB89;
- context->intermediate_hash[2] = 0x98BADCFE;
- context->intermediate_hash[3] = 0x10325476;
- context->intermediate_hash[4] = 0xC3D2E1F0;
- context->computed = 0;
- context->corrupted = 0;
- return SHA1_RESULT_SUCCESS;
-}
-
-
-static enum SHA1_RESULT
-SHA1Result (struct SHA1Context *context, unsigned char
- Message_Digest[SHA1HashSize])
-{
- int i;
-
- if (! context || ! Message_Digest)
- {
- return SHA1_RESULT_NULL;
- }
- if (context->corrupted)
- {
- return SHA1_RESULT_STATE_ERROR;
- }
- if (! context->computed)
- {
- SHA1PadMessage (context);
- for (i = 0; i < 64; ++i)
- {
- context->message_block[i] = 0;
- }
- context->length_low = 0;
- context->length_high = 0;
- context->computed = 1;
- }
- for (i = 0; i < SHA1HashSize; ++i)
- {
- Message_Digest[i]
- = (unsigned char) (context->intermediate_hash[i >> 2]
- >> 8 * (3 - (i & 0x03)));
- }
- return SHA1_RESULT_SUCCESS;
-}
-
-
-static enum SHA1_RESULT
-SHA1Input (struct SHA1Context *context, const unsigned char *message_array,
- unsigned length)
-{
- if (! length)
- {
- return SHA1_RESULT_SUCCESS;
- }
- if (! context || ! message_array)
- {
- return SHA1_RESULT_NULL;
- }
- if (context->computed)
- {
- context->corrupted = 1;
- return SHA1_RESULT_STATE_ERROR;
- }
- if (context->corrupted)
- {
- return SHA1_RESULT_STATE_ERROR;
- }
- while (length-- && ! context->corrupted)
- {
- context->message_block[context->message_block_index++]
- = (*message_array & 0xFF);
- context->length_low += 8;
- if (context->length_low == 0)
- {
- context->length_high++;
- if (context->length_high == 0)
- {
- context->corrupted = 1;
- }
- }
- if (context->message_block_index == 64)
- {
- SHA1ProcessMessageBlock (context);
- }
- message_array++;
- }
- return SHA1_RESULT_SUCCESS;
-}
-
-
-/********** end SHA-1 **********/
-
-
-/********** begin Base64 **********/
-
-
-static ssize_t
-BASE64Encode (const void *in, size_t len, char **output)
-{
-#define FILLCHAR '='
- const char *cvt = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789+/";
- const char *data = in;
- char *opt;
- ssize_t ret;
- size_t i;
- char c;
- ret = 0;
-
- opt = malloc (2 + (len * 4 / 3) + 8);
- if (NULL == opt)
- {
- return -1;
- }
- for (i = 0; i < len; ++i)
- {
- c = (data[i] >> 2) & 0x3F;
- opt[ret++] = cvt[(int) c];
- c = (data[i] << 4) & 0x3F;
- if (++i < len)
- {
- c = (char) (c | ((data[i] >> 4) & 0x0F));
- }
- opt[ret++] = cvt[(int) c];
- if (i < len)
- {
- c = (char) (c | ((data[i] << 2) & 0x3F));
- if (++i < len)
- {
- c = (char) (c | ((data[i] >> 6) & 0x03));
- }
- opt[ret++] = cvt[(int) c];
- }
- else
- {
- ++i;
- opt[ret++] = FILLCHAR;
- }
- if (i < len)
- {
- c = data[i] & 0x3F;
- opt[ret++] = cvt[(int) c];
- }
- else
- {
- opt[ret++] = FILLCHAR;
- }
- }
- *output = opt;
- return ret;
-}
-
-
-/********** end Base64 **********/
-
-
-static enum MHD_Result
-is_websocket_request (struct MHD_Connection *con, const char *upg_header,
- const char *con_header)
-{
-
- (void) con; /* Unused. Silent compiler warning. */
-
- return ((upg_header != NULL) && (con_header != NULL)
- && (0 == strcmp (upg_header, WS_UPGRADE_VALUE))
- && (NULL != strstr (con_header, "Upgrade")))
- ? MHD_YES
- : MHD_NO;
-}
-
-
-static enum MHD_Result
-send_chat_page (struct MHD_Connection *con)
-{
- struct MHD_Response *res;
- enum MHD_Result ret;
-
- res = MHD_create_response_from_buffer_static (strlen (CHAT_PAGE),
- (const void *) CHAT_PAGE);
- ret = MHD_queue_response (con, MHD_HTTP_OK, res);
- MHD_destroy_response (res);
- return ret;
-}
-
-
-static enum MHD_Result
-send_bad_request (struct MHD_Connection *con)
-{
- struct MHD_Response *res;
- enum MHD_Result ret;
-
- res =
- MHD_create_response_from_buffer_static (strlen (BAD_REQUEST_PAGE),
- (const void *) BAD_REQUEST_PAGE);
- ret = MHD_queue_response (con, MHD_HTTP_BAD_REQUEST, res);
- MHD_destroy_response (res);
- return ret;
-}
-
-
-static enum MHD_Result
-send_upgrade_required (struct MHD_Connection *con)
-{
- struct MHD_Response *res;
- enum MHD_Result ret;
-
- res =
- MHD_create_response_from_buffer_static (strlen (UPGRADE_REQUIRED_PAGE),
- (const void *)
- UPGRADE_REQUIRED_PAGE);
- if (MHD_YES !=
- MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION,
- WS_SEC_WEBSOCKET_VERSION))
- {
- MHD_destroy_response (res);
- return MHD_NO;
- }
- ret = MHD_queue_response (con, MHD_HTTP_UPGRADE_REQUIRED, res);
- MHD_destroy_response (res);
- return ret;
-}
-
-
-static enum MHD_Result
-ws_get_accept_value (const char *key, char **val)
-{
- struct SHA1Context ctx;
- unsigned char hash[SHA1HashSize];
- char *str;
- ssize_t len;
-
- if ( (NULL == key) || (WS_KEY_LEN != strlen (key)))
- {
- return MHD_NO;
- }
- str = malloc (WS_KEY_LEN + WS_GUID_LEN + 1);
- if (NULL == str)
- {
- return MHD_NO;
- }
- strncpy (str, key, (WS_KEY_LEN + 1));
- strncpy (str + WS_KEY_LEN, WS_GUID, WS_GUID_LEN + 1);
- SHA1Reset (&ctx);
- SHA1Input (&ctx, (const unsigned char *) str, WS_KEY_GUID_LEN);
- if (SHA1_RESULT_SUCCESS != SHA1Result (&ctx, hash))
- {
- free (str);
- return MHD_NO;
- }
- free (str);
- len = BASE64Encode (hash, SHA1HashSize, val);
- if (-1 == len)
- {
- return MHD_NO;
- }
- (*val)[len] = '\0';
- return MHD_YES;
-}
-
-
-static void
-make_blocking (MHD_socket fd)
-{
-#if defined(MHD_POSIX_SOCKETS)
- int flags;
-
- flags = fcntl (fd, F_GETFL);
- if (-1 == flags)
- abort ();
- if ((flags & ~O_NONBLOCK) != flags)
- if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK))
- abort ();
-#elif defined(MHD_WINSOCK_SOCKETS)
- unsigned long flags = 0;
-
- if (0 != ioctlsocket (fd, (int) FIONBIO, &flags))
- abort ();
-#endif /* MHD_WINSOCK_SOCKETS */
-}
-
-
-static size_t
-send_all (MHD_socket sock, const unsigned char *buf, size_t len)
-{
- ssize_t ret;
- size_t off;
-
- for (off = 0; off < len; off += (size_t) ret)
- {
-#if ! defined(_WIN32) || defined(__CYGWIN__)
- ret = send (sock, (const void *) &buf[off], len - off, 0);
-#else /* Native W32 */
- ret = send (sock, (const void *) &buf[off], (int) (len - off), 0);
-#endif /* Native W32 */
- if (0 > ret)
- {
- if (EAGAIN == errno)
- {
- ret = 0;
- continue;
- }
- break;
- }
- if (0 == ret)
- {
- break;
- }
- }
- return off;
-}
-
-
-static ssize_t
-ws_send_frame (MHD_socket sock, const char *msg, size_t length)
-{
- unsigned char *response;
- unsigned char frame[10];
- unsigned char idx_first_rdata;
- size_t idx_response;
- size_t output;
- MHD_socket isock;
- size_t i;
-
- frame[0] = (WS_FIN | WS_OPCODE_TEXT_FRAME);
- if (length <= 125)
- {
- frame[1] = length & 0x7F;
- idx_first_rdata = 2;
- }
-#if SIZEOF_SIZE_T > 4
- else if (0xFFFF < length)
- {
- frame[1] = 127;
- frame[2] = (unsigned char) ((length >> 56) & 0xFF);
- frame[3] = (unsigned char) ((length >> 48) & 0xFF);
- frame[4] = (unsigned char) ((length >> 40) & 0xFF);
- frame[5] = (unsigned char) ((length >> 32) & 0xFF);
- frame[6] = (unsigned char) ((length >> 24) & 0xFF);
- frame[7] = (unsigned char) ((length >> 16) & 0xFF);
- frame[8] = (unsigned char) ((length >> 8) & 0xFF);
- frame[9] = (unsigned char) (length & 0xFF);
- idx_first_rdata = 10;
- }
-#endif /* SIZEOF_SIZE_T > 4 */
- else
- {
- frame[1] = 126;
- frame[2] = (length >> 8) & 0xFF;
- frame[3] = length & 0xFF;
- idx_first_rdata = 4;
- }
- idx_response = 0;
- response = malloc (idx_first_rdata + length + 1);
- if (NULL == response)
- {
- return -1;
- }
- for (i = 0; i < idx_first_rdata; i++)
- {
- response[i] = frame[i];
- idx_response++;
- }
- for (i = 0; i < length; i++)
- {
- response[idx_response] = (unsigned char) msg[i];
- idx_response++;
- }
- response[idx_response] = '\0';
- output = 0;
- if (0 != pthread_mutex_lock (&MUTEX))
- abort ();
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- isock = CLIENT_SOCKS[i];
- if ((isock != MHD_INVALID_SOCKET) && (isock != sock))
- {
- output += send_all (isock, response, idx_response);
- }
- }
- if (0 != pthread_mutex_unlock (&MUTEX))
- abort ();
- free (response);
- return (ssize_t) output;
-}
-
-
-static unsigned char *
-ws_receive_frame (unsigned char *frame, ssize_t *length, int *type)
-{
- unsigned char masks[4];
- unsigned char mask;
- unsigned char *msg;
- unsigned char flength;
- unsigned char idx_first_mask;
- unsigned char idx_first_data;
- size_t data_length;
- int i;
- int j;
-
- msg = NULL;
- if (frame[0] == (WS_FIN | WS_OPCODE_TEXT_FRAME))
- {
- *type = WS_OPCODE_TEXT_FRAME;
- idx_first_mask = 2;
- mask = frame[1];
- flength = mask & 0x7F;
- if (flength == 126)
- {
- idx_first_mask = 4;
- }
- else if (flength == 127)
- {
- idx_first_mask = 10;
- }
- idx_first_data = (unsigned char) (idx_first_mask + 4);
- data_length = (size_t) *length - idx_first_data;
- masks[0] = frame[idx_first_mask + 0];
- masks[1] = frame[idx_first_mask + 1];
- masks[2] = frame[idx_first_mask + 2];
- masks[3] = frame[idx_first_mask + 3];
- msg = malloc (data_length + 1);
- if (NULL != msg)
- {
- for (i = idx_first_data, j = 0; i < *length; i++, j++)
- {
- msg[j] = frame[i] ^ masks[j % 4];
- }
- *length = (ssize_t) data_length;
- msg[j] = '\0';
- }
- }
- else if (frame[0] == (WS_FIN | WS_OPCODE_CON_CLOSE_FRAME))
- {
- *type = WS_OPCODE_CON_CLOSE_FRAME;
- }
- else
- {
- *type = frame[0] & 0x0F;
- }
- return msg;
-}
-
-
-static void *
-run_usock (void *cls)
-{
- struct WsData *ws = cls;
- struct MHD_UpgradeResponseHandle *urh = ws->urh;
- unsigned char buf[2048];
- unsigned char *msg;
- char *text;
- ssize_t got;
- int type;
- int i;
-
- make_blocking (ws->sock);
- while (1)
- {
- got = recv (ws->sock, (void *) buf, sizeof (buf), 0);
- if (0 >= got)
- {
- break;
- }
- msg = ws_receive_frame (buf, &got, &type);
- if (NULL == msg)
- {
- break;
- }
- if (type == WS_OPCODE_TEXT_FRAME)
- {
- ssize_t sent;
- int buf_size;
- buf_size = snprintf (NULL, 0, "User#%d: %s", (int) ws->sock, msg);
- if (0 < buf_size)
- {
- text = malloc ((size_t) buf_size + 1);
- if (NULL != text)
- {
- if (snprintf (text, (size_t) buf_size + 1,
- "User#%d: %s", (int) ws->sock, msg) == buf_size)
- sent = ws_send_frame (ws->sock, text, (size_t) buf_size);
- else
- sent = -1;
- free (text);
- }
- else
- sent = -1;
- }
- else
- sent = -1;
- free (msg);
- if (-1 == sent)
- {
- break;
- }
- }
- else
- {
- if (type == WS_OPCODE_CON_CLOSE_FRAME)
- {
- free (msg);
- break;
- }
- }
- }
- if (0 != pthread_mutex_lock (&MUTEX))
- abort ();
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- if (CLIENT_SOCKS[i] == ws->sock)
- {
- CLIENT_SOCKS[i] = MHD_INVALID_SOCKET;
- break;
- }
- }
- if (0 != pthread_mutex_unlock (&MUTEX))
- abort ();
- free (ws);
- MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE);
- return NULL;
-}
-
-
-static void
-uh_cb (void *cls, struct MHD_Connection *con, void *req_cls,
- const char *extra_in, size_t extra_in_size, MHD_socket sock,
- struct MHD_UpgradeResponseHandle *urh)
-{
- struct WsData *ws;
- pthread_t pt;
- int sock_overflow;
- int i;
-
- (void) cls; /* Unused. Silent compiler warning. */
- (void) con; /* Unused. Silent compiler warning. */
- (void) req_cls; /* Unused. Silent compiler warning. */
- (void) extra_in; /* Unused. Silent compiler warning. */
- (void) extra_in_size; /* Unused. Silent compiler warning. */
-
- ws = malloc (sizeof (struct WsData));
- if (NULL == ws)
- abort ();
- memset (ws, 0, sizeof (struct WsData));
- ws->sock = sock;
- ws->urh = urh;
- sock_overflow = MHD_YES;
- if (0 != pthread_mutex_lock (&MUTEX))
- abort ();
- for (i = 0; i < MAX_CLIENTS; i++)
- {
- if (MHD_INVALID_SOCKET == CLIENT_SOCKS[i])
- {
- CLIENT_SOCKS[i] = ws->sock;
- sock_overflow = MHD_NO;
- break;
- }
- }
- if (0 != pthread_mutex_unlock (&MUTEX))
- abort ();
- if (sock_overflow)
- {
- free (ws);
- MHD_upgrade_action (urh, MHD_UPGRADE_ACTION_CLOSE);
- return;
- }
- if (0 != pthread_create (&pt, NULL, &run_usock, ws))
- abort ();
- /* Note that by detaching like this we make it impossible to ensure
- a clean shutdown, as the we stop the daemon even if a worker thread
- is still running. Alas, this is a simple example... */
- pthread_detach (pt);
-}
-
-
-static enum MHD_Result
-ahc_cb (void *cls, struct MHD_Connection *con, const char *url,
- const char *method, const char *version, const char *upload_data,
- size_t *upload_data_size, void **req_cls)
-{
- struct MHD_Response *res;
- const char *upg_header;
- const char *con_header;
- const char *ws_version_header;
- const char *ws_key_header;
- char *ws_ac_value;
- enum MHD_Result ret;
- size_t key_size;
-
- (void) cls; /* Unused. Silent compiler warning. */
- (void) url; /* Unused. Silent compiler warning. */
- (void) upload_data; /* Unused. Silent compiler warning. */
- (void) upload_data_size; /* Unused. Silent compiler warning. */
-
- if (NULL == *req_cls)
- {
- *req_cls = (void *) 1;
- return MHD_YES;
- }
- *req_cls = NULL;
- upg_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
- MHD_HTTP_HEADER_UPGRADE);
- con_header = MHD_lookup_connection_value (con, MHD_HEADER_KIND,
- MHD_HTTP_HEADER_CONNECTION);
- if (MHD_NO == is_websocket_request (con, upg_header, con_header))
- {
- return send_chat_page (con);
- }
- if ((0 != strcmp (method, MHD_HTTP_METHOD_GET))
- || (0 != strcmp (version, MHD_HTTP_VERSION_1_1)))
- {
- return send_bad_request (con);
- }
- ws_version_header =
- MHD_lookup_connection_value (con, MHD_HEADER_KIND,
- MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION);
- if ((NULL == ws_version_header)
- || (0 != strcmp (ws_version_header, WS_SEC_WEBSOCKET_VERSION)))
- {
- return send_upgrade_required (con);
- }
- ret = MHD_lookup_connection_value_n (con, MHD_HEADER_KIND,
- MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY,
- strlen (
- MHD_HTTP_HEADER_SEC_WEBSOCKET_KEY),
- &ws_key_header, &key_size);
- if ((MHD_NO == ret) || (key_size != WS_KEY_LEN))
- {
- return send_bad_request (con);
- }
- ret = ws_get_accept_value (ws_key_header, &ws_ac_value);
- if (MHD_NO == ret)
- {
- return ret;
- }
- res = MHD_create_response_for_upgrade (&uh_cb, NULL);
- if (MHD_YES !=
- MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
- ws_ac_value))
- {
- free (ws_ac_value);
- MHD_destroy_response (res);
- return MHD_NO;
- }
- free (ws_ac_value);
- if (MHD_YES !=
- MHD_add_response_header (res, MHD_HTTP_HEADER_UPGRADE, WS_UPGRADE_VALUE))
- {
- MHD_destroy_response (res);
- return MHD_NO;
- }
- ret = MHD_queue_response (con, MHD_HTTP_SWITCHING_PROTOCOLS, res);
- MHD_destroy_response (res);
- return ret;
-}
-
-
-int
-main (int argc, char *const *argv)
-{
- struct MHD_Daemon *d;
- unsigned int port;
- size_t i;
- if ( (argc != 2) ||
- (1 != sscanf (argv[1], "%u", &port)) ||
- (65535 < port) )
- {
- printf ("%s PORT\n", argv[0]);
- return 1;
- }
- d = MHD_start_daemon (MHD_ALLOW_UPGRADE | MHD_USE_AUTO_INTERNAL_THREAD
- | MHD_USE_ERROR_LOG,
- (uint16_t) port, NULL, NULL,
- &ahc_cb, NULL, MHD_OPTION_END);
- if (NULL == d)
- return 1;
- for (i = 0; i < sizeof(CLIENT_SOCKS) / sizeof(CLIENT_SOCKS[0]); ++i)
- CLIENT_SOCKS[i] = MHD_INVALID_SOCKET;
- (void) getc (stdin);
- MHD_stop_daemon (d);
- return 0;
-}