diff options
Diffstat (limited to 'doc/examples/websocket.c')
-rw-r--r-- | doc/examples/websocket.c | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/doc/examples/websocket.c b/doc/examples/websocket.c new file mode 100644 index 00000000..39995479 --- /dev/null +++ b/doc/examples/websocket.c @@ -0,0 +1,446 @@ +/* Feel free to use this example code in any way + you see fit (Public Domain) */ + +#include <sys/types.h> +#ifndef _WIN32 +#include <sys/select.h> +#include <sys/socket.h> +#include <fcntl.h> +#else +#include <winsock2.h> +#endif +#include <microhttpd.h> +#include <microhttpd_ws.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> + +#define PORT 80 + +#define PAGE \ + "<!DOCTYPE html>\n" \ + "<html>\n" \ + "<head>\n" \ + "<meta charset=\"UTF-8\">\n" \ + "<title>Websocket Demo</title>\n" \ + "<script>\n" \ + "\n" \ + "let url = 'ws' + (window.location.protocol === 'https:' ? 's' : '')" \ + " + '://' +\n" \ + " window.location.host + '/chat';\n" \ + "let socket = null;\n" \ + "\n" \ + "window.onload = function(event) {\n" \ + " socket = new WebSocket(url);\n" \ + " socket.onopen = function(event) {\n" \ + " document.write('The websocket connection has been " \ + "established.<br>');\n" \ + "\n" \ + " // Send some text\n" \ + " socket.send('Hello from JavaScript!');\n" \ + " }\n" \ + "\n" \ + " socket.onclose = function(event) {\n" \ + " document.write('The websocket connection has been closed.<br>');\n" \ + " }\n" \ + "\n" \ + " socket.onerror = function(event) {\n" \ + " document.write('An error occurred during the websocket " \ + "communication.<br>');\n" \ + " }\n" \ + "\n" \ + " socket.onmessage = function(event) {\n" \ + " document.write('Websocket message received: ' + " \ + "event.data + '<br>');\n" \ + " }\n" \ + "}\n" \ + "\n" \ + "</script>\n" \ + "</head>\n" \ + "<body>\n" \ + "</body>\n" \ + "</html>" + +#define PAGE_NOT_FOUND \ + "404 Not Found" + +#define PAGE_INVALID_WEBSOCKET_REQUEST \ + "Invalid WebSocket request!" + +static void +send_all (MHD_socket fd, + const char *buf, + size_t len); +static void +make_blocking (MHD_socket fd); + +static void +upgrade_handler (void *cls, + struct MHD_Connection *connection, + void *con_cls, + const char *extra_in, + size_t extra_in_size, + MHD_socket fd, + struct MHD_UpgradeResponseHandle *urh) +{ + /* make the socket blocking (operating-system-dependent code) */ + make_blocking (fd); + + /* create a websocket stream for this connection */ + struct MHD_WebSocketStream* ws; + int result = MHD_websocket_stream_init (&ws, + 0, + 0); + if (0 != result) + { + /* Couldn't create the websocket stream. + * So we close the socket and leave + */ + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); + return; + } + + /* Let's wait for incoming data */ + const size_t buf_len = 256; + char buf[buf_len]; + ssize_t got; + while (MHD_WEBSOCKET_VALIDITY_VALID == MHD_websocket_stream_is_valid (ws)) + { + got = recv (fd, + buf, + buf_len, + 0); + if (0 >= got) + { + /* the TCP/IP socket has been closed */ + break; + } + + /* parse the entire received data */ + size_t buf_offset = 0; + while (buf_offset < (size_t) got) + { + size_t new_offset = 0; + char *frame_data = NULL; + size_t frame_len = 0; + int status = MHD_websocket_decode (ws, + buf + buf_offset, + ((size_t) got) - buf_offset, + &new_offset, + &frame_data, + &frame_len); + if (0 > status) + { + /* an error occurred and the connection must be closed */ + if (NULL != frame_data) + { + MHD_websocket_free (ws, frame_data); + } + break; + } + else + { + buf_offset += new_offset; + if (0 < status) + { + /* the frame is complete */ + switch (status) + { + case MHD_WEBSOCKET_STATUS_TEXT_FRAME: + /* The client has sent some text. + * We will display it and answer with a text frame. + */ + if (NULL != frame_data) + { + printf ("Received message: %s\n", frame_data); + MHD_websocket_free (ws, frame_data); + frame_data = NULL; + } + result = MHD_websocket_encode_text (ws, + "Hello", + 5, /* length of "Hello" */ + 0, + &frame_data, + &frame_len, + NULL); + if (0 == result) + { + send_all (fd, + frame_data, + frame_len); + } + break; + + case MHD_WEBSOCKET_STATUS_CLOSE_FRAME: + /* if we receive a close frame, we will respond with one */ + MHD_websocket_free (ws, + frame_data); + frame_data = NULL; + + result = MHD_websocket_encode_close (ws, + 0, + NULL, + 0, + &frame_data, + &frame_len); + if (0 == result) + { + send_all (fd, + frame_data, + frame_len); + } + break; + + 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; + result = MHD_websocket_encode_pong (ws, + frame_data, + frame_len, + &pong, + &pong_len); + if (0 == result) + { + send_all (fd, + pong, + pong_len); + } + MHD_websocket_free (ws, + pong); + } + break; + + default: + /* Other frame types are ignored + * in this minimal example. + * This is valid, because they become + * automatically skipped if we receive them unexpectedly + */ + break; + } + } + if (NULL != frame_data) + { + MHD_websocket_free (ws, frame_data); + } + } + } + } + + /* free the websocket stream */ + MHD_websocket_stream_free (ws); + + /* close the socket when it is not needed anymore */ + MHD_upgrade_action (urh, + MHD_UPGRADE_ACTION_CLOSE); +} + +/* This helper function is used for the case that + * we need to resend some data + */ +static void +send_all (MHD_socket fd, + const char *buf, + size_t len) +{ + ssize_t ret; + size_t off; + + for (off = 0; off < len; off += ret) + { + ret = send (fd, + &buf[off], + (int) (len - off), + 0); + if (0 > ret) + { + if (EAGAIN == errno) + { + ret = 0; + continue; + } + break; + } + if (0 == ret) + break; + } +} + +/* This helper function contains operating-system-dependent code and + * is used to make a socket blocking. + */ +static void +make_blocking (MHD_socket fd) +{ +#ifndef _WIN32 + int flags; + + flags = fcntl (fd, F_GETFL); + if (-1 == flags) + return; + if ((flags & ~O_NONBLOCK) != flags) + if (-1 == fcntl (fd, F_SETFL, flags & ~O_NONBLOCK)) + abort (); +#else + unsigned long flags = 0; + + ioctlsocket (fd, FIONBIO, &flags); +#endif +} + +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 **ptr) +{ + static int aptr; + struct MHD_Response *response; + int ret; + + (void) cls; /* Unused. Silent compiler warning. */ + (void) upload_data; /* Unused. Silent compiler warning. */ + (void) upload_data_size; /* Unused. Silent compiler warning. */ + + if (0 != strcmp (method, "GET")) + return MHD_NO; /* unexpected method */ + if (&aptr != *ptr) + { + /* do never respond on first call */ + *ptr = &aptr; + return MHD_YES; + } + *ptr = NULL; /* reset when done */ + + if (0 == strcmp (url, "/")) + { + /* Default page for visiting the server */ + struct MHD_Response *response = MHD_create_response_from_buffer ( + strlen (PAGE), + PAGE, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_OK, + response); + MHD_destroy_response (response); + } + else if (0 == strcmp (url, "/chat")) + { + char is_valid = 1; + const char* value = NULL; + char sec_websocket_accept[29]; + + 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) + { + /* upgrade the connection */ + response = MHD_create_response_for_upgrade (&upgrade_handler, + NULL); + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONNECTION, + "Upgrade"); + MHD_add_response_header (response, + MHD_HTTP_HEADER_UPGRADE, + "websocket"); + MHD_add_response_header (response, + MHD_HTTP_HEADER_SEC_WEBSOCKET_ACCEPT, + sec_websocket_accept); + ret = MHD_queue_response (connection, + MHD_HTTP_SWITCHING_PROTOCOLS, + response); + MHD_destroy_response (response); + } + else + { + /* return error page */ + struct MHD_Response*response = MHD_create_response_from_buffer ( + strlen (PAGE_INVALID_WEBSOCKET_REQUEST), + PAGE_INVALID_WEBSOCKET_REQUEST, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_BAD_REQUEST, + response); + MHD_destroy_response (response); + } + } + else + { + struct MHD_Response*response = MHD_create_response_from_buffer ( + strlen (PAGE_NOT_FOUND), + PAGE_NOT_FOUND, + MHD_RESPMEM_PERSISTENT); + ret = MHD_queue_response (connection, + MHD_HTTP_NOT_FOUND, + response); + MHD_destroy_response (response); + } + + return ret; +} + +int +main (int argc, + char *const *argv) +{ + (void) argc; /* Unused. Silent compiler warning. */ + (void) argv; /* Unused. Silent compiler warning. */ + struct MHD_Daemon *daemon; + + daemon = MHD_start_daemon (MHD_USE_INTERNAL_POLLING_THREAD | + MHD_USE_THREAD_PER_CONNECTION | + MHD_ALLOW_UPGRADE | + MHD_USE_ERROR_LOG, + PORT, NULL, NULL, + &access_handler, NULL, + MHD_OPTION_END); + + if (NULL == daemon) + return 1; + (void) getc (stdin); + + MHD_stop_daemon (daemon); + + return 0; +} |