2 * uMurmurd Websocket server - HTTP/JSON serverexample
4 * Copyright (C) 2014 Michael P. Pounders <>
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"
41 #include <libwebsockets.h>
42 #include "../../../src/sharedmemory.h"
44 int max_poll_elements;
46 struct pollfd *pollfds;
55 PROTOCOL_JSON_UMURMURD,
63 char *resource_path = "../web";
66 * We take a strict whitelist approach to stop ../ attacks
74 static const struct serveable whitelist[] = {
75 { "/favicon.ico", "image/x-icon" },
76 { "/css/mon_umurmurd.css", "text/css" },
77 { "/css/json.human.css", "text/css" },
78 { "/js/crel.js", "text/javascript" },
79 { "/js/json.human.js", "text/javascript" },
80 { "/js/jquery.min.js", "text/javascript" },
82 /* last one is the default served if no match */
83 { "/mon_umurmurd.html", "text/html" },
86 struct per_session_data__http {
90 /* this protocol server (always the first one) just knows how to do HTTP */
92 static int callback_http( struct libwebsocket_context *context,
93 struct libwebsocket *wsi,
94 enum libwebsocket_callback_reasons reason, void *user,
98 char client_name[128];
102 char leaf_path[1024];
105 static unsigned char buffer[4096];
106 struct stat stat_buf;
107 struct per_session_data__http *pss =
108 (struct per_session_data__http *)user;
111 case LWS_CALLBACK_HTTP:
113 /* check for the "send a big file by hand" example case */
115 if (!strcmp((const char *)in, "/leaf.jpg")) {
116 if (strlen(resource_path) > sizeof(leaf_path) - 10)
118 sprintf(leaf_path, "%s/leaf.jpg", resource_path);
120 /* well, let's demonstrate how to send the hard way */
124 pss->fd = open(leaf_path, O_RDONLY);
129 fstat(pss->fd, &stat_buf);
132 * we will send a big jpeg file, but it could be
133 * anything. Set the Content-Type: appropriately
134 * so the browser knows what to do with it.
137 p += sprintf((char *)p,
138 "HTTP/1.0 200 OK\x0d\x0a"
139 "Server: libwebsockets\x0d\x0a"
140 "Content-Type: image/jpeg\x0d\x0a"
141 "Content-Length: %u\x0d\x0a\x0d\x0a",
142 (unsigned int)stat_buf.st_size);
145 * send the http headers...
146 * this won't block since it's the first payload sent
147 * on the connection since it was established
148 * (too small for partial)
151 n = libwebsocket_write(wsi, buffer,
152 p - buffer, LWS_WRITE_HTTP);
159 * book us a LWS_CALLBACK_HTTP_WRITEABLE callback
161 libwebsocket_callback_on_writable(context, wsi);
165 /* if not, send a file the easy way */
167 for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++)
168 if (in && strcmp((const char *)in, whitelist[n].urlpath) == 0)
171 sprintf(buf, "%s%s", resource_path, whitelist[n].urlpath);
173 if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype))
174 return -1; /* through completion or error, close the socket */
177 * notice that the sending of the file completes asynchronously,
178 * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when
184 case LWS_CALLBACK_HTTP_FILE_COMPLETION:
185 // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n");
186 /* kill the connection after we sent one file */
189 case LWS_CALLBACK_HTTP_WRITEABLE:
191 * we can send more of whatever it is we were sending
195 n = read(pss->fd, buffer, sizeof buffer);
196 /* problem reading, close conn */
199 /* sent it all, close conn */
203 * because it's HTTP and not websocket, don't need to take
204 * care about pre and postamble
206 m = libwebsocket_write(wsi, buffer, n, LWS_WRITE_HTTP);
208 /* write failed, close conn */
211 /* partial write, adjust */
212 lseek(pss->fd, m - n, SEEK_CUR);
214 } while (!lws_send_pipe_choked(wsi));
215 libwebsocket_callback_on_writable(context, wsi);
223 * callback for confirming to continue with client IP appear in
224 * protocol 0 callback since no websocket protocol has been agreed
225 * yet. You can just ignore this if you won't filter on client IP
226 * since the default uhandled callback return is 0 meaning let the
227 * connection continue.
230 case LWS_CALLBACK_FILTER_NETWORK_CONNECTION:
232 libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name,
233 sizeof(client_name), client_ip, sizeof(client_ip));
235 fprintf(stderr, "Received network connect from %s (%s)\n",
236 client_name, client_ip);
238 /* if we returned non-zero from here, we kill the connection */
249 * this is just an example of parsing handshake headers, you don't need this
250 * in your code unless you will filter allowing connections by the header
255 dump_handshake_info(struct libwebsocket *wsi)
258 static const char *token_names[WSI_TOKEN_COUNT] = {
259 /*[WSI_TOKEN_GET_URI] =*/ "GET URI",
260 /*[WSI_TOKEN_HOST] =*/ "Host",
261 /*[WSI_TOKEN_CONNECTION] =*/ "Connection",
262 /*[WSI_TOKEN_KEY1] =*/ "key 1",
263 /*[WSI_TOKEN_KEY2] =*/ "key 2",
264 /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol",
265 /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade",
266 /*[WSI_TOKEN_ORIGIN] =*/ "Origin",
267 /*[WSI_TOKEN_DRAFT] =*/ "Draft",
268 /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge",
271 /*[WSI_TOKEN_KEY] =*/ "Key",
272 /*[WSI_TOKEN_VERSION] =*/ "Version",
273 /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin",
276 /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions",
278 /* client receives these */
279 /*[WSI_TOKEN_ACCEPT] =*/ "Accept",
280 /*[WSI_TOKEN_NONCE] =*/ "Nonce",
281 /*[WSI_TOKEN_HTTP] =*/ "Http",
282 /*[WSI_TOKEN_MUXURL] =*/ "MuxURL",
286 for (n = 0; n < WSI_TOKEN_COUNT; n++) {
287 if (!lws_hdr_total_length(wsi, n))
290 lws_hdr_copy(wsi, buf, sizeof buf, n);
292 fprintf(stderr, " %s = %s\n", token_names[n], buf);
296 void *getJsonData( unsigned char * buf, int *n )
303 json_t *root = NULL, *server = NULL, *client, *clients;
305 root = json_object();
306 clients = json_object();
309 server = json_pack( "{s:{:s:i,s:i}}",
311 "clients_max", shmptr->server_max_clients,
312 "clients_connected", shmptr->clientcount );
314 json_object_update( root, server );
316 if( shmptr->clientcount )
318 jarr1 = json_array();
320 for( cc = 0 ; cc < shmptr->server_max_clients ; cc++ )
323 if( !shmptr->client[cc].authenticated )
326 client = json_pack( "{:s:s,s:s,s:i,s:s,s:I,s:I,s:I}", "username",
327 shmptr->client[cc].username,
329 shmptr->client[cc].ipaddress,
331 shmptr->client[cc].udp_port,
333 shmptr->client[cc].channel,
335 shmptr->client[cc].lastActivity,
337 shmptr->client[cc].connectTime,
339 (long long unsigned int)shmptr->client[cc].lastActivity - shmptr->client[cc].idleTime
341 json_array_append_new( jarr1, client );
343 json_object_set_new( clients, "clients", jarr1 );
344 json_object_update( root, clients );
346 json_dump_file(root, "json.txt", JSON_PRESERVE_ORDER | JSON_INDENT(4) );
347 result = json_dumps(root, JSON_PRESERVE_ORDER | JSON_COMPACT );
349 *n = sprintf( (char *)&buf[LWS_SEND_BUFFER_PRE_PADDING], "%s", result );
359 struct per_session_data__umurmur_json {
364 callback_umurmur_json( struct libwebsocket_context *context,
365 struct libwebsocket *wsi,
366 enum libwebsocket_callback_reasons reason,
367 void *user, void *in, size_t len)
371 unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096 +
372 LWS_SEND_BUFFER_POST_PADDING];
374 //struct per_session_data__umurmur_json *pss = (struct per_session_data__umurmur_json *)user;
378 case LWS_CALLBACK_ESTABLISHED:
379 lwsl_info("callback_umurmur_json: LWS_CALLBACK_ESTABLISHED\n");
382 case LWS_CALLBACK_SERVER_WRITEABLE:
383 getJsonData( buf, &n );
384 m = libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], n, LWS_WRITE_TEXT); //printf("N: %d M: %d\n", n, m );
390 case LWS_CALLBACK_RECEIVE:
391 //fprintf(stderr, "rx %d\n", (int)len);
394 if (strcmp((const char *)in, "update\n") == 0)
395 libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol( wsi ));
399 * this just demonstrates how to use the protocol filter. If you won't
400 * study and reject connections based on header content, you don't need
401 * to handle this callback
404 case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
405 dump_handshake_info(wsi);
406 /* you could return non-zero here and kill the connection */
418 /* list of supported protocols and callbacks */
420 static struct libwebsocket_protocols protocols[] = {
421 /* first protocol must always be HTTP handler */
424 "http-only", /* name */
425 callback_http, /* callback */
426 sizeof (struct per_session_data__http), /* per_session_data_size */
427 0, /* max frame size / rx buffer */
430 "umurmur-json-protocol",
431 callback_umurmur_json,
432 sizeof(struct per_session_data__umurmur_json),
435 { NULL, NULL, 0, 0 } /* terminator */
438 void sighandler(int sig)
443 static struct option options[] = {
444 { "help", no_argument, NULL, 'h' },
445 { "debug", required_argument, NULL, 'd' },
446 { "port", required_argument, NULL, 'p' },
447 { "ssl", no_argument, NULL, 's' },
448 { "interface", required_argument, NULL, 'i' },
449 { "daemonize", no_argument, NULL, 'D' },
450 { "resource_path", required_argument, NULL, 'r' },
454 int main(int argc, char **argv)
456 char cert_path[1024];
460 struct libwebsocket_context *context;
462 char interface_name[128] = "";
463 const char *iface = NULL;
464 // unsigned int oldus = 0;
465 struct lws_context_creation_info info;
467 int syslog_options = LOG_PID | LOG_PERROR;
474 memset(&info, 0, sizeof info);
478 n = getopt_long(argc, argv, "i:hsp:d:Dr:", options, NULL);
484 syslog_options &= ~LOG_PERROR;
488 debug_level = atoi(optarg);
494 info.port = atoi(optarg);
497 strncpy(interface_name, optarg, sizeof interface_name);
498 interface_name[(sizeof interface_name) - 1] = '\0';
499 iface = interface_name;
502 resource_path = optarg;
503 printf("Setting resource path to \"%s\"\n", resource_path);
506 fprintf(stderr, "Usage: test-server "
507 "[--port=<p>] [--ssl] "
508 "[-d <log bitfield>] "
509 "[--resource_path <path>]\n");
514 key_t key = 0x53021d79;
516 if( ( shmid = shmget( key, 0, 0) ) == -1 )
519 printf( "umurmurd doesn't seem to be running\n\r" );
524 if( ( shmptr = shmat( shmid,0, 0 ) ) == (void *) -1 )
532 * normally lock path would be /var/lock/lwsts or similar, to
533 * simplify getting started without having to take care about
534 * permissions or running as root, set to /tmp/.lwsts-lock
536 if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) {
537 fprintf(stderr, "Failed to daemonize\n");
542 signal(SIGINT, sighandler);
545 /* we will only try to log things according to our debug_level */
546 setlogmask(LOG_UPTO (LOG_DEBUG));
547 openlog("lwsts", syslog_options, LOG_DAEMON);
550 /* tell the library what debug level to emit and to send it to syslog */
551 lws_set_log_level(debug_level, lwsl_emit_syslog);
553 lwsl_notice("uMurmurd Websocket server - "
554 "(C) Copyright 2014 Michael J. Pounders <> - "
555 "licensed under LGPL2.1\n");
558 info.protocols = protocols;
560 info.extensions = libwebsocket_get_internal_extensions();
563 info.ssl_cert_filepath = NULL;
564 info.ssl_private_key_filepath = NULL;
566 if (strlen(resource_path) > sizeof(cert_path) - 32) {
567 lwsl_err("resource path too long\n");
570 sprintf(cert_path, "%s/ssl/umurmurd_websocket.pem",
572 if (strlen(resource_path) > sizeof(key_path) - 32) {
573 lwsl_err("resource path too long\n");
576 sprintf(key_path, "%s/ssl/umurmurd_websocket.key.pem",
579 info.ssl_cert_filepath = cert_path;
580 info.ssl_private_key_filepath = key_path;
586 context = libwebsocket_create_context(&info);
587 if (context == NULL) {
588 lwsl_err("libwebsocket init failed\n");
593 while (n >= 0 && !force_exit) {
596 gettimeofday(&tv, NULL);
599 * This provokes the LWS_CALLBACK_SERVER_WRITEABLE for every
600 * live websocket connection using the DUMB_INCREMENT protocol,
601 * as soon as it can take more packets (usually immediately)
604 // if (((unsigned int)tv.tv_usec - oldus) > 50000) {
605 // libwebsocket_callback_on_writable_all_protocol(&protocols[PROTOCOL_JSON_UMURMURD]);
606 // oldus = tv.tv_usec;
611 * If libwebsockets sockets are all we care about,
612 * you can use this api which takes care of the poll()
613 * and looping through finding who needed service.
615 * If no socket needs service, it'll return anyway after
616 * the number of ms in the second argument.
619 n = libwebsocket_service(context, 50);
624 libwebsocket_context_destroy(context);
626 lwsl_notice("umurmur_websocket server exited cleanly\n");