/* * libwebsockets-test-server - libwebsockets test implementation * * Copyright (C) 2010-2011 Andy Green * * 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: * version 2.1 of the License. * * 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 */ #ifdef CMAKE_BUILD #include "lws_config.h" #endif #include #include #include #include #include #include #include #ifndef WIN32 #include #endif #include #include "../lib/libwebsockets.h" static int close_testing; int max_poll_elements; struct pollfd *pollfds; int *fd_lookup; int count_pollfds; int force_exit = 0; /* * This demo server shows how to use libwebsockets for one or more * websocket protocols in the same server * * It defines the following websocket protocols: * * dumb-increment-protocol: once the socket is opened, an incrementing * ascii string is sent down it every 50ms. * If you send "reset\n" on the websocket, then * the incrementing number is reset to 0. * * lws-mirror-protocol: copies any received packet to every connection also * using this protocol, including the sender */ enum demo_protocols { /* always first */ PROTOCOL_HTTP = 0, PROTOCOL_DUMB_INCREMENT, PROTOCOL_LWS_MIRROR, /* always last */ DEMO_PROTOCOL_COUNT }; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" /* * We take a strict whitelist approach to stop ../ attacks */ struct serveable { const char *urlpath; const char *mimetype; }; static const struct serveable whitelist[] = { { "/favicon.ico", "image/x-icon" }, { "/libwebsockets.org-logo.png", "image/png" }, /* last one is the default served if no match */ { "/test.html", "text/html" }, }; /* this protocol server (always the first one) just knows how to do HTTP */ static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { #if 0 char client_name[128]; char client_ip[128]; #endif char buf[256]; int n; #ifdef EXTERNAL_POLL int m; int fd = (int)(long)user; #endif switch (reason) { case LWS_CALLBACK_HTTP: for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++) if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0) break; sprintf(buf, LOCAL_RESOURCE_PATH"%s", whitelist[n].urlpath); if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype)) return 1; /* through completion or error, close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_FILE_COMPLETION: // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n"); /* kill the connection after we sent one file */ return 1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: #if 0 libwebsockets_get_peer_addresses(context, wsi, (int)(long)user, client_name, sizeof(client_name), client_ip, sizeof(client_ip)); fprintf(stderr, "Received network connect from %s (%s)\n", client_name, client_ip); #endif /* if we returned non-zero from here, we kill the connection */ break; #ifdef EXTERNAL_POLL /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[fd] = count_pollfds; pollfds[count_pollfds].fd = fd; pollfds[count_pollfds].events = (int)(long)len; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_SET_MODE_POLL_FD: pollfds[fd_lookup[fd]].events |= (int)(long)len; break; case LWS_CALLBACK_CLEAR_MODE_POLL_FD: pollfds[fd_lookup[fd]].events &= ~(int)(long)len; break; #endif default: break; } return 0; } /* * this is just an example of parsing handshake headers, you don't need this * in your code unless you will filter allowing connections by the header * content */ static void dump_handshake_info(struct libwebsocket *wsi) { int n; static const char *token_names[WSI_TOKEN_COUNT] = { /*[WSI_TOKEN_GET_URI] =*/ "GET URI", /*[WSI_TOKEN_HOST] =*/ "Host", /*[WSI_TOKEN_CONNECTION] =*/ "Connection", /*[WSI_TOKEN_KEY1] =*/ "key 1", /*[WSI_TOKEN_KEY2] =*/ "key 2", /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol", /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade", /*[WSI_TOKEN_ORIGIN] =*/ "Origin", /*[WSI_TOKEN_DRAFT] =*/ "Draft", /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge", /* new for 04 */ /*[WSI_TOKEN_KEY] =*/ "Key", /*[WSI_TOKEN_VERSION] =*/ "Version", /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin", /* new for 05 */ /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions", /* client receives these */ /*[WSI_TOKEN_ACCEPT] =*/ "Accept", /*[WSI_TOKEN_NONCE] =*/ "Nonce", /*[WSI_TOKEN_HTTP] =*/ "Http", /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", }; char buf[256]; for (n = 0; n < WSI_TOKEN_COUNT; n++) { if (!lws_hdr_total_length(wsi, n)) continue; lws_hdr_copy(wsi, buf, sizeof buf, n); fprintf(stderr, " %s = %s\n", token_names[n], buf); } } /* dumb_increment protocol */ /* * one of these is auto-created for each connection and a pointer to the * appropriate instance is passed to the callback in the user parameter * * for this example protocol we use it to individualize the count for each * connection. */ struct per_session_data__dumb_increment { int number; }; static int callback_dumb_increment(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { int n; unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING]; unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING]; struct per_session_data__dumb_increment *pss = (struct per_session_data__dumb_increment *)user; switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_info("callback_dumb_increment: " "LWS_CALLBACK_ESTABLISHED\n"); pss->number = 0; break; case LWS_CALLBACK_SERVER_WRITEABLE: n = sprintf((char *)p, "%d", pss->number++); n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT); if (n < 0) { lwsl_err("ERROR %d writing to socket\n", n); return 1; } if (close_testing && pss->number == 50) { lwsl_info("close tesing limit, closing\n"); libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NORMAL); } break; case LWS_CALLBACK_RECEIVE: // fprintf(stderr, "rx %d\n", (int)len); if (len < 6) break; if (strcmp((const char *)in, "reset\n") == 0) pss->number = 0; break; /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need * to handle this callback */ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: dump_handshake_info(wsi); /* you could return non-zero here and kill the connection */ break; default: break; } return 0; } /* lws-mirror_protocol */ #define MAX_MESSAGE_QUEUE 128 struct per_session_data__lws_mirror { struct libwebsocket *wsi; int ringbuffer_tail; }; struct a_message { void *payload; size_t len; }; static struct a_message ringbuffer[MAX_MESSAGE_QUEUE]; static int ringbuffer_head; static struct libwebsocket *wsi_choked[20]; static int num_wsi_choked; static int callback_lws_mirror(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { int n; struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user; switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_info("callback_lws_mirror: " "LWS_CALLBACK_ESTABLISHED\n"); pss->ringbuffer_tail = ringbuffer_head; pss->wsi = wsi; break; case LWS_CALLBACK_PROTOCOL_DESTROY: lwsl_notice("mirror protocol cleaning up\n"); for (n = 0; n < sizeof ringbuffer / sizeof ringbuffer[0]; n++) if (ringbuffer[n].payload) free(ringbuffer[n].payload); break; case LWS_CALLBACK_SERVER_WRITEABLE: if (close_testing) break; while (pss->ringbuffer_tail != ringbuffer_head) { n = libwebsocket_write(wsi, (unsigned char *) ringbuffer[pss->ringbuffer_tail].payload + LWS_SEND_BUFFER_PRE_PADDING, ringbuffer[pss->ringbuffer_tail].len, LWS_WRITE_TEXT); if (n < 0) { lwsl_err("ERROR %d writing to socket\n", n); return 1; } if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1)) pss->ringbuffer_tail = 0; else pss->ringbuffer_tail++; if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) { for (n = 0; n < num_wsi_choked; n++) libwebsocket_rx_flow_control(wsi_choked[n], 1); num_wsi_choked = 0; } // lwsl_debug("tx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)); if (lws_send_pipe_choked(wsi)) { libwebsocket_callback_on_writable(context, wsi); return 0; } } break; case LWS_CALLBACK_RECEIVE: if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) { lwsl_err("dropping!\n"); goto choke; } if (ringbuffer[ringbuffer_head].payload) free(ringbuffer[ringbuffer_head].payload); ringbuffer[ringbuffer_head].payload = malloc(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING); ringbuffer[ringbuffer_head].len = len; memcpy((char *)ringbuffer[ringbuffer_head].payload + LWS_SEND_BUFFER_PRE_PADDING, in, len); if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1)) ringbuffer_head = 0; else ringbuffer_head++; if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2)) goto done; choke: if (num_wsi_choked < sizeof wsi_choked / sizeof wsi_choked[0]) { libwebsocket_rx_flow_control(wsi, 0); wsi_choked[num_wsi_choked++] = wsi; } // lwsl_debug("rx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)); done: libwebsocket_callback_on_writable_all_protocol( libwebsockets_get_protocol(wsi)); break; /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need * to handle this callback */ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: dump_handshake_info(wsi); /* you could return non-zero here and kill the connection */ break; default: break; } return 0; } /* list of supported protocols and callbacks */ static struct libwebsocket_protocols protocols[] = { /* first protocol must always be HTTP handler */ { "http-only", /* name */ callback_http, /* callback */ 0, /* per_session_data_size */ 0, /* max frame size / rx buffer */ }, { "dumb-increment-protocol", callback_dumb_increment, sizeof(struct per_session_data__dumb_increment), 10, }, { "lws-mirror-protocol", callback_lws_mirror, sizeof(struct per_session_data__lws_mirror), 128, }, { NULL, NULL, 0, 0 } /* terminator */ }; void sighandler(int sig) { force_exit = 1; } static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "debug", required_argument, NULL, 'd' }, { "port", required_argument, NULL, 'p' }, { "ssl", no_argument, NULL, 's' }, { "interface", required_argument, NULL, 'i' }, { "closetest", no_argument, NULL, 'c' }, #ifndef LWS_NO_DAEMONIZE { "daemonize", no_argument, NULL, 'D' }, #endif { NULL, 0, 0, 0 } }; int main(int argc, char **argv) { int n = 0; int use_ssl = 0; struct libwebsocket_context *context; int opts = 0; char interface_name[128] = ""; const char *interface = NULL; #ifndef WIN32 int syslog_options = LOG_PID | LOG_PERROR; #endif unsigned int oldus = 0; struct lws_context_creation_info info; int debug_level = 7; #ifndef LWS_NO_DAEMONIZE int daemonize = 0; #endif memset(&info, 0, sizeof info); info.port = 7681; while (n >= 0) { n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL); if (n < 0) continue; switch (n) { #ifndef LWS_NO_DAEMONIZE case 'D': daemonize = 1; #ifndef WIN32 syslog_options &= ~LOG_PERROR; #endif break; #endif case 'd': debug_level = atoi(optarg); break; case 's': use_ssl = 1; break; case 'p': info.port = atoi(optarg); break; case 'i': strncpy(interface_name, optarg, sizeof interface_name); interface_name[(sizeof interface_name) - 1] = '\0'; interface = interface_name; break; case 'c': close_testing = 1; fprintf(stderr, " Close testing mode -- closes on " "client after 50 dumb increments" "and suppresses lws_mirror spam\n"); break; case 'h': fprintf(stderr, "Usage: test-server " "[--port=

] [--ssl] " "[-d ]\n"); exit(1); } } #if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32) /* * normally lock path would be /var/lock/lwsts or similar, to * simplify getting started without having to take care about * permissions or running as root, set to /tmp/.lwsts-lock */ if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) { fprintf(stderr, "Failed to daemonize\n"); return 1; } #endif signal(SIGINT, sighandler); #ifndef WIN32 /* we will only try to log things according to our debug_level */ setlogmask(LOG_UPTO (LOG_DEBUG)); openlog("lwsts", syslog_options, LOG_DAEMON); #endif /* tell the library what debug level to emit and to send it to syslog */ lws_set_log_level(debug_level, lwsl_emit_syslog); lwsl_notice("libwebsockets test server - " "(C) Copyright 2010-2013 Andy Green - " "licensed under LGPL2.1\n"); #ifdef EXTERNAL_POLL max_poll_elements = getdtablesize(); pollfds = malloc(max_poll_elements * sizeof (struct pollfd)); fd_lookup = malloc(max_poll_elements * sizeof (int)); if (pollfds == NULL || fd_lookup == NULL) { lwsl_err("Out of memory pollfds=%d\n", max_poll_elements); return -1; } #endif info.interface = interface; info.protocols = protocols; #ifndef LWS_NO_EXTENSIONS info.extensions = libwebsocket_internal_extensions; #endif if (!use_ssl) { info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; } else { info.ssl_cert_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem"; info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem"; } info.gid = -1; info.uid = -1; info.options = opts; context = libwebsocket_create_context(&info); if (context == NULL) { lwsl_err("libwebsocket init failed\n"); return -1; } n = 0; while (n >= 0 && !force_exit) { struct timeval tv; gettimeofday(&tv, NULL); /* * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every * live websocket connection using the DUMB_INCREMENT protocol, * as soon as it can take more packets (usually immediately) */ if (((unsigned int)tv.tv_usec - oldus) > 50000) { libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_DUMB_INCREMENT]); oldus = tv.tv_usec; } #ifdef EXTERNAL_POLL /* * this represents an existing server's single poll action * which also includes libwebsocket sockets */ n = poll(pollfds, count_pollfds, 50); if (n < 0) continue; if (n) for (n = 0; n < count_pollfds; n++) if (pollfds[n].revents) /* * returns immediately if the fd does not * match anything under libwebsockets * control */ if (libwebsocket_service_fd(context, &pollfds[n]) < 0) goto done; #else /* * If libwebsockets sockets are all we care about, * you can use this api which takes care of the poll() * and looping through finding who needed service. * * If no socket needs service, it'll return anyway after * the number of ms in the second argument. */ n = libwebsocket_service(context, 50); #endif } #ifdef EXTERNAL_POLL done: #endif libwebsocket_context_destroy(context); lwsl_notice("libwebsockets-test-server exited cleanly\n"); #ifndef WIN32 closelog(); #endif return 0; }