| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399 |
- /*
- * lws-api-test-lws_sequencer
- *
- * Written in 2019 by Andy Green <[email protected]>
- *
- * This file is made available under the Creative Commons CC0 1.0
- * Universal Public Domain Dedication.
- *
- * This api test uses the lws_sequencer api to make five http client requests
- * to libwebsockets.org in sequence, from inside the event loop. The fourth
- * fourth http client request is directed to port 22 where it stalls
- * triggering the lws_sequencer timeout flow. The fifth is given a nonexistant
- * dns name and is expected to fail.
- */
- #include <libwebsockets.h>
- #include <signal.h>
- static int interrupted, test_good = 0;
- enum {
- SEQ1,
- SEQ2,
- SEQ3_404,
- SEQ4_TIMEOUT, /* we expect to timeout */
- SEQ5_BAD_ADDRESS /* we expect the connection to fail */
- };
- /*
- * This is the user defined struct whose space is allocated along with the
- * sequencer when that is created.
- *
- * You'd put everything your sequencer needs to do its job in here.
- */
- struct myseq {
- struct lws_vhost *vhost;
- struct lws *cwsi; /* client wsi for current step if any */
- int state; /* which test we're on */
- int http_resp;
- };
- /* sequencer messages specific to this sequencer */
- enum {
- SEQ_MSG_CLIENT_FAILED = LWSSEQ_USER_BASE,
- SEQ_MSG_CLIENT_DONE,
- };
- /* this is the sequence of GETs we will do */
- static const char *url_paths[] = {
- "https://libwebsockets.org/index.html",
- "https://libwebsockets.org/lws.css",
- "https://libwebsockets.org/404.html",
- "https://libwebsockets.org:22", /* this causes us to time out */
- "https://doesntexist.invalid/" /* fail early in connect */
- };
- static void
- sigint_handler(int sig)
- {
- interrupted = 1;
- }
- /*
- * This is the sequencer-aware http protocol handler. It monitors the client
- * http action and queues messages for the sequencer when something definitive
- * happens.
- */
- static int
- callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user,
- void *in, size_t len)
- {
- struct myseq *s = (struct myseq *)user;
- int seq_msg = SEQ_MSG_CLIENT_FAILED;
- switch (reason) {
- /* because we are protocols[0] ... */
- case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
- lwsl_notice("CLIENT_CONNECTION_ERROR: %s\n",
- in ? (char *)in : "(null)");
- goto notify;
- case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP:
- if (!s)
- return 1;
- s->http_resp = lws_http_client_http_response(wsi);
- lwsl_info("Connected with server response: %d\n", s->http_resp);
- break;
- /* chunks of chunked content, with header removed */
- case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
- lwsl_info("RECEIVE_CLIENT_HTTP_READ: read %d\n", (int)len);
- #if 0 /* enable to dump the html */
- {
- const char *p = in;
- while (len--)
- if (*p < 0x7f)
- putchar(*p++);
- else
- putchar('.');
- }
- #endif
- return 0; /* don't passthru */
- /* uninterpreted http content */
- case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
- {
- char buffer[1024 + LWS_PRE];
- char *px = buffer + LWS_PRE;
- int lenx = sizeof(buffer) - LWS_PRE;
- if (lws_http_client_read(wsi, &px, &lenx) < 0)
- return -1;
- }
- return 0; /* don't passthru */
- case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
- lwsl_notice("LWS_CALLBACK_COMPLETED_CLIENT_HTTP: wsi %p\n",
- wsi);
- if (!s)
- return 1;
- /*
- * We got a definitive transaction completion
- */
- seq_msg = SEQ_MSG_CLIENT_DONE;
- goto notify;
- case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
- lwsl_info("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n");
- if (!s)
- return 1;
- lwsl_user("%s: wsi %p: seq failed at CLOSED_CLIENT_HTTP\n",
- __func__, wsi);
- goto notify;
- default:
- break;
- }
- return lws_callback_http_dummy(wsi, reason, user, in, len);
- notify:
- /*
- * We only inform the sequencer of a definitive outcome for our step.
- *
- * So once we have informed it, we detach ourselves from the sequencer
- * and the sequencer from ourselves. Wsi may want to live on but after
- * we got our result and moved on to the next test or completed, the
- * sequencer doesn't want to hear from it again.
- */
- if (!s)
- return 1;
- lws_set_wsi_user(wsi, NULL);
- s->cwsi = NULL;
- lws_seq_queue_event(lws_seq_from_user(s), seq_msg,
- NULL, NULL);
- return 0;
- }
- static const struct lws_protocols protocols[] = {
- { "seq-test-http", callback_http, 0, 0, },
- { NULL, NULL, 0, 0 }
- };
- static int
- sequencer_start_client(struct myseq *s)
- {
- struct lws_client_connect_info i;
- const char *prot, *path1;
- char uri[128], path[128];
- int n;
- lws_strncpy(uri, url_paths[s->state], sizeof(uri));
- memset(&i, 0, sizeof i);
- i.context = lws_seq_get_context(lws_seq_from_user(s));
- if (lws_parse_uri(uri, &prot, &i.address, &i.port, &path1)) {
- lwsl_err("%s: uri error %s\n", __func__, uri);
- }
- if (!strcmp(prot, "https"))
- i.ssl_connection = LCCSCF_USE_SSL;
- path[0] = '/';
- n = 1;
- if (path1[0] == '/')
- n = 0;
- lws_strncpy(&path[n], path1, sizeof(path) - 1);
- i.path = path;
- i.host = i.address;
- i.origin = i.address;
- i.method = "GET";
- i.vhost = s->vhost;
- i.userdata = s;
- i.protocol = protocols[0].name;
- i.local_protocol_name = protocols[0].name;
- i.pwsi = &s->cwsi;
- if (!lws_client_connect_via_info(&i)) {
- lwsl_notice("%s: connecting to %s://%s:%d%s failed\n",
- __func__, prot, i.address, i.port, path);
- /* we couldn't even get started with the client connection */
- lws_seq_queue_event(lws_seq_from_user(s),
- SEQ_MSG_CLIENT_FAILED, NULL, NULL);
- return 1;
- }
- lws_seq_timeout_us(lws_seq_from_user(s), 3 * LWS_US_PER_SEC);
- lwsl_notice("%s: wsi %p: connecting to %s://%s:%d%s\n", __func__,
- s->cwsi, prot, i.address, i.port, path);
- return 0;
- }
- /*
- * The sequencer callback handles queued sequencer messages in the order they
- * were queued. The messages are presented from the event loop thread context
- * even if they were queued from a different thread.
- */
- static lws_seq_cb_return_t
- sequencer_cb(struct lws_sequencer *seq, void *user, int event,
- void *data, void *aux)
- {
- struct myseq *s = (struct myseq *)user;
- switch ((int)event) {
- case LWSSEQ_CREATED: /* our sequencer just got started */
- s->state = SEQ1; /* first thing we'll do is the first url */
- goto step;
- case LWSSEQ_DESTROYED:
- /*
- * This sequencer is about to be destroyed. If we have any
- * other assets in play, detach them from us.
- */
- if (s->cwsi)
- lws_set_wsi_user(s->cwsi, NULL);
- interrupted = 1;
- break;
- case LWSSEQ_TIMED_OUT: /* current step timed out */
- if (s->state == SEQ4_TIMEOUT) {
- lwsl_user("%s: test %d got expected timeout\n",
- __func__, s->state);
- goto done;
- }
- lwsl_user("%s: seq timed out at step %d\n", __func__, s->state);
- return LWSSEQ_RET_DESTROY;
- case SEQ_MSG_CLIENT_FAILED:
- if (s->state == SEQ5_BAD_ADDRESS) {
- /*
- * in this specific case, we expect to fail
- */
- lwsl_user("%s: test %d failed as expected\n",
- __func__, s->state);
- goto done;
- }
- lwsl_user("%s: seq failed at step %d\n", __func__, s->state);
- return LWSSEQ_RET_DESTROY;
- case SEQ_MSG_CLIENT_DONE:
- if (s->state >= SEQ4_TIMEOUT) {
- /*
- * In these specific cases, done would be a failure,
- * we expected to timeout or fail
- */
- lwsl_user("%s: seq failed at step %d\n", __func__,
- s->state);
- return LWSSEQ_RET_DESTROY;
- }
- lwsl_user("%s: seq done step %d (resp %d)\n", __func__,
- s->state, s->http_resp);
- done:
- lws_seq_timeout_us(lws_seq_from_user(s), LWSSEQTO_NONE);
- s->state++;
- if (s->state == LWS_ARRAY_SIZE(url_paths)) {
- /* the sequence has completed */
- lwsl_user("%s: sequence completed OK\n", __func__);
- test_good = 1;
- return LWSSEQ_RET_DESTROY;
- }
- step:
- sequencer_start_client(s);
- break;
- default:
- break;
- }
- return LWSSEQ_RET_CONTINUE;
- }
- int
- main(int argc, const char **argv)
- {
- int n = 1, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE;
- struct lws_context_creation_info info;
- struct lws_context *context;
- struct lws_sequencer *seq;
- struct lws_vhost *vh;
- lws_seq_info_t i;
- struct myseq *s;
- const char *p;
- /* the normal lws init */
- signal(SIGINT, sigint_handler);
- if ((p = lws_cmdline_option(argc, argv, "-d")))
- logs = atoi(p);
- lws_set_log_level(logs, NULL);
- lwsl_user("LWS API selftest: lws_sequencer\n");
- memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */
- info.port = CONTEXT_PORT_NO_LISTEN;
- info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT |
- LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
- info.protocols = protocols;
- #if defined(LWS_WITH_MBEDTLS) || defined(USE_WOLFSSL)
- /*
- * OpenSSL uses the system trust store. mbedTLS has to be told which
- * CA to trust explicitly.
- */
- info.client_ssl_ca_filepath = "./libwebsockets.org.cer";
- #endif
- context = lws_create_context(&info);
- if (!context) {
- lwsl_err("lws init failed\n");
- return 1;
- }
- vh = lws_create_vhost(context, &info);
- if (!vh) {
- lwsl_err("Failed to create first vhost\n");
- goto bail1;
- }
- /*
- * Create the sequencer... when the event loop starts, it will
- * receive the LWSSEQ_CREATED callback
- */
- memset(&i, 0, sizeof(i));
- i.context = context;
- i.user_size = sizeof(struct myseq);
- i.puser = (void **)&s;
- i.cb = sequencer_cb;
- i.name = "seq";
- seq = lws_seq_create(&i);
- if (!seq) {
- lwsl_err("%s: unable to create sequencer\n", __func__);
- goto bail1;
- }
- s->vhost = vh;
- /* the usual lws event loop */
- while (n >= 0 && !interrupted)
- n = lws_service(context, 0);
- bail1:
- lwsl_user("Completed: %s\n", !test_good ? "FAIL" : "PASS");
- lws_context_destroy(context);
- return !test_good;
- }
|