/* * 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 #ifdef _WIN32 #include #ifdef EXTERNAL_POLL #define poll WSAPoll #endif #else #include #include #include #endif #include "deps/libwebsocket/libwebsockets.h" #include "oslib/oslib.h" static int close_testing; int max_poll_elements; struct pollfd *pollfds; int *fd_lookup; int count_pollfds; static volatile int force_exit = 0; static struct libwebsocket_context *context; /* * 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 WEBUI_PATH get_writable_data_path("/webui") /* * We take a strict whitelist approach to stop ../ attacks */ struct serveable { const char *urlpath; const char *mimetype; }; struct per_session_data__http { int fd; }; /* * 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_GET_URI] =*/ "GET URI", /*[WSI_TOKEN_POST_URI] =*/ "POST 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", "Accept:", "If-Modified-Since:", "Accept-Encoding:", "Accept-Language:", "Pragma:", "Cache-Control:", "Authorization:", "Cookie:", "Content-Length:", "Content-Type:", "Date:", "Range:", "Referer:", "Uri-Args:", /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", }; char buf[256]; for (n = 0; n < sizeof(token_names) / sizeof(token_names[0]); n++) { if (!lws_hdr_total_length(wsi, (lws_token_indexes)n)) continue; lws_hdr_copy(wsi, buf, sizeof buf, (lws_token_indexes)n); fprintf(stderr, " %s = %s\n", token_names[n], buf); } } const char * get_mimetype(const char *file) { int n = strlen(file); if (n < 5) return NULL; if (!strcmp(&file[n - 4], ".ico")) return "image/x-icon"; if (!strcmp(&file[n - 4], ".png")) return "image/png"; if (!strcmp(&file[n - 5], ".html")) return "text/html"; return NULL; } /* 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]; char leaf_path[1024]; char b64[64]; struct timeval tv; int n, m; unsigned char *p; char *other_headers = 0; static unsigned char buffer[4096]; struct stat stat_buf; struct per_session_data__http *pss = (struct per_session_data__http *)user; const char *mimetype; #ifdef EXTERNAL_POLL struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in; #endif const string path = WEBUI_PATH; const char* resource_path = path.c_str(); switch (reason) { case LWS_CALLBACK_HTTP: dump_handshake_info(wsi); if (len < 1) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_BAD_REQUEST, NULL); return -1; } /* this server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_FORBIDDEN, NULL); return -1; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer; #ifdef WIN32 pss->fd = open(leaf_path, O_RDONLY | _O_BINARY); #else pss->fd = open(leaf_path, O_RDONLY); #endif if (pss->fd < 0) return -1; fstat(pss->fd, &stat_buf); /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. */ p += sprintf((char *)p, "HTTP/1.0 200 OK\x0d\x0a" "Server: libwebsockets\x0d\x0a" "Content-Type: image/jpeg\x0d\x0a" "Content-Length: %u\x0d\x0a\x0d\x0a", (unsigned int)stat_buf.st_size); /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) */ n = libwebsocket_write(wsi, buffer, p - buffer, LWS_WRITE_HTTP); if (n < 0) { close(pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ libwebsocket_callback_on_writable(context, wsi); break; } /* if not, send a file the easy way */ // FIXME: Data loss if buffer is too small strncpy(buf, resource_path, sizeof(buf)); buf[sizeof(buf) - 1] = '\0'; if (strcmp((const char*)in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, (const char*)in, sizeof(buf) - strlen(resource_path)); } else /* default file to serve */ strcat(buf, "/debugger.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); libwebsockets_return_http_status(context, wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } if (libwebsockets_serve_http_file(context, wsi, buf, mimetype, other_headers)) 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_BODY: strncpy(buf, (const char*)in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arried, close the connection */ libwebsockets_return_http_status(context, wsi, HTTP_STATUS_OK, NULL); return -1; 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; case LWS_CALLBACK_HTTP_WRITEABLE: /* * we can send more of whatever it is we were sending */ do { n = read(pss->fd, buffer, sizeof buffer); /* problem reading, close conn */ if (n < 0) goto bail; /* sent it all, close conn */ if (n == 0) goto flush_bail; /* * because it's HTTP and not websocket, don't need to take * care about pre and postamble */ m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP); if (m < 0) /* write failed, close conn */ goto bail; if (m != n) /* partial write, adjust */ lseek(pss->fd, m - n, SEEK_CUR); if (m) /* while still active, extend timeout */ libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); } while (!lws_send_pipe_choked(wsi)); libwebsocket_callback_on_writable(context, wsi); break; flush_bail: /* true if still partial pending */ if (lws_send_pipe_choked(wsi)) { libwebsocket_callback_on_writable(context, wsi); break; } bail: close(pss->fd); 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)in, 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_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback */ break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback */ break; 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[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->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_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "libwebsocket_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; } /* 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, m; 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++); m = libwebsocket_write(wsi, (u8*)&p_sh4rcb->cntx, sizeof(p_sh4rcb->cntx), LWS_WRITE_BINARY); if (m < n) { lwsl_err("ERROR %d writing to di socket\n", n); return -1; } if (close_testing && pss->number == 50) { lwsl_info("close tesing limit, closing\n"); return -1; } 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 32 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 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 mirror socket\n", n); return -1; } if (n < ringbuffer[pss->ringbuffer_tail].len) lwsl_err("mirror partial write %d vs %d\n", n, ringbuffer[pss->ringbuffer_tail].len); 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)) libwebsocket_rx_flow_allow_all_protocol( libwebsockets_get_protocol(wsi)); // 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); break; } /* * for tests with chrome on same machine as client and * server, this is needed to stop chrome choking */ #ifdef _WIN32 Sleep(1); #else usleep(1); #endif } 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: lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi); libwebsocket_rx_flow_control(wsi, 0); // 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 */ sizeof (struct per_session_data__http), /* 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; libwebsocket_cancel_service(context); } void webui_start() { char cert_path[1024]; char key_path[1024]; int n = 0; int use_ssl = 0; int opts = 0; char interface_name[128] = ""; const char *iface = NULL; #ifndef WIN32 int syslog_options = LOG_PID | LOG_PERROR; #endif 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 = 5678; const string path = WEBUI_PATH; const char* resource_path = path.c_str(); #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.iface = iface; info.protocols = protocols; #ifndef LWS_NO_EXTENSIONS info.extensions = libwebsocket_get_internal_extensions(); #endif if (!use_ssl) { info.ssl_cert_filepath = NULL; info.ssl_private_key_filepath = NULL; } else { if (strlen(resource_path) > sizeof(cert_path) - 32) { lwsl_err("resource path too long\n"); return ; } sprintf(cert_path, "%s/libwebsockets-test-server.pem", resource_path); if (strlen(resource_path) > sizeof(key_path) - 32) { lwsl_err("resource path too long\n"); return; } sprintf(key_path, "%s/libwebsockets-test-server.key.pem", resource_path); info.ssl_cert_filepath = cert_path; info.ssl_private_key_filepath = key_path; } info.gid = -1; info.uid = -1; info.options = opts; context = libwebsocket_create_context(&info); if (context == NULL) { lwsl_err("libwebsocket init failed\n"); return ; } n = 0; double old_time = 0; while (n >= 0 && !force_exit) { double time = os_GetSeconds(); /* * 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 ((time - old_time ) > 0.050) { libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_DUMB_INCREMENT]); old_time = time; } #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; }