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,
32 #include "../lib/libwebsockets.h"
34 static int close_testing;
35 int max_poll_elements;
37 struct pollfd *pollfds;
43 * This demo server shows how to use libwebsockets for one or more
44 * websocket protocols in the same server
46 * It defines the following websocket protocols:
48 * dumb-increment-protocol: once the socket is opened, an incrementing
49 * ascii string is sent down it every 50ms.
50 * If you send "reset\n" on the websocket, then
51 * the incrementing number is reset to 0.
53 * lws-mirror-protocol: copies any received packet to every connection also
54 * using this protocol, including the sender
61 PROTOCOL_DUMB_INCREMENT,
69 #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
72 * We take a strict whitelist approach to stop ../ attacks
80 static const struct serveable whitelist[] = {
81 { "/favicon.ico", "image/x-icon" },
82 { "/libwebsockets.org-logo.png", "image/png" },
84 /* last one is the default served if no match */
85 { "/test.html", "text/html" },
88 /* this protocol server (always the first one) just knows how to do HTTP */
90 static int callback_http(struct libwebsocket_context *context,
91 struct libwebsocket *wsi,
92 enum libwebsocket_callback_reasons reason, void *user,
96 char client_name[128];
103 int fd = (int)(long)user;
107 case LWS_CALLBACK_HTTP:
109 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
110 if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
113 sprintf(buf, LOCAL_RESOURCE_PATH"%s", whitelist[n].urlpath);
115 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
116 lwsl_err("Failed to send HTTP file\n");
119 * notice that the sending of the file completes asynchronously,
120 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
126 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
127 // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
128 /* kill the connection after we sent one file */
132 * callback for confirming to continue with client IP appear in
133 * protocol 0 callback since no websocket protocol has been agreed
134 * yet. You can just ignore this if you won't filter on client IP
135 * since the default uhandled callback return is 0 meaning let the
136 * connection continue.
139 case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
141 libwebsockets_get_peer_addresses(context, wsi, (int)(long)user, client_name,
142 sizeof(client_name), client_ip, sizeof(client_ip));
144 fprintf(stderr, "Received network connect from %s (%s)\n",
145 client_name, client_ip);
147 /* if we returned non-zero from here, we kill the connection */
152 * callbacks for managing the external poll() array appear in
153 * protocol 0 callback
156 case LWS_CALLBACK_ADD_POLL_FD:
158 if (count_pollfds >= max_poll_elements) {
159 lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n");
163 fd_lookup[fd] = count_pollfds;
164 pollfds[count_pollfds].fd = fd;
165 pollfds[count_pollfds].events = (int)(long)len;
166 pollfds[count_pollfds++].revents = 0;
169 case LWS_CALLBACK_DEL_POLL_FD:
170 if (!--count_pollfds)
173 /* have the last guy take up the vacant slot */
174 pollfds[m] = pollfds[count_pollfds];
175 fd_lookup[pollfds[count_pollfds].fd] = m;
178 case LWS_CALLBACK_SET_MODE_POLL_FD:
179 pollfds[fd_lookup[fd]].events |= (int)(long)len;
182 case LWS_CALLBACK_CLEAR_MODE_POLL_FD:
183 pollfds[fd_lookup[fd]].events &= ~(int)(long)len;
195 * this is just an example of parsing handshake headers, you don't need this
196 * in your code unless you will filter allowing connections by the header
201 dump_handshake_info(struct lws_tokens *lwst)
204 static const char *token_names[WSI_TOKEN_COUNT] = {
205 /*[WSI_TOKEN_GET_URI] =*/ "GET URI",
206 /*[WSI_TOKEN_HOST] =*/ "Host",
207 /*[WSI_TOKEN_CONNECTION] =*/ "Connection",
208 /*[WSI_TOKEN_KEY1] =*/ "key 1",
209 /*[WSI_TOKEN_KEY2] =*/ "key 2",
210 /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol",
211 /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade",
212 /*[WSI_TOKEN_ORIGIN] =*/ "Origin",
213 /*[WSI_TOKEN_DRAFT] =*/ "Draft",
214 /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge",
217 /*[WSI_TOKEN_KEY] =*/ "Key",
218 /*[WSI_TOKEN_VERSION] =*/ "Version",
219 /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin",
222 /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions",
224 /* client receives these */
225 /*[WSI_TOKEN_ACCEPT] =*/ "Accept",
226 /*[WSI_TOKEN_NONCE] =*/ "Nonce",
227 /*[WSI_TOKEN_HTTP] =*/ "Http",
228 /*[WSI_TOKEN_MUXURL] =*/ "MuxURL",
231 for (n = 0; n < WSI_TOKEN_COUNT; n++) {
232 if (lwst[n].token == NULL)
235 fprintf(stderr, " %s = %s\n", token_names[n], lwst[n].token);
239 /* dumb_increment protocol */
242 * one of these is auto-created for each connection and a pointer to the
243 * appropriate instance is passed to the callback in the user parameter
245 * for this example protocol we use it to individualize the count for each
249 struct per_session_data__dumb_increment {
254 callback_dumb_increment(struct libwebsocket_context *context,
255 struct libwebsocket *wsi,
256 enum libwebsocket_callback_reasons reason,
257 void *user, void *in, size_t len)
260 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 +
261 LWS_SEND_BUFFER_POST_PADDING];
262 unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING];
263 struct per_session_data__dumb_increment *pss = (struct per_session_data__dumb_increment *)user;
267 case LWS_CALLBACK_ESTABLISHED:
268 lwsl_info("callback_dumb_increment: "
269 "LWS_CALLBACK_ESTABLISHED\n");
273 case LWS_CALLBACK_SERVER_WRITEABLE:
274 n = sprintf((char *)p, "%d", pss->number++);
275 n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT);
277 lwsl_err("ERROR %d writing to socket\n", n);
280 if (close_testing && pss->number == 50) {
281 lwsl_info("close tesing limit, closing\n");
282 libwebsocket_close_and_free_session(context, wsi,
283 LWS_CLOSE_STATUS_NORMAL);
287 case LWS_CALLBACK_RECEIVE:
288 // fprintf(stderr, "rx %d\n", (int)len);
291 if (strcmp((const char *)in, "reset\n") == 0)
295 * this just demonstrates how to use the protocol filter. If you won't
296 * study and reject connections based on header content, you don't need
297 * to handle this callback
300 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
301 dump_handshake_info((struct lws_tokens *)(long)user);
302 /* you could return non-zero here and kill the connection */
313 /* lws-mirror_protocol */
315 #define MAX_MESSAGE_QUEUE 128
317 struct per_session_data__lws_mirror {
318 struct libwebsocket *wsi;
327 static struct a_message ringbuffer[MAX_MESSAGE_QUEUE];
328 static int ringbuffer_head;
330 static struct libwebsocket *wsi_choked[20];
331 static int num_wsi_choked;
334 callback_lws_mirror(struct libwebsocket_context *context,
335 struct libwebsocket *wsi,
336 enum libwebsocket_callback_reasons reason,
337 void *user, void *in, size_t len)
340 struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user;
344 case LWS_CALLBACK_ESTABLISHED:
345 lwsl_info("callback_lws_mirror: "
346 "LWS_CALLBACK_ESTABLISHED\n");
347 pss->ringbuffer_tail = ringbuffer_head;
351 case LWS_CALLBACK_SERVER_WRITEABLE:
354 while (pss->ringbuffer_tail != ringbuffer_head) {
356 n = libwebsocket_write(wsi, (unsigned char *)
357 ringbuffer[pss->ringbuffer_tail].payload +
358 LWS_SEND_BUFFER_PRE_PADDING,
359 ringbuffer[pss->ringbuffer_tail].len,
362 lwsl_err("ERROR %d writing to socket\n", n);
366 if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1))
367 pss->ringbuffer_tail = 0;
369 pss->ringbuffer_tail++;
371 if (((ringbuffer_head - pss->ringbuffer_tail) &
372 (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) {
373 for (n = 0; n < num_wsi_choked; n++)
374 libwebsocket_rx_flow_control(wsi_choked[n], 1);
377 // lwsl_debug("tx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1));
379 if (lws_send_pipe_choked(wsi)) {
380 libwebsocket_callback_on_writable(context, wsi);
386 case LWS_CALLBACK_RECEIVE:
388 if (((ringbuffer_head - pss->ringbuffer_tail) &
389 (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) {
390 lwsl_err("dropping!\n");
394 if (ringbuffer[ringbuffer_head].payload)
395 free(ringbuffer[ringbuffer_head].payload);
397 ringbuffer[ringbuffer_head].payload =
398 malloc(LWS_SEND_BUFFER_PRE_PADDING + len +
399 LWS_SEND_BUFFER_POST_PADDING);
400 ringbuffer[ringbuffer_head].len = len;
401 memcpy((char *)ringbuffer[ringbuffer_head].payload +
402 LWS_SEND_BUFFER_PRE_PADDING, in, len);
403 if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1))
408 if (((ringbuffer_head - pss->ringbuffer_tail) &
409 (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2))
413 if (num_wsi_choked < sizeof wsi_choked / sizeof wsi_choked[0]) {
414 libwebsocket_rx_flow_control(wsi, 0);
415 wsi_choked[num_wsi_choked++] = wsi;
418 // lwsl_debug("rx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1));
420 libwebsocket_callback_on_writable_all_protocol(
421 libwebsockets_get_protocol(wsi));
425 * this just demonstrates how to use the protocol filter. If you won't
426 * study and reject connections based on header content, you don't need
427 * to handle this callback
430 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
431 dump_handshake_info((struct lws_tokens *)(long)user);
432 /* you could return non-zero here and kill the connection */
443 /* list of supported protocols and callbacks */
445 static struct libwebsocket_protocols protocols[] = {
446 /* first protocol must always be HTTP handler */
449 "http-only", /* name */
450 callback_http, /* callback */
451 0 /* per_session_data_size */
454 "dumb-increment-protocol",
455 callback_dumb_increment,
456 sizeof(struct per_session_data__dumb_increment),
459 "lws-mirror-protocol",
461 sizeof(struct per_session_data__lws_mirror)
464 NULL, NULL, 0 /* End of list */
468 void sighandler(int sig)
473 static struct option options[] = {
474 { "help", no_argument, NULL, 'h' },
475 { "debug", required_argument, NULL, 'd' },
476 { "port", required_argument, NULL, 'p' },
477 { "ssl", no_argument, NULL, 's' },
478 { "interface", required_argument, NULL, 'i' },
479 { "closetest", no_argument, NULL, 'c' },
480 #ifndef LWS_NO_DAEMONIZE
481 { "daemonize", no_argument, NULL, 'D' },
486 int main(int argc, char **argv)
489 const char *cert_path =
490 LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem";
491 const char *key_path =
492 LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem";
495 struct libwebsocket_context *context;
497 char interface_name[128] = "";
498 const char *interface = NULL;
499 int syslog_options = LOG_PID | LOG_PERROR;
500 unsigned int oldus = 0;
503 #ifndef LWS_NO_DAEMONIZE
508 n = getopt_long(argc, argv, "ci:hsp:d:D", options, NULL);
512 #ifndef LWS_NO_DAEMONIZE
515 syslog_options &= ~LOG_PERROR;
519 debug_level = atoi(optarg);
528 strncpy(interface_name, optarg, sizeof interface_name);
529 interface_name[(sizeof interface_name) - 1] = '\0';
530 interface = interface_name;
534 fprintf(stderr, " Close testing mode -- closes on "
535 "client after 50 dumb increments"
536 "and suppresses lws_mirror spam\n");
539 fprintf(stderr, "Usage: test-server "
540 "[--port=<p>] [--ssl] "
541 "[-d <log bitfield>]\n");
546 #ifndef LWS_NO_DAEMONIZE
548 * normally lock path would be /var/lock/lwsts or similar, to
549 * simplify getting started without having to take care about
550 * permissions or running as root, set to /tmp/.lwsts-lock
552 if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
553 fprintf(stderr, "Failed to daemonize\n");
558 signal(SIGINT, sighandler);
560 /* we will only try to log things according to our debug_level */
561 setlogmask(LOG_UPTO (LOG_DEBUG));
562 openlog("lwsts", syslog_options, LOG_DAEMON);
564 /* tell the library what debug level to emit and to send it to syslog */
565 lws_set_log_level(debug_level, lwsl_emit_syslog);
567 lwsl_notice("libwebsockets test server - "
568 "(C) Copyright 2010-2013 Andy Green <andy@warmcat.com> - "
569 "licensed under LGPL2.1\n");
571 cert_path = key_path = NULL;
573 max_poll_elements = getdtablesize();
574 pollfds = malloc(max_poll_elements * sizeof (struct pollfd));
575 fd_lookup = malloc(max_poll_elements * sizeof (int));
576 if (pollfds == NULL || fd_lookup == NULL) {
577 lwsl_err("Out of memory pollfds=%d\n", max_poll_elements);
582 context = libwebsocket_create_context(port, interface, protocols,
583 #ifndef LWS_NO_EXTENSIONS
584 libwebsocket_internal_extensions,
588 cert_path, key_path, NULL, -1, -1, opts, NULL);
589 if (context == NULL) {
590 lwsl_err("libwebsocket init failed\n");
595 while (n >= 0 && !force_exit) {
598 gettimeofday(&tv, NULL);
601 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
602 * live websocket connection using the DUMB_INCREMENT protocol,
603 * as soon as it can take more packets (usually immediately)
606 if (((unsigned int)tv.tv_usec - oldus) > 50000) {
607 libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_DUMB_INCREMENT]);
614 * this represents an existing server's single poll action
615 * which also includes libwebsocket sockets
618 n = poll(pollfds, count_pollfds, 50);
624 for (n = 0; n < count_pollfds; n++)
625 if (pollfds[n].revents)
627 * returns immediately if the fd does not
628 * match anything under libwebsockets
631 if (libwebsocket_service_fd(context,
636 * If libwebsockets sockets are all we care about,
637 * you can use this api which takes care of the poll()
638 * and looping through finding who needed service.
640 * If no socket needs service, it'll return anyway after
641 * the number of ms in the second argument.
644 n = libwebsocket_service(context, 50);
652 libwebsocket_context_destroy(context);
654 lwsl_notice("libwebsockets-test-server exited cleanly\n");