2 * libwebsockets-test-client - libwebsockets test implementation
4 * Copyright (C) 2011-2016 Andy Green <andy@warmcat.com>
6 * This file is made available under the Creative Commons CC0 1.0
7 * Universal Public Domain Dedication.
9 * The person who associated a work with this deed has dedicated
10 * the work to the public domain by waiving all of his or her rights
11 * to the work worldwide under copyright law, including all related
12 * and neighboring rights, to the extent allowed by law. You can copy,
13 * modify, distribute and perform the work, even for commercial purposes,
14 * all without asking permission.
16 * The test apps are intended to be adapted for use in your code, which
17 * may be proprietary. So unlike the library itself, they are licensed
21 #include "lws_config.h"
31 #include "gettimeofday.h"
38 #include "../lib/libwebsockets.h"
40 static int deny_deflate, longlived, mirror_lifetime, test_post;
41 static struct lws *wsi_dumb, *wsi_mirror;
42 static struct lws *wsi_multi[3];
43 static volatile int force_exit;
44 static unsigned int opts, rl_multi[3];
45 static int flag_no_mirror_traffic;
47 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
48 char crl_path[1024] = "";
52 * This demo shows how to connect multiple websockets simultaneously to a
53 * websocket server (there is no restriction on their having to be the same
54 * server just it simplifies the demo).
56 * dumb-increment-protocol: we connect to the server and print the number
59 * lws-mirror-protocol: draws random circles, which are mirrored on to every
60 * client (see them being drawn in every browser
61 * session also using the test server)
66 PROTOCOL_DUMB_INCREMENT,
73 static void show_http_content(const char *p, size_t l)
75 if (lwsl_visible(LLL_INFO)) {
86 * dumb_increment protocol
88 * since this also happens to be protocols[0], some callbacks that are not
89 * bound to a specific protocol also turn up here.
93 callback_dumb_increment(struct lws *wsi, enum lws_callback_reasons reason,
94 void *user, void *in, size_t len)
96 const char *which = "http";
97 char which_wsi[10], buf[50 + LWS_PRE];
102 case LWS_CALLBACK_CLIENT_ESTABLISHED:
103 lwsl_info("dumb: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
106 case LWS_CALLBACK_CLOSED:
107 lwsl_notice("dumb: LWS_CALLBACK_CLOSED\n");
111 case LWS_CALLBACK_CLIENT_RECEIVE:
112 ((char *)in)[len] = '\0';
113 lwsl_info("rx %d '%s'\n", (int)len, (char *)in);
116 /* because we are protocols[0] ... */
118 case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
119 if (wsi == wsi_dumb) {
123 if (wsi == wsi_mirror) {
128 for (n = 0; n < ARRAY_SIZE(wsi_multi); n++)
129 if (wsi == wsi_multi[n]) {
130 sprintf(which_wsi, "multi %d", n);
135 lwsl_err("CLIENT_CONNECTION_ERROR: %s: %s\n", which,
136 in ? (char *)in : "(null)");
139 case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
140 if ((strcmp((const char *)in, "deflate-stream") == 0) && deny_deflate) {
141 lwsl_notice("denied deflate-stream extension\n");
144 if ((strcmp((const char *)in, "x-webkit-deflate-frame") == 0))
146 if ((strcmp((const char *)in, "deflate-frame") == 0))
150 case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
151 lwsl_notice("lws_http_client_http_response %d\n",
152 lws_http_client_http_response(wsi));
155 /* chunked content */
156 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
157 lwsl_notice("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: %ld\n",
159 show_http_content(in, len);
162 /* unchunked content */
163 case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
165 char buffer[1024 + LWS_PRE];
166 char *px = buffer + LWS_PRE;
167 int lenx = sizeof(buffer) - LWS_PRE;
170 * Often you need to flow control this by something
171 * else being writable. In that case call the api
172 * to get a callback when writable here, and do the
173 * pending client read in the writeable callback of
176 * In the case of chunked content, this will call back
177 * LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ once per
178 * chunk or partial chunk in the buffer, and report
179 * zero length back here.
181 if (lws_http_client_read(wsi, &px, &lenx) < 0)
186 case LWS_CALLBACK_CLIENT_WRITEABLE:
187 lwsl_info("Client wsi %p writable\n", wsi);
190 case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
192 unsigned char **p = (unsigned char **)in, *end = (*p) + len;
194 if (lws_add_http_header_by_token(wsi,
195 WSI_TOKEN_HTTP_CONTENT_LENGTH,
196 (unsigned char *)"29", 2, p, end))
198 if (lws_add_http_header_by_token(wsi,
199 WSI_TOKEN_HTTP_CONTENT_TYPE,
200 (unsigned char *)"application/x-www-form-urlencoded", 33, p, end))
203 /* inform lws we have http body to send */
204 lws_client_http_body_pending(wsi, 1);
205 lws_callback_on_writable(wsi);
209 case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE:
210 strcpy(buf + LWS_PRE, "text=hello&send=Send+the+form");
211 n = lws_write(wsi, (unsigned char *)&buf[LWS_PRE], strlen(&buf[LWS_PRE]), LWS_WRITE_HTTP);
214 /* we only had one thing to send, so inform lws we are done
215 * if we had more to send, call lws_callback_on_writable(wsi);
216 * and just return 0 from callback. On having sent the last
217 * part, call the below api instead.*/
218 lws_client_http_body_pending(wsi, 0);
221 case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
226 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
227 case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS:
229 /* Enable CRL checking of the server certificate */
230 X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
231 X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
232 SSL_CTX_set1_param((SSL_CTX*)user, param);
233 X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user);
234 X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
235 int n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM);
236 X509_VERIFY_PARAM_free(param);
240 lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n);
255 /* lws-mirror_protocol */
259 callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason,
260 void *user, void *in, size_t len)
262 unsigned char buf[LWS_PRE + 4096];
263 unsigned int rands[4];
268 case LWS_CALLBACK_CLIENT_ESTABLISHED:
270 lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n");
272 lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0]));
273 mirror_lifetime = 16384 + (rands[0] & 65535);
274 /* useful to test single connection stability */
276 mirror_lifetime += 500000;
278 lwsl_info("opened mirror connection with "
279 "%d lifetime\n", mirror_lifetime);
282 * mirror_lifetime is decremented each send, when it reaches
283 * zero the connection is closed in the send callback.
284 * When the close callback comes, wsi_mirror is set to NULL
285 * so a new connection will be opened
287 * start the ball rolling,
288 * LWS_CALLBACK_CLIENT_WRITEABLE will come next service
290 if (!flag_no_mirror_traffic)
291 lws_callback_on_writable(wsi);
294 case LWS_CALLBACK_CLOSED:
295 lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime);
299 case LWS_CALLBACK_CLIENT_WRITEABLE:
300 if (flag_no_mirror_traffic)
302 for (n = 0; n < 1; n++) {
303 lws_get_random(lws_get_context(wsi), rands, sizeof(rands));
304 l += sprintf((char *)&buf[LWS_PRE + l],
306 rands[0] & 0xffffff, /* colour */
307 rands[1] & 511, /* x */
308 rands[2] & 255, /* y */
309 (rands[3] & 31) + 1); /* radius */
312 n = lws_write(wsi, &buf[LWS_PRE], l,
313 opts | LWS_WRITE_TEXT);
317 lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n");
322 if (!mirror_lifetime) {
323 lwsl_info("closing mirror session\n");
326 /* get notified as soon as we can write again */
327 lws_callback_on_writable(wsi);
338 callback_test_raw_client(struct lws *wsi, enum lws_callback_reasons reason,
339 void *user, void *in, size_t len)
342 case LWS_CALLBACK_RAW_ADOPT:
343 lwsl_notice("LWS_CALLBACK_RAW_ADOPT\n");
346 case LWS_CALLBACK_RAW_RX:
347 lwsl_notice("LWS_CALLBACK_RAW_RX %ld\n", (long)len);
351 case LWS_CALLBACK_RAW_CLOSE:
352 lwsl_notice("LWS_CALLBACK_RAW_CLOSE\n");
355 case LWS_CALLBACK_RAW_WRITEABLE:
356 lwsl_notice("LWS_CALLBACK_RAW_WRITEABLE\n");
366 /* list of supported protocols and callbacks */
368 static const struct lws_protocols protocols[] = {
370 "dumb-increment-protocol",
371 callback_dumb_increment,
376 "lws-mirror-protocol",
381 "lws-test-raw-client",
382 callback_test_raw_client,
386 { NULL, NULL, 0, 0 } /* end */
389 static const struct lws_extension exts[] = {
391 "permessage-deflate",
392 lws_extension_callback_pm_deflate,
393 "permessage-deflate; client_no_context_takeover"
397 lws_extension_callback_pm_deflate,
400 { NULL, NULL, NULL /* terminator */ }
405 void sighandler(int sig)
410 static struct option options[] = {
411 { "help", no_argument, NULL, 'h' },
412 { "debug", required_argument, NULL, 'd' },
413 { "port", required_argument, NULL, 'p' },
414 { "ssl", no_argument, NULL, 's' },
415 { "strict-ssl", no_argument, NULL, 'S' },
416 { "version", required_argument, NULL, 'v' },
417 { "undeflated", no_argument, NULL, 'u' },
418 { "multi-test", no_argument, NULL, 'm' },
419 { "nomirror", no_argument, NULL, 'n' },
420 { "longlived", no_argument, NULL, 'l' },
421 { "post", no_argument, NULL, 'o' },
422 { "pingpong-secs", required_argument, NULL, 'P' },
423 { "ssl-cert", required_argument, NULL, 'C' },
424 { "ssl-key", required_argument, NULL, 'K' },
425 { "ssl-ca", required_argument, NULL, 'A' },
426 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
427 { "ssl-crl", required_argument, NULL, 'R' },
432 static int ratelimit_connects(unsigned int *last, unsigned int secs)
436 gettimeofday(&tv, NULL);
438 if (tv.tv_sec - (*last) < secs)
446 int main(int argc, char **argv)
448 int n = 0, m, ret = 0, port = 7681, use_ssl = 0, ietf_version = -1;
449 unsigned int rl_dumb = 0, rl_mirror = 0, do_ws = 1, pp_secs = 0, do_multi = 0;
450 struct lws_context_creation_info info;
451 struct lws_client_connect_info i;
452 struct lws_context *context;
453 const char *prot, *p;
455 char cert_path[1024] = "";
456 char key_path[1024] = "";
457 char ca_path[1024] = "";
459 memset(&info, 0, sizeof info);
461 lwsl_notice("libwebsockets test client - license LGPL2.1+SLE\n");
462 lwsl_notice("(C) Copyright 2010-2016 Andy Green <andy@warmcat.com>\n");
468 n = getopt_long(argc, argv, "Snuv:hsp:d:lC:K:A:P:mo", options, NULL);
473 lws_set_log_level(atoi(optarg), NULL);
475 case 's': /* lax SSL, allow selfsigned, skip checking hostname */
476 use_ssl = LCCSCF_USE_SSL |
477 LCCSCF_ALLOW_SELFSIGNED |
478 LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK;
480 case 'S': /* Strict SSL, no selfsigned, check server hostname */
481 use_ssl = LCCSCF_USE_SSL;
487 pp_secs = atoi(optarg);
488 lwsl_notice("Setting pingpong interval to %d\n", pp_secs);
494 ietf_version = atoi(optarg);
506 flag_no_mirror_traffic = 1;
507 lwsl_notice("Disabled sending mirror data (for pingpong testing)\n");
510 strncpy(cert_path, optarg, sizeof(cert_path) - 1);
511 cert_path[sizeof(cert_path) - 1] = '\0';
514 strncpy(key_path, optarg, sizeof(key_path) - 1);
515 key_path[sizeof(key_path) - 1] = '\0';
518 strncpy(ca_path, optarg, sizeof(ca_path) - 1);
519 ca_path[sizeof(ca_path) - 1] = '\0';
522 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
524 strncpy(crl_path, optarg, sizeof(crl_path) - 1);
525 crl_path[sizeof(crl_path) - 1] = '\0';
536 signal(SIGINT, sighandler);
538 memset(&i, 0, sizeof(i));
541 if (lws_parse_uri(argv[optind], &prot, &i.address, &i.port, &p))
544 /* add back the leading / on path */
546 strncpy(path + 1, p, sizeof(path) - 2);
547 path[sizeof(path) - 1] = '\0';
550 if (!strcmp(prot, "http") || !strcmp(prot, "ws"))
552 if (!strcmp(prot, "https") || !strcmp(prot, "wss"))
554 use_ssl = LCCSCF_USE_SSL;
557 * create the websockets context. This tracks open connections and
558 * knows how to route any traffic and which protocol version to use,
559 * and if each connection is client or server side.
561 * For this client-only demo, we tell it to not listen on any port.
564 info.port = CONTEXT_PORT_NO_LISTEN;
565 info.protocols = protocols;
568 info.ws_ping_pong_interval = pp_secs;
569 info.extensions = exts;
571 #if defined(LWS_OPENSSL_SUPPORT)
572 info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
577 * If the server wants us to present a valid SSL client certificate
578 * then we can set it up here.
582 info.ssl_cert_filepath = cert_path;
584 info.ssl_private_key_filepath = key_path;
587 * A CA cert and CRL can be used to validate the cert send by the server
590 info.ssl_ca_filepath = ca_path;
592 #if defined(LWS_OPENSSL_SUPPORT) && defined(LWS_HAVE_SSL_CTX_set1_param)
593 else if (crl_path[0])
594 lwsl_notice("WARNING, providing a CRL requires a CA cert!\n");
598 if (use_ssl & LCCSCF_USE_SSL)
599 lwsl_notice(" Using SSL\n");
601 lwsl_notice(" SSL disabled\n");
602 if (use_ssl & LCCSCF_ALLOW_SELFSIGNED)
603 lwsl_notice(" Selfsigned certs allowed\n");
605 lwsl_notice(" Cert must validate correctly (use -s to allow selfsigned)\n");
606 if (use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)
607 lwsl_notice(" Skipping peer cert hostname check\n");
609 lwsl_notice(" Requiring peer cert hostname matches\n");
611 context = lws_create_context(&info);
612 if (context == NULL) {
613 fprintf(stderr, "Creating libwebsocket context failed\n");
618 i.ssl_connection = use_ssl;
620 i.origin = i.address;
621 i.ietf_version_or_minus_one = ietf_version;
623 if (!strcmp(prot, "http") || !strcmp(prot, "https")) {
624 lwsl_notice("using %s mode (non-ws)\n", prot);
627 lwsl_notice("POST mode\n");
633 if (!strcmp(prot, "raw")) {
635 i.protocol = "lws-test-raw-client";
636 lwsl_notice("using RAW mode connection\n");
639 lwsl_notice("using %s mode (ws)\n", prot);
642 * sit there servicing the websocket context to handle incoming
643 * packets, and drawing random circles on the mirror protocol websocket
645 * nothing happens until the client websocket connection is
646 * asynchronously established... calling lws_client_connect() only
647 * instantiates the connection logically, lws_service() progresses it
652 while (!force_exit) {
655 for (n = 0; n < ARRAY_SIZE(wsi_multi); n++) {
656 if (!wsi_multi[n] && ratelimit_connects(&rl_multi[n], 2u)) {
657 lwsl_notice("dumb %d: connecting\n", n);
658 i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
659 i.pwsi = &wsi_multi[n];
660 lws_client_connect_via_info(&i);
666 if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
667 lwsl_notice("dumb: connecting\n");
668 i.protocol = protocols[PROTOCOL_DUMB_INCREMENT].name;
670 lws_client_connect_via_info(&i);
673 if (!wsi_mirror && ratelimit_connects(&rl_mirror, 2u)) {
674 lwsl_notice("mirror: connecting\n");
675 i.protocol = protocols[PROTOCOL_LWS_MIRROR].name;
676 i.pwsi = &wsi_mirror;
677 wsi_mirror = lws_client_connect_via_info(&i);
680 if (!wsi_dumb && ratelimit_connects(&rl_dumb, 2u)) {
681 lwsl_notice("http: connecting\n");
683 lws_client_connect_via_info(&i);
687 lws_service(context, 500);
693 lwsl_notice("doing lws_callback_on_writable_all_protocol\n");
694 lws_callback_on_writable_all_protocol(context, &protocols[PROTOCOL_DUMB_INCREMENT]);
699 lwsl_err("Exiting\n");
700 lws_context_destroy(context);
705 fprintf(stderr, "Usage: libwebsockets-test-client "
706 "<server address> [--port=<p>] "
707 "[--ssl] [-k] [-v <ver>] "
708 "[-d <log bitfield>] [-l]\n");