Newer
Older
* libwebsockets-test-server - libwebsockets test implementation
* This file is made available under the Creative Commons CC0 1.0
* Universal Public Domain Dedication.
* The person who associated a work with this deed has dedicated
* the work to the public domain by waiving all of his or her rights
* to the work worldwide under copyright law, including all related
* and neighboring rights, to the extent allowed by law. You can copy,
* modify, distribute and perform the work, even for commercial purposes,
* all without asking permission.
* The test apps are intended to be adapted for use in your code, which
* may be proprietary. So unlike the library itself, they are licensed
#include <libwebsockets.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <signal.h>
int max_poll_elements;
struct lws_pollfd *pollfds;
int *fd_lookup;
volatile int force_exit = 0, dynamic_vhost_enable = 0;
struct lws_vhost *dynamic_vhost;
struct lws_context *context;
struct lws_plat_file_ops fops_plat;
/* http server gets files from this path */
#define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server"
char *resource_path = LOCAL_RESOURCE_PATH;
#if defined(LWS_WITH_TLS) && defined(LWS_HAVE_SSL_CTX_set1_param)
/*
* This demonstrates how to use the clean protocol service separation of
* plugins, but with static inclusion instead of runtime dynamic loading
* (which requires libuv).
*
* dumb-increment doesn't use the plugin, both to demonstrate how to
* do the protocols directly, and because it wants libuv for a timer.
*
* Please consider using test-server-v2.0.c instead of this: it has the
* same functionality but
*
* 1) uses lws built-in http handling so you don't need to deal with it in
* your callback
*
* 2) Links with libuv and uses the plugins at runtime
*
* 3) Uses advanced lws features like mounts to bind parts of the filesystem
* to the served URL space
*
* Another option is lwsws, this operates like test-server-v2,0.c but is
* configured using JSON, do you do not need to provide any code for the
* serving action at all, just implement your protocols in plugins.
*/
#define LWS_PLUGIN_STATIC
#include "../plugins/protocol_lws_mirror.c"
#include "../plugins/protocol_lws_status.c"
#include "../plugins/protocol_dumb_increment.c"
#include "../plugins/protocol_post_demo.c"
static int
lws_callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
void *in, size_t len)
{
const unsigned char *c;
char buf[1024];
int n = 0, hlen;
switch (reason) {
case LWS_CALLBACK_HTTP:
/* non-mount-handled accesses will turn up here */
/* dump the headers */
do {
c = lws_token_to_string(n);
if (!c) {
n++;
continue;
}
hlen = lws_hdr_total_length(wsi, n);
if (!hlen || hlen > (int)sizeof(buf) - 1) {
n++;
continue;
}
if (lws_hdr_copy(wsi, buf, sizeof buf, n) < 0)
fprintf(stderr, " %s (too big)\n", (char *)c);
else {
buf[sizeof(buf) - 1] = '\0';
fprintf(stderr, " %s = %s\n", (char *)c, buf);
}
n++;
} while (c);
/* dump the individual URI Arg parameters */
n = 0;
while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf),
WSI_TOKEN_HTTP_URI_ARGS, n) > 0) {
lwsl_notice("URI Arg %d: %s\n", ++n, buf);
}
if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
return -1;
if (lws_http_transaction_completed(wsi))
return -1;
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
/* list of supported protocols and callbacks */
static struct lws_protocols protocols[] = {
/* first protocol must always be HTTP handler */
{ "http-only", lws_callback_http, 0, 0, },
LWS_PLUGIN_PROTOCOL_MIRROR,
LWS_PLUGIN_PROTOCOL_LWS_STATUS,
{ NULL, NULL, 0, 0 } /* terminator */
/* this shows how to override the lws file operations. You don't need
* to do any of this unless you have a reason (eg, want to serve
* compressed files without decompressing the whole archive)
*/
Andy Green
committed
test_server_fops_open(const struct lws_plat_file_ops *fops,
/* call through to original platform implementation */
fop_fd = fops_plat.open(fops, vfs_path, vpath, flags);
if (fop_fd)
lwsl_info("%s: opening %s, ret %p, len %lu\n", __func__,
vfs_path, fop_fd,
(long)lws_vfs_get_length(fop_fd));
else
lwsl_info("%s: open %s failed\n", __func__, vfs_path);
#if !defined(WIN32) && !defined(_WIN32)
/* because windows is too dumb to have SIGUSR1... */
if (sig == SIGUSR1) {
/*
* For testing, you can fire a SIGUSR1 at the test server
* to toggle the existence of an identical server on
* port + 1
*/
dynamic_vhost_enable ^= 1;
lws_cancel_service(context);
lwsl_notice("SIGUSR1: dynamic_vhost_enable: %d\n",
dynamic_vhost_enable);
return;
}
#endif
static const struct lws_extension exts[] = {
{
"permessage-deflate",
lws_extension_callback_pm_deflate,
"permessage-deflate"
},
{ NULL, NULL, NULL /* terminator */ }
};
/*
* mount handlers for sections of the URL space
*/
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
static const struct lws_http_mount mount_ziptest = {
NULL, /* linked-list pointer to next*/
"/ziptest", /* mountpoint in URL namespace on this vhost */
LOCAL_RESOURCE_PATH"/candide.zip", /* handler */
NULL, /* default filename if none given */
NULL,
NULL,
NULL,
NULL,
0,
0,
0,
0,
0,
0,
LWSMPRO_FILE, /* origin points to a callback */
8, /* strlen("/ziptest"), ie length of the mountpoint */
NULL,
{ NULL, NULL } // sentinel
};
static const struct lws_http_mount mount_post = {
(struct lws_http_mount *)&mount_ziptest, /* linked-list pointer to next*/
"/formtest", /* mountpoint in URL namespace on this vhost */
"protocol-post-demo", /* handler */
NULL, /* default filename if none given */
NULL,
NULL,
NULL,
NULL,
0,
0,
0,
0,
0,
0,
LWSMPRO_CALLBACK, /* origin points to a callback */
9, /* strlen("/formtest"), ie length of the mountpoint */
NULL,
{ NULL, NULL } // sentinel
};
/*
* mount a filesystem directory into the URL space at /
* point it to our /usr/share directory with our assets in
* stuff from here is autoserved by the library
*/
static const struct lws_http_mount mount = {
(struct lws_http_mount *)&mount_post, /* linked-list pointer to next*/
"/", /* mountpoint in URL namespace on this vhost */
LOCAL_RESOURCE_PATH, /* where to go on the filesystem for that */
"test.html", /* default filename if none given */
NULL,
NULL,
NULL,
NULL,
0,
0,
0,
0,
0,
0,
LWSMPRO_FILE, /* mount type is a directory in a filesystem */
1, /* strlen("/"), ie length of the mountpoint */
NULL,
{ NULL, NULL } // sentinel
};
static const struct lws_protocol_vhost_options pvo_options = {
NULL,
NULL,
"options", /* pvo name */
(void *)&test_options /* pvo value */
};
static const struct lws_protocol_vhost_options pvo = {
NULL, /* "next" pvo linked-list */
&pvo_options, /* "child" pvo linked-list */
"dumb-increment-protocol", /* protocol name we belong to on this vhost */
"" /* ignored */
};
{ "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' },
{ "ssl-cert", required_argument, NULL, 'C' },
{ "ssl-key", required_argument, NULL, 'K' },
{ "ssl-ca", required_argument, NULL, 'A' },
{ "ssl-verify-client", no_argument, NULL, 'v' },
#if defined(LWS_HAVE_SSL_CTX_set1_param)
{ "ssl-crl", required_argument, NULL, 'R' },
#endif
#endif
{ "libev", no_argument, NULL, 'e' },
{ "unix-socket", required_argument, NULL, 'U' },
{ "daemonize", no_argument, NULL, 'D' },
{ "pingpong-secs", required_argument, NULL, 'P' },
#ifdef EXTERNAL_POLL
unsigned int ms;
#endif
char cert_path[1024] = "";
char key_path[1024] = "";
char ca_path[1024] = "";
int daemonize = 0;
* take care to zero down the info struct, he contains random garbaage
* from the stack otherwise
*/
memset(&info, 0, sizeof info);
info.port = 7681;
n = getopt_long(argc, argv, "eci:hsap:d:DC:K:A:R:vu:g:P:kU:n", options, NULL);
case 'e':
opts |= LWS_SERVER_OPTION_LIBEV;
break;
case 'D':
daemonize = 1;
break;
#endif
case 'u':
uid = atoi(optarg);
break;
case 'g':
gid = atoi(optarg);
break;
/* no dumb increment send */
test_options |= 1;
opts |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
case 'a':
opts |= LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT;
break;
case 'U':
lws_strncpy(interface_name, optarg, sizeof interface_name);
iface = interface_name;
opts |= LWS_SERVER_OPTION_UNIX_SOCK;
break;
case 'k':
info.bind_iface = 1;
#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP)
info.caps[0] = CAP_NET_RAW;
info.count_caps = 1;
#endif
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 'P':
pp_secs = atoi(optarg);
lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
break;
case 'v':
use_ssl = 1;
opts |= LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT;
break;
#if defined(LWS_HAVE_SSL_CTX_set1_param)
case 'R':
break;
#endif
#endif
#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");
#if !defined(WIN32) && !defined(_WIN32)
/* because windows is too dumb to have SIGUSR1... */
/* dynamic vhost create / destroy toggle (on port + 1) */
signal(SIGUSR1, sighandler);
#endif
/* tell the library what debug level to emit and to send it to stderr */
lwsl_notice("libwebsockets test server - license LGPL2.1+SLE\n");
lwsl_notice("(C) Copyright 2010-2018 Andy Green <andy@warmcat.com>\n");
Joakim Söderberg
committed
printf("Using resource path \"%s\"\n", resource_path);
#ifdef EXTERNAL_POLL
max_poll_elements = getdtablesize();
pollfds = malloc(max_poll_elements * sizeof (struct lws_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.ssl_cert_filepath = NULL;
info.ssl_private_key_filepath = NULL;
info.ws_ping_pong_interval = pp_secs;
if (strlen(resource_path) > sizeof(cert_path) - 32) {
lwsl_err("resource path too long\n");
return -1;
}
if (!cert_path[0])
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 -1;
}
if (!key_path[0])
sprintf(key_path, "%s/libwebsockets-test-server.key.pem",
info.ssl_cert_filepath = cert_path;
info.ssl_private_key_filepath = key_path;
if (ca_path[0])
info.ssl_ca_filepath = ca_path;
info.gid = gid;
info.uid = uid;
info.options = opts | LWS_SERVER_OPTION_VALIDATE_UTF8 | LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
info.ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES256-GCM-SHA384:"
"DHE-RSA-AES256-GCM-SHA384:"
"ECDHE-RSA-AES256-SHA384:"
"HIGH:!aNULL:!eNULL:!EXPORT:"
"!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:"
"!SHA1:!DHE-RSA-AES128-GCM-SHA256:"
"!DHE-RSA-AES128-SHA256:"
"!AES128-GCM-SHA256:"
"!AES128-SHA256:"
"!DHE-RSA-AES256-SHA256:"
"!AES256-GCM-SHA384:"
"!AES256-SHA256";
if (use_ssl)
/* redirect guys coming on http */
info.options |= LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS;
context = lws_create_context(&info);
vhost = lws_create_vhost(context, &info);
if (!vhost) {
lwsl_err("vhost creation failed\n");
return -1;
}
/*
* For testing dynamic vhost create / destroy later, we use port + 1
* Normally if you were creating more vhosts, you would set info.name
* for each to be the hostname external clients use to reach it
*/
info.port++;
lws_init_vhost_client_ssl(&info, vhost);
#endif
/* this shows how to override the lws file operations. You don't need
* to do any of this unless you have a reason (eg, want to serve
* compressed files without decompressing the whole archive)
*/
/* stash original platform fops */
fops_plat = *(lws_get_fops(context));
/* override the active fops */
lws_get_fops(context)->open = test_server_fops_open;
#ifdef EXTERNAL_POLL
int ms_1sec = 0;
#endif
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)
#ifdef EXTERNAL_POLL
/*
* this represents an existing server's single poll action
* which also includes libwebsocket sockets
*/
ms = (tv.tv_sec * 1000) + (tv.tv_usec / 1000);
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
*/
&pollfds[n]) < 0)
goto done;
/* if needed, force-service wsis that may not have read all input */
while (!lws_service_adjust_timeout(context, 1, 0)) {
lwsl_notice("extpoll doing forced service!\n");
lws_service_tsi(context, -1, 0);
} else {
/* no revents, but before polling again, make lws check for any timeouts */
if (ms - ms_1sec > 1000) {
lwsl_notice("1 per sec\n");
lws_service_fd(context, NULL);
ms_1sec = ms;
}
/*
* 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.
*/
if (dynamic_vhost_enable && !dynamic_vhost) {
lwsl_notice("creating dynamic vhost...\n");
dynamic_vhost = lws_create_vhost(context, &info);
} else
if (!dynamic_vhost_enable && dynamic_vhost) {
lwsl_notice("destroying dynamic vhost...\n");
lws_vhost_destroy(dynamic_vhost);
dynamic_vhost = NULL;
}
lwsl_notice("libwebsockets-test-server exited cleanly\n");