libmicrohttpd

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

commit 593bac45c20821f8101dde98779e9e5319dd2849
parent 8a163c3f73fc7d283c5d7fd0232053b02d1bf040
Author: Christian Grothoff <christian@grothoff.org>
Date:   Tue, 16 Sep 2025 11:08:31 +0200

remove websocket examples

Diffstat:
Msrc/examples/Makefile.am | 22+---------------------
Dsrc/examples/websocket_chatserver_example.c | 2352-------------------------------------------------------------------------------
Dsrc/examples/websocket_threaded_example.c | 944-------------------------------------------------------------------------------
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; -}