blob: af036d248924389f0d9d2487ce756a065bb58660 [file] [log] [blame]
/*
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 <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 |= (data[i] >> 4) & 0x0F;
}
opt[ret++] = cvt[(int) c];
if (i < len)
{
c = (data[i] << 2) & 0x3F;
if (++i < len)
{
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);
MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_VERSION,
WS_SEC_WEBSOCKET_VERSION);
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;
pthread_mutex_lock (&MUTEX);
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);
}
}
pthread_mutex_unlock (&MUTEX);
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 = 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;
}
}
}
pthread_mutex_lock (&MUTEX);
for (i = 0; i < MAX_CLIENTS; i++)
{
if (CLIENT_SOCKS[i] == ws->sock)
{
CLIENT_SOCKS[i] = MHD_INVALID_SOCKET;
break;
}
}
pthread_mutex_unlock (&MUTEX);
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;
pthread_mutex_lock (&MUTEX);
for (i = 0; i < MAX_CLIENTS; i++)
{
if (MHD_INVALID_SOCKET == CLIENT_SOCKS[i])
{
CLIENT_SOCKS[i] = ws->sock;
sock_overflow = MHD_NO;
break;
}
}
pthread_mutex_unlock (&MUTEX);
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);
MHD_add_response_header (res, MHD_HTTP_HEADER_UPGRADE, WS_UPGRADE_VALUE);
MHD_add_response_header (res, MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT,
ws_ac_value);
free (ws_ac_value);
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;
}