2 * libwebsockets-test-server - libwebsockets test implementation
4 * Copyright (C) 2010-2011 Andy Green <andy@warmcat.com>
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation:
9 * version 2.1 of the License.
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
22 #include "lws_config.h"
37 #include "../lib/libwebsockets.h"
39 static int close_testing;
40 int max_poll_elements;
42 struct pollfd *pollfds;
48 * This demo server shows how to use libwebsockets for one or more
49 * websocket protocols in the same server
51 * It defines the following websocket protocols:
53 * dumb-increment-protocol: once the socket is opened, an incrementing
54 * ascii string is sent down it every 50ms.
55 * If you send "reset\n" on the websocket, then
56 * the incrementing number is reset to 0.
58 * lws-mirror-protocol: copies any received packet to every connection also
59 * using this protocol, including the sender
66 PROTOCOL_DUMB_INCREMENT,
74 #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
77 * We take a strict whitelist approach to stop ../ attacks
85 static const struct serveable whitelist[] = {
86 { "/favicon.ico", "image/x-icon" },
87 { "/libwebsockets.org-logo.png", "image/png" },
89 /* last one is the default served if no match */
90 { "/test.html", "text/html" },
93 /* this protocol server (always the first one) just knows how to do HTTP */
95 static int callback_http(struct libwebsocket_context *context,
96 struct libwebsocket *wsi,
97 enum libwebsocket_callback_reasons reason, void *user,
101 char client_name[128];
108 int fd = (int)(long)user;
112 case LWS_CALLBACK_HTTP:
114 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
115 if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
118 sprintf(buf, LOCAL_RESOURCE_PATH"%s", whitelist[n].urlpath);
120 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
121 return 1; /* through completion or error, close the socket */
124 * notice that the sending of the file completes asynchronously,
125 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
131 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
132 // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
133 /* kill the connection after we sent one file */
137 * callback for confirming to continue with client IP appear in
138 * protocol 0 callback since no websocket protocol has been agreed
139 * yet. You can just ignore this if you won't filter on client IP
140 * since the default uhandled callback return is 0 meaning let the
141 * connection continue.
144 case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
146 libwebsockets_get_peer_addresses(context, wsi, (int)(long)user, client_name,
147 sizeof(client_name), client_ip, sizeof(client_ip));
149 fprintf(stderr, "Received network connect from %s (%s)\n",
150 client_name, client_ip);
152 /* if we returned non-zero from here, we kill the connection */
157 * callbacks for managing the external poll() array appear in
158 * protocol 0 callback
161 case LWS_CALLBACK_ADD_POLL_FD:
163 if (count_pollfds >= max_poll_elements) {
164 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
168 fd_lookup[fd] = count_pollfds;
169 pollfds[count_pollfds].fd = fd;
170 pollfds[count_pollfds].events = (int)(long)len;
171 pollfds[count_pollfds++].revents = 0;
174 case LWS_CALLBACK_DEL_POLL_FD:
175 if (!--count_pollfds)
178 /* have the last guy take up the vacant slot */
179 pollfds[m] = pollfds[count_pollfds];
180 fd_lookup[pollfds[count_pollfds].fd] = m;
183 case LWS_CALLBACK_SET_MODE_POLL_FD:
184 pollfds[fd_lookup[fd]].events |= (int)(long)len;
187 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
188 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
200 * this is just an example of parsing handshake headers, you don't need this
201 * in your code unless you will filter allowing connections by the header
206 dump_handshake_info(struct libwebsocket *wsi)
209 static const char *token_names[WSI_TOKEN_COUNT] = {
210 /*[WSI_TOKEN_GET_URI] =*/ "GET URI",
211 /*[WSI_TOKEN_HOST] =*/ "Host",
212 /*[WSI_TOKEN_CONNECTION] =*/ "Connection",
213 /*[WSI_TOKEN_KEY1] =*/ "key 1",
214 /*[WSI_TOKEN_KEY2] =*/ "key 2",
215 /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol",
216 /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade",
217 /*[WSI_TOKEN_ORIGIN] =*/ "Origin",
218 /*[WSI_TOKEN_DRAFT] =*/ "Draft",
219 /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge",
222 /*[WSI_TOKEN_KEY] =*/ "Key",
223 /*[WSI_TOKEN_VERSION] =*/ "Version",
224 /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin",
227 /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions",
229 /* client receives these */
230 /*[WSI_TOKEN_ACCEPT] =*/ "Accept",
231 /*[WSI_TOKEN_NONCE] =*/ "Nonce",
232 /*[WSI_TOKEN_HTTP] =*/ "Http",
233 /*[WSI_TOKEN_MUXURL] =*/ "MuxURL",
237 for (n = 0; n < WSI_TOKEN_COUNT; n++) {
238 if (!lws_hdr_total_length(wsi, n))
241 lws_hdr_copy(wsi, buf, sizeof buf, n);
243 fprintf(stderr, " %s = %s\n", token_names[n], buf);
247 /* dumb_increment protocol */
250 * one of these is auto-created for each connection and a pointer to the
251 * appropriate instance is passed to the callback in the user parameter
253 * for this example protocol we use it to individualize the count for each
257 struct per_session_data__dumb_increment {
262 callback_dumb_increment(struct libwebsocket_context *context,
263 struct libwebsocket *wsi,
264 enum libwebsocket_callback_reasons reason,
265 void *user, void *in, size_t len)
268 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
269 LWS_SEND_BUFFER_POST_PADDING];
270 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
271 struct per_session_data__dumb_increment *pss = (struct per_session_data__dumb_increment *)user;
275 case LWS_CALLBACK_ESTABLISHED:
276 lwsl_info("callback_dumb_increment: "
277 "LWS_CALLBACK_ESTABLISHED\n");
281 case LWS_CALLBACK_SERVER_WRITEABLE:
282 n = sprintf((char *)p, "%d", pss->number++);
283 n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
285 lwsl_err("ERROR %d writing to socket\n", n);
288 if (close_testing && pss->number == 50) {
289 lwsl_info("close tesing limit, closing\n");
290 libwebsocket_close_and_free_session(context, wsi,
291 LWS_CLOSE_STATUS_NORMAL);
295 case LWS_CALLBACK_RECEIVE:
296 // fprintf(stderr, "rx %d\n", (int)len);
299 if (strcmp((const char *)in, "reset\n") == 0)
303 * this just demonstrates how to use the protocol filter. If you won't
304 * study and reject connections based on header content, you don't need
305 * to handle this callback
308 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
309 dump_handshake_info(wsi);
310 /* you could return non-zero here and kill the connection */
321 /* lws-mirror_protocol */
323 #define MAX_MESSAGE_QUEUE 128
325 struct per_session_data__lws_mirror {
326 struct libwebsocket *wsi;
335 static struct a_message ringbuffer[MAX_MESSAGE_QUEUE];
336 static int ringbuffer_head;
338 static struct libwebsocket *wsi_choked[20];
339 static int num_wsi_choked;
342 callback_lws_mirror(struct libwebsocket_context *context,
343 struct libwebsocket *wsi,
344 enum libwebsocket_callback_reasons reason,
345 void *user, void *in, size_t len)
348 struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user;
352 case LWS_CALLBACK_ESTABLISHED:
353 lwsl_info("callback_lws_mirror: "
354 "LWS_CALLBACK_ESTABLISHED\n");
355 pss->ringbuffer_tail = ringbuffer_head;
359 case LWS_CALLBACK_PROTOCOL_DESTROY:
360 lwsl_notice("mirror protocol cleaning up\n");
361 for (n = 0; n < sizeof ringbuffer / sizeof ringbuffer[0]; n++)
362 if (ringbuffer[n].payload)
363 free(ringbuffer[n].payload);
366 case LWS_CALLBACK_SERVER_WRITEABLE:
369 while (pss->ringbuffer_tail != ringbuffer_head) {
371 n = libwebsocket_write(wsi, (unsigned char *)
372 ringbuffer[pss->ringbuffer_tail].payload +
373 LWS_SEND_BUFFER_PRE_PADDING,
374 ringbuffer[pss->ringbuffer_tail].len,
377 lwsl_err("ERROR %d writing to socket\n", n);
381 if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1))
382 pss->ringbuffer_tail = 0;
384 pss->ringbuffer_tail++;
386 if (((ringbuffer_head - pss->ringbuffer_tail) &
387 (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) {
388 for (n = 0; n < num_wsi_choked; n++)
389 libwebsocket_rx_flow_control(wsi_choked[n], 1);
392 // lwsl_debug("tx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1));
394 if (lws_send_pipe_choked(wsi)) {
395 libwebsocket_callback_on_writable(context, wsi);
401 case LWS_CALLBACK_RECEIVE:
403 if (((ringbuffer_head - pss->ringbuffer_tail) &
404 (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
405 lwsl_err("dropping!\n");
409 if (ringbuffer[ringbuffer_head].payload)
410 free(ringbuffer[ringbuffer_head].payload);
412 ringbuffer[ringbuffer_head].payload =
413 malloc(LWS_SEND_BUFFER_PRE_PADDING + len +
414 LWS_SEND_BUFFER_POST_PADDING);
415 ringbuffer[ringbuffer_head].len = len;
416 memcpy((char *)ringbuffer[ringbuffer_head].payload +
417 LWS_SEND_BUFFER_PRE_PADDING, in, len);
418 if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
423 if (((ringbuffer_head - pss->ringbuffer_tail) &
424 (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
428 if (num_wsi_choked < sizeof wsi_choked / sizeof wsi_choked[0]) {
429 libwebsocket_rx_flow_control(wsi, 0);
430 wsi_choked[num_wsi_choked++] = wsi;
433 // lwsl_debug("rx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1));
435 libwebsocket_callback_on_writable_all_protocol(
436 libwebsockets_get_protocol(wsi));
440 * this just demonstrates how to use the protocol filter. If you won't
441 * study and reject connections based on header content, you don't need
442 * to handle this callback
445 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
446 dump_handshake_info(wsi);
447 /* you could return non-zero here and kill the connection */
458 /* list of supported protocols and callbacks */
460 static struct libwebsocket_protocols protocols[] = {
461 /* first protocol must always be HTTP handler */
464 "http-only", /* name */
465 callback_http, /* callback */
466 0, /* per_session_data_size */
467 0, /* max frame size / rx buffer */
470 "dumb-increment-protocol",
471 callback_dumb_increment,
472 sizeof(struct per_session_data__dumb_increment),
476 "lws-mirror-protocol",
478 sizeof(struct per_session_data__lws_mirror),
481 { NULL, NULL, 0, 0 } /* terminator */
484 void sighandler(int sig)
489 static struct option options[] = {
490 { "help", no_argument, NULL, 'h' },
491 { "debug", required_argument, NULL, 'd' },
492 { "port", required_argument, NULL, 'p' },
493 { "ssl", no_argument, NULL, 's' },
494 { "interface", required_argument, NULL, 'i' },
495 { "closetest", no_argument, NULL, 'c' },
496 #ifndef LWS_NO_DAEMONIZE
497 { "daemonize", no_argument, NULL, 'D' },
502 int main(int argc, char **argv)
506 struct libwebsocket_context *context;
508 char interface_name[128] = "";
509 const char *iface = NULL;
511 int syslog_options = LOG_PID | LOG_PERROR;
513 unsigned int oldus = 0;
514 struct lws_context_creation_info info;
517 #ifndef LWS_NO_DAEMONIZE
521 memset(&info, 0, sizeof info);
525 n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL);
529 #ifndef LWS_NO_DAEMONIZE
533 syslog_options &= ~LOG_PERROR;
538 debug_level = atoi(optarg);
544 info.port = atoi(optarg);
547 strncpy(interface_name, optarg, sizeof interface_name);
548 interface_name[(sizeof interface_name) - 1] = '\0';
549 iface = interface_name;
553 fprintf(stderr, " Close testing mode -- closes on "
554 "client after 50 dumb increments"
555 "and suppresses lws_mirror spam\n");
558 fprintf(stderr, "Usage: test-server "
559 "[--port=<p>] [--ssl] "
560 "[-d <log bitfield>]\n");
565 #if !defined(LWS_NO_DAEMONIZE) && !defined(WIN32)
567 * normally lock path would be /var/lock/lwsts or similar, to
568 * simplify getting started without having to take care about
569 * permissions or running as root, set to /tmp/.lwsts-lock
571 if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
572 fprintf(stderr, "Failed to daemonize\n");
577 signal(SIGINT, sighandler);
580 /* we will only try to log things according to our debug_level */
581 setlogmask(LOG_UPTO (LOG_DEBUG));
582 openlog("lwsts", syslog_options, LOG_DAEMON);
585 /* tell the library what debug level to emit and to send it to syslog */
586 lws_set_log_level(debug_level, lwsl_emit_syslog);
588 lwsl_notice("libwebsockets test server - "
589 "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
590 "licensed under LGPL2.1\n");
592 max_poll_elements = getdtablesize();
593 pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
594 fd_lookup = malloc(max_poll_elements * sizeof (int));
595 if (pollfds == NULL || fd_lookup == NULL) {
596 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
602 info.protocols = protocols;
603 #ifndef LWS_NO_EXTENSIONS
604 info.extensions = libwebsocket_internal_extensions;
607 info.ssl_cert_filepath = NULL;
608 info.ssl_private_key_filepath = NULL;
610 info.ssl_cert_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
611 info.ssl_private_key_filepath = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
617 context = libwebsocket_create_context(&info);
618 if (context == NULL) {
619 lwsl_err("libwebsocket init failed\n");
624 while (n >= 0 && !force_exit) {
627 gettimeofday(&tv, NULL);
630 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
631 * live websocket connection using the DUMB_INCREMENT protocol,
632 * as soon as it can take more packets (usually immediately)
635 if (((unsigned int)tv.tv_usec - oldus) > 50000) {
636 libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_DUMB_INCREMENT]);
643 * this represents an existing server's single poll action
644 * which also includes libwebsocket sockets
647 n = poll(pollfds, count_pollfds, 50);
653 for (n = 0; n < count_pollfds; n++)
654 if (pollfds[n].revents)
656 * returns immediately if the fd does not
657 * match anything under libwebsockets
660 if (libwebsocket_service_fd(context,
665 * If libwebsockets sockets are all we care about,
666 * you can use this api which takes care of the poll()
667 * and looping through finding who needed service.
669 * If no socket needs service, it'll return anyway after
670 * the number of ms in the second argument.
673 n = libwebsocket_service(context, 50);
681 libwebsocket_context_destroy(context);
683 lwsl_notice("libwebsockets-test-server exited cleanly\n");